elanous 0.2.9 → 0.2.10
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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { debug } from '../debug/log.js';
|
|
5
|
+
import { notifyExecRequestTransition } from '../web-push/notify-exec-request.js';
|
|
5
6
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
7
|
import { lastJsonObject, runGraph, type GraphRunState } from '../graph-runner/runner.js';
|
|
7
8
|
import { installedGraphs, planExecRequest, type ExecPlanItem, type InstalledGraph } from './planner.js';
|
|
8
|
-
import { ExecRequestStore, type ExecRequest, type ExecResult, type ExecSeat } from './store.js';
|
|
9
|
+
import { ExecRequestStore, type ExecAttachment, type ExecRequest, type ExecResult, type ExecSeat } from './store.js';
|
|
10
|
+
import { answerAsSeat } from '../intake-plane/seat-answer.js';
|
|
11
|
+
import { redactSecrets } from '../task-cards/card-store.js';
|
|
9
12
|
|
|
10
13
|
export interface ExecRunnerDeps {
|
|
11
14
|
store?: ExecRequestStore;
|
|
12
15
|
graphs?: () => Promise<InstalledGraph[]>;
|
|
13
|
-
plan?: (text: string) => Promise<ExecPlanItem[]>;
|
|
16
|
+
plan?: (text: string, attachments?: readonly ExecAttachment[]) => Promise<ExecPlanItem[]>;
|
|
17
|
+
answer?: typeof answerAsSeat;
|
|
14
18
|
run?: typeof runGraph;
|
|
15
19
|
root?: string;
|
|
16
20
|
}
|
|
@@ -38,7 +42,11 @@ function safeFile(path: string, root: string, graphId: string, runId: string): s
|
|
|
38
42
|
? realpathSync(artifactsDir) : null;
|
|
39
43
|
const inRunDirectory = realArtifactsDir === join(base, `${runId}.json.artifacts`)
|
|
40
44
|
&& real.startsWith(`${realArtifactsDir}${sep}`);
|
|
41
|
-
|
|
45
|
+
// EXEC2 — plugin graphs (doc-draft · geo-check) write into `<graphId>/<runId>/`; that directory is this run's own too.
|
|
46
|
+
const runDir = join(allowed, runId);
|
|
47
|
+
const realRunDir = existsSync(runDir) && statSync(runDir).isDirectory() ? realpathSync(runDir) : null;
|
|
48
|
+
const inOwnRunDir = realRunDir === join(base, runId) && real.startsWith(`${realRunDir}${sep}`);
|
|
49
|
+
if ((!local && !inRunDirectory && !inOwnRunDir) || !statSync(real).isFile()) return null;
|
|
42
50
|
return real;
|
|
43
51
|
} catch { return null; }
|
|
44
52
|
}
|
|
@@ -56,8 +64,32 @@ function fileName(path: string, runId: string): string {
|
|
|
56
64
|
return `${runId}--${digest}--${basename(path)}`;
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
/** EXEC2 — `report` is how geo-check names its file; treat it like `file`/`path`. */
|
|
68
|
+
function fileCandidate(output: Record<string, unknown>): string | null {
|
|
69
|
+
if (typeof output.file === 'string') return output.file;
|
|
70
|
+
if (typeof output.path === 'string') return output.path;
|
|
71
|
+
if (typeof output.report === 'string' && /\.(md|txt|pdf)$/i.test(output.report)) return output.report;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const TEXT_RESULT_LIMIT = 200_000;
|
|
76
|
+
const TEXT_RESULT_NAME = 'result.md';
|
|
77
|
+
|
|
78
|
+
/** EXEC2 — a done seat whose graph only answered in text gets that text as a run artifact (redacted), so the result is viewable. */
|
|
79
|
+
function textResultFile(seat: ExecSeat, state: GraphRunState, root: string): string | null {
|
|
80
|
+
const outputs = [...(state.nodes ?? [])].reverse().map(node => lastJsonObject(node.output)).filter((o): o is Record<string, unknown> => !!o);
|
|
81
|
+
const text = outputs.map(o => o.markdown ?? o.text).find((v): v is string => typeof v === 'string' && v.trim().length > 0);
|
|
82
|
+
if (!text || !SEGMENT.test(seat.graphId) || !SEGMENT.test(seat.runId)) return null;
|
|
83
|
+
const dir = join(root, 'graph-runs', seat.graphId, `${seat.runId}.json.artifacts`);
|
|
84
|
+
const path = join(dir, TEXT_RESULT_NAME);
|
|
85
|
+
try {
|
|
86
|
+
if (!existsSync(path)) { mkdirSync(dir, { recursive: true }); writeFileSync(path, redactSecrets(text.slice(0, TEXT_RESULT_LIMIT)), { mode: 0o600 }); }
|
|
87
|
+
return path;
|
|
88
|
+
} catch { return null; }
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
function resultOf(output: Record<string, unknown>, seat: ExecSeat, id: string, root: string): ExecResult | null {
|
|
60
|
-
const candidate =
|
|
92
|
+
const candidate = fileCandidate(output);
|
|
61
93
|
let url: string;
|
|
62
94
|
let kind: ExecResult['kind'];
|
|
63
95
|
let name: string;
|
|
@@ -95,6 +127,7 @@ function syncRun(item: ExecRequest, seat: ExecSeat, state: GraphRunState, root:
|
|
|
95
127
|
if (state.status === 'awaiting-approval' && state.pending && !state.pending.decision && !existsSync(claim)) {
|
|
96
128
|
if (!item.approvals.some(a => a.graphId === seat.graphId && a.runId === seat.runId)) {
|
|
97
129
|
item.approvals.push({ graphId: seat.graphId, runId: seat.runId, message: state.pending.message });
|
|
130
|
+
notifyTransition(item, item.status, item.approvals.length);
|
|
98
131
|
}
|
|
99
132
|
} else item.approvals = item.approvals.filter(a => a.graphId !== seat.graphId || a.runId !== seat.runId);
|
|
100
133
|
for (const node of state.nodes ?? []) {
|
|
@@ -107,9 +140,28 @@ function syncRun(item: ExecRequest, seat: ExecSeat, state: GraphRunState, root:
|
|
|
107
140
|
if (result && !item.results.some(existing => existing.seat === result.seat && existing.url === result.url)) item.results.push(result);
|
|
108
141
|
}
|
|
109
142
|
}
|
|
143
|
+
if (seat.status === 'done' && !item.results.some(existing => existing.seat === seat.seat)) {
|
|
144
|
+
const file = textResultFile(seat, state, root);
|
|
145
|
+
const result = file ? resultOf({ file, title: seat.title }, seat, item.id, root) : null;
|
|
146
|
+
if (result) item.results.push(result);
|
|
147
|
+
else {
|
|
148
|
+
const keys = [...new Set((state.nodes ?? []).flatMap(node => Object.keys(lastJsonObject(node.output) ?? {})))];
|
|
149
|
+
debug.log('exec.result', 'none', { seat: seat.seat, graphId: seat.graphId, keys });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
110
152
|
const summary = [...(state.nodes ?? [])].reverse().map(node => lastJsonObject(node.output)?.summary).find(value => typeof value === 'string');
|
|
111
153
|
if (typeof summary === 'string') item.summary = summary;
|
|
112
|
-
if (seat.status === 'failed') seat.reason = state
|
|
154
|
+
if (seat.status === 'failed') seat.reason = failureReason(state);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** EXEC2 — the failing node's own error (e.g. doc-draft `check` {outcome:'fail', error}) beats the terminal gate's empty one. */
|
|
158
|
+
function failureReason(state: GraphRunState): string {
|
|
159
|
+
for (const node of [...(state.nodes ?? [])].reverse()) {
|
|
160
|
+
if (typeof node.error === 'string' && node.error) return node.error;
|
|
161
|
+
const output = lastJsonObject(node.output);
|
|
162
|
+
if (output && typeof output.error === 'string' && output.error) return redactSecrets(output.error);
|
|
163
|
+
}
|
|
164
|
+
return `그래프 런 ${state.status}`;
|
|
113
165
|
}
|
|
114
166
|
|
|
115
167
|
const PRIOR_TEXT_LIMIT = 4_000;
|
|
@@ -129,8 +181,14 @@ export function withPriorResults(inputs: Record<string, unknown> | undefined, pr
|
|
|
129
181
|
return { ...base, context: `${previous}앞 자리 결과:\n${parts.join('\n\n')}` };
|
|
130
182
|
}
|
|
131
183
|
|
|
184
|
+
function notifyTransition(item: ExecRequest, from: ExecRequest['status'], pendingApprovals: number): void {
|
|
185
|
+
void notifyExecRequestTransition({ id: item.id, text: item.text, from, to: item.status,
|
|
186
|
+
summary: item.summary, pendingApprovals }).catch(() => { /* Push never interrupts execution. */ });
|
|
187
|
+
}
|
|
188
|
+
|
|
132
189
|
function aggregate(item: ExecRequest): void {
|
|
133
190
|
if (item.status === 'planning' || item.seats.length === 0) return;
|
|
191
|
+
const previous = item.status;
|
|
134
192
|
if (item.seats.some(seat => seat.status === 'waiting' || seat.status === 'running')) item.status = 'running';
|
|
135
193
|
else item.status = item.seats.some(seat => seat.status === 'failed') ? 'failed' : 'done';
|
|
136
194
|
// 실패면 요약은 «실패 사유»가 이긴다 — 먼저 끝난 자리의 산출 설명이 남아 있어도 덮는다(혼합 결과에서 성공처럼 보이지 않게).
|
|
@@ -138,6 +196,7 @@ function aggregate(item: ExecRequest): void {
|
|
|
138
196
|
const reasons = item.seats.filter(seat => seat.status === 'failed').map(seat => seat.reason ?? `${seat.seat}: 그래프 실행 실패`);
|
|
139
197
|
if (reasons.length > 0) item.summary = reasons.join('; ');
|
|
140
198
|
}
|
|
199
|
+
if (previous !== item.status && (item.status === 'done' || item.status === 'failed')) notifyTransition(item, previous, item.approvals.length);
|
|
141
200
|
}
|
|
142
201
|
|
|
143
202
|
export class ExecRequestRunner {
|
|
@@ -156,10 +215,12 @@ export class ExecRequestRunner {
|
|
|
156
215
|
if (item.status === 'planning') {
|
|
157
216
|
item.status = 'failed';
|
|
158
217
|
item.summary = '데몬 재시작으로 COO 계획이 중단됐습니다';
|
|
218
|
+
notifyTransition(item, 'planning', item.approvals.length);
|
|
159
219
|
} else {
|
|
160
220
|
if (item.seats.length === 0) {
|
|
161
221
|
item.status = 'failed';
|
|
162
222
|
item.summary = '데몬 재시작으로 자리 배정이 중단됐습니다';
|
|
223
|
+
notifyTransition(item, 'running', item.approvals.length);
|
|
163
224
|
}
|
|
164
225
|
for (const seat of item.seats) {
|
|
165
226
|
if (seat.status === 'done' || seat.status === 'failed') continue;
|
|
@@ -192,14 +253,16 @@ export class ExecRequestRunner {
|
|
|
192
253
|
}
|
|
193
254
|
}
|
|
194
255
|
|
|
195
|
-
submit(text: string): ExecRequest {
|
|
196
|
-
const item = this.store.create(text);
|
|
256
|
+
submit(text: string, attachments?: ExecAttachment[]): ExecRequest {
|
|
257
|
+
const item = this.store.create(text, attachments);
|
|
197
258
|
void this.execute(item.id).catch(error => {
|
|
198
259
|
const current = this.store.get(item.id);
|
|
199
260
|
if (!current) return;
|
|
261
|
+
const previous = current.status;
|
|
200
262
|
current.status = 'failed';
|
|
201
263
|
current.summary = error instanceof Error ? error.message : String(error);
|
|
202
264
|
this.store.save(current);
|
|
265
|
+
if (previous !== current.status) notifyTransition(current, previous, current.approvals.length);
|
|
203
266
|
debug.log('exec-requests', 'failed', { id: item.id, reason: current.summary });
|
|
204
267
|
});
|
|
205
268
|
return item;
|
|
@@ -209,8 +272,40 @@ export class ExecRequestRunner {
|
|
|
209
272
|
const item = this.store.get(id);
|
|
210
273
|
if (!item) return;
|
|
211
274
|
const graphs = await (this.deps.graphs ?? installedGraphs)();
|
|
212
|
-
const plans = await (this.deps.plan ?? ((text: string) => planExecRequest(text, { graphs: async () => graphs })))(item.text);
|
|
275
|
+
const plans = await (this.deps.plan ?? ((text: string, attachments?: readonly ExecAttachment[]) => planExecRequest(text, { graphs: async () => graphs, attachments })))(item.text, item.attachments);
|
|
213
276
|
const byId = new Map(graphs.map(graph => [graph.id, graph]));
|
|
277
|
+
// Every clause must be a question — «상황 알려줘? 카드 만들어줘» carries a work item (review r1).
|
|
278
|
+
const clauses = [...item.text.trim().matchAll(/([^??.!。\n]+)([??]?)/g)].map(m => ({ body: m[1]!.trim(), asked: m[2] !== '' })).filter(c => c.body);
|
|
279
|
+
const isQuestion = clauses.length > 0 && clauses.every(c => c.asked
|
|
280
|
+
|| /(?:알려\s*줘|알려\s*주세요|보여\s*줘|보여\s*주세요|어떻게|무엇|뭐|어떤|언제|누구|질문|인가요|나요|습니까)$/.test(c.body));
|
|
281
|
+
if (isQuestion && plans.length > 0 && plans.every(plan => !plan.graphId
|
|
282
|
+
&& !/\s—\s.+\s필요\s*$/.test(plan.title) && !/\s—\s.+\s필요\s*$/.test(plan.reason ?? '')
|
|
283
|
+
&& !/(?:초안|작성|제작|생성|게시|분석|전략|메일|보고서)/.test(plan.title)) && !(item.attachments?.length)) {
|
|
284
|
+
for (const plan of plans) {
|
|
285
|
+
const answered = await (this.deps.answer ?? answerAsSeat)(plan.seat, item.text);
|
|
286
|
+
if (!answered) {
|
|
287
|
+
item.seats.push({ seat: plan.seat, title: plan.title, status: 'failed', graphId: '', runId: '',
|
|
288
|
+
reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` });
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const seat: ExecSeat = { seat: plan.seat, title: plan.title, status: 'done', graphId: 'seat-answer', runId: randomUUID() };
|
|
292
|
+
const dir = join(this.root, 'graph-runs', seat.graphId);
|
|
293
|
+
const statePath = join(dir, `${seat.runId}.json`);
|
|
294
|
+
const artifactDir = join(dir, `${seat.runId}.json.artifacts`);
|
|
295
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
296
|
+
const file = join(artifactDir, TEXT_RESULT_NAME);
|
|
297
|
+
writeFileSync(file, redactSecrets(answered.text.slice(0, TEXT_RESULT_LIMIT)), { mode: 0o600 });
|
|
298
|
+
const state: GraphRunState = { graphId: seat.graphId, runId: seat.runId, status: 'done', path: [], executed: 0, dryRun: false, statePath, nodes: [] };
|
|
299
|
+
writeFileSync(statePath, JSON.stringify(state), { mode: 0o600 });
|
|
300
|
+
item.seats.push(seat);
|
|
301
|
+
const result = resultOf({ file, title: answered.title }, seat, id, this.root);
|
|
302
|
+
if (result) item.results.push(result);
|
|
303
|
+
}
|
|
304
|
+
item.status = 'running';
|
|
305
|
+
aggregate(item);
|
|
306
|
+
this.store.save(item);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
214
309
|
item.seats = plans.map(plan => ({ seat: plan.seat, title: plan.title, status: plan.reason || !byId.has(plan.graphId) ? 'failed' : 'waiting', graphId: plan.graphId,
|
|
215
310
|
runId: plan.reason || !byId.has(plan.graphId) ? '' : randomUUID(), inputs: plan.inputs, ...(plan.after?.length ? { after: plan.after } : {}),
|
|
216
311
|
...(plan.reason || !byId.has(plan.graphId) ? { reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` } : {}) }));
|
|
@@ -366,12 +461,14 @@ export class ExecRequestRunner {
|
|
|
366
461
|
for (const entry of Array.isArray(output.artifacts) ? output.artifacts : [output]) {
|
|
367
462
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
368
463
|
const artifact = entry as Record<string, unknown>;
|
|
369
|
-
const candidate =
|
|
464
|
+
const candidate = fileCandidate(artifact);
|
|
370
465
|
if (typeof candidate !== 'string') continue;
|
|
371
466
|
const path = safeFile(candidate, this.root, seat.graphId, seat.runId);
|
|
372
467
|
if (path && fileName(path, seat.runId) === name) return path;
|
|
373
468
|
}
|
|
374
469
|
}
|
|
470
|
+
const text = safeFile(join(this.root, 'graph-runs', seat.graphId, `${seat.runId}.json.artifacts`, TEXT_RESULT_NAME), this.root, seat.graphId, seat.runId);
|
|
471
|
+
if (text && fileName(text, seat.runId) === name) return text;
|
|
375
472
|
} catch { return null; }
|
|
376
473
|
return null;
|
|
377
474
|
}
|
|
@@ -22,9 +22,15 @@ export interface ExecResult {
|
|
|
22
22
|
url: string;
|
|
23
23
|
sources?: Array<{ title: string; url: string }>;
|
|
24
24
|
}
|
|
25
|
+
export interface ExecAttachment {
|
|
26
|
+
name: string;
|
|
27
|
+
path: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
export interface ExecRequest {
|
|
26
31
|
id: string;
|
|
27
32
|
text: string;
|
|
33
|
+
attachments?: ExecAttachment[];
|
|
28
34
|
createdAt: string;
|
|
29
35
|
status: 'planning' | 'running' | 'done' | 'failed';
|
|
30
36
|
summary: string;
|
|
@@ -39,8 +45,8 @@ export class ExecRequestStore {
|
|
|
39
45
|
readonly dir: string;
|
|
40
46
|
constructor(configDir = getElanousConfigDir()) { this.dir = join(configDir, 'exec-requests'); }
|
|
41
47
|
|
|
42
|
-
create(text: string): ExecRequest {
|
|
43
|
-
const item: ExecRequest = { id: randomUUID(), text, createdAt: new Date().toISOString(), status: 'planning', summary: '', seats: [], results: [], approvals: [] };
|
|
48
|
+
create(text: string, attachments?: ExecAttachment[]): ExecRequest {
|
|
49
|
+
const item: ExecRequest = { id: randomUUID(), text, ...(attachments !== undefined ? { attachments } : {}), createdAt: new Date().toISOString(), status: 'planning', summary: '', seats: [], results: [], approvals: [] };
|
|
44
50
|
this.save(item);
|
|
45
51
|
return item;
|
|
46
52
|
}
|
package/src/field/field-media.ts
CHANGED
|
@@ -219,19 +219,22 @@ function uniqueName(dir: string, name: string, size: number, taken: Set<string>)
|
|
|
219
219
|
|
|
220
220
|
export const FIELD_CAPTION_TAG = '#현장';
|
|
221
221
|
|
|
222
|
-
/** 캡션에 독립 토큰 `#현장` 이 있으면 `{ event? }`, 없으면 null.
|
|
223
|
-
* `#현장 <slug>` — 바로 다음 토큰을 소문자로 바꿔 슬러그 규칙에 맞으면 행사 이름, 아니면 기본 행사. */
|
|
224
|
-
export function parseFieldCaption(caption: string | undefined | null): { event?: string; text?: string } | null {
|
|
222
|
+
/** 캡션에 독립 토큰 `#현장` 이 있으면 `{ event?, mode?, text? }`, 없으면 null.
|
|
223
|
+
* `#현장 <slug>` — 모드 토큰을 건너뛴 바로 다음 토큰을 소문자로 바꿔 슬러그 규칙에 맞으면 행사 이름, 아니면 기본 행사. */
|
|
224
|
+
export function parseFieldCaption(caption: string | undefined | null): { event?: string; mode?: 'instant'; text?: string } | null {
|
|
225
225
|
if (!caption) return null;
|
|
226
226
|
const tokens = caption.trim().split(/\s+/);
|
|
227
227
|
const at = tokens.indexOf(FIELD_CAPTION_TAG);
|
|
228
228
|
if (at < 0) return null;
|
|
229
|
-
const
|
|
229
|
+
const isInstant = (token: string) => token === '즉석' || token.toLowerCase() === 'instant';
|
|
230
|
+
const mode = tokens.some(isInstant) ? 'instant' : undefined;
|
|
231
|
+
const eventAt = at + 1 + (tokens[at + 1] && isInstant(tokens[at + 1]) ? 1 : 0);
|
|
232
|
+
const next = tokens[eventAt]?.toLowerCase();
|
|
230
233
|
const hasEvent = !!next && isFieldSlug(next);
|
|
231
|
-
// 태그·행사
|
|
232
|
-
const rest = tokens.filter((
|
|
234
|
+
// 태그·행사 이름·모드를 뺀 나머지가 «글»(자막) — 태그 앞에 쓴 글도 같이.
|
|
235
|
+
const rest = tokens.filter((token, i) => i !== at && !(hasEvent && i === eventAt) && !isInstant(token)).join(' ');
|
|
233
236
|
const text = normalizeFieldCaption(rest);
|
|
234
|
-
return { ...(hasEvent ? { event: next } : {}), ...(text ? { text } : {}) };
|
|
237
|
+
return { ...(hasEvent ? { event: next } : {}), ...(mode ? { mode } : {}), ...(text ? { text } : {}) };
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
export const FIELD_CAPTIONS_FILE = 'captions.txt';
|
package/src/field/field-reel.ts
CHANGED
|
@@ -4,11 +4,12 @@ import { join, resolve } from 'node:path';
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { spawn, execFileSync } from 'node:child_process';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
7
|
+
import { mergeCapturedPath } from '../shell-env-bootstrap.js';
|
|
7
8
|
import { listFieldMedia } from './field-media.js';
|
|
8
9
|
import { startFieldFeed, type FieldFeedStart } from './field-feed-auto.js';
|
|
9
10
|
|
|
10
11
|
export interface FieldReelResult { ok: boolean; file?: string; seconds: number; error?: string; feed?: FieldFeedStart }
|
|
11
|
-
export type FieldReelRunner = (dir: string, opts: { title: string; sub: string }) => Promise<FieldReelResult>;
|
|
12
|
+
export type FieldReelRunner = (dir: string, opts: { title: string; sub: string; mode?: 'standard' | 'instant' }) => Promise<FieldReelResult>;
|
|
12
13
|
export interface FieldReelStatus {
|
|
13
14
|
state: 'waiting' | 'rendering' | 'done' | 'failed';
|
|
14
15
|
items: number;
|
|
@@ -23,6 +24,7 @@ export interface FieldReelOptions {
|
|
|
23
24
|
quietMs?: number;
|
|
24
25
|
title?: string;
|
|
25
26
|
sub?: string;
|
|
27
|
+
mode?: 'standard' | 'instant';
|
|
26
28
|
runner?: FieldReelRunner;
|
|
27
29
|
onDone?: (result: FieldReelResult) => void | Promise<void>;
|
|
28
30
|
/** 같은 수신자가 앨범 항목마다 예약해도 한 렌더당 한 번만 통지한다. */
|
|
@@ -96,10 +98,12 @@ export function readFieldReelStatus(dir: string): FieldReelStatus | null {
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
export const defaultFieldReelRunner
|
|
101
|
+
export const defaultFieldReelRunner = (dir: string, { title, sub, mode = 'standard' }: { title: string; sub: string; mode?: 'standard' | 'instant' }, spawnReel: typeof spawn = spawn): Promise<FieldReelResult> => new Promise((finish) => {
|
|
100
102
|
const script = resolve(import.meta.dir, '../../skills/explainer-video/engine/reel.sh');
|
|
101
103
|
const started = Date.now();
|
|
102
|
-
const
|
|
104
|
+
const env = { ...process.env };
|
|
105
|
+
mergeCapturedPath(env, { capture: true });
|
|
106
|
+
const child = spawnReel('zsh', [script, dir, '--title', title, '--sub', sub, ...(mode === 'instant' ? ['--instant'] : [])], { stdio: 'ignore', env });
|
|
103
107
|
let settled = false;
|
|
104
108
|
const done = (error?: string) => {
|
|
105
109
|
if (settled) return;
|
|
@@ -147,6 +151,7 @@ async function render(dir: string, job: Job): Promise<void> {
|
|
|
147
151
|
result = await (renderOpts.runner ?? defaultFieldReelRunner)(dir, {
|
|
148
152
|
title: titleLines[0]?.trim() || renderOpts.title || event,
|
|
149
153
|
sub: titleLines[1]?.trim() || renderOpts.sub || sub,
|
|
154
|
+
...(renderOpts.mode ? { mode: renderOpts.mode } : {}),
|
|
150
155
|
});
|
|
151
156
|
} catch (err) {
|
|
152
157
|
result = { ok: false, seconds: 0, error: err instanceof Error ? err.message : String(err) };
|
|
@@ -160,10 +165,15 @@ async function render(dir: string, job: Job): Promise<void> {
|
|
|
160
165
|
debug.log('field.reel', result.ok ? 'done' : 'failed', { event, items, seconds: result.seconds });
|
|
161
166
|
if (result.ok) {
|
|
162
167
|
// EV10b: 엔진이 timeline.json 에 남긴 비전 호출 수·성공·«현장 N» 강등·걸린 ms — codex 잔량과 겹치므로 관측한다(OP 10-01).
|
|
168
|
+
let cardsCache: 'hit' | 'miss' | 'n/a' = 'n/a';
|
|
163
169
|
try {
|
|
164
|
-
const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown };
|
|
170
|
+
const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown; cardsCache?: 'hit' | 'miss' };
|
|
165
171
|
if (t.vision) debug.log('field.reel', 'vision', { event, ...t.vision, music: t.music === true });
|
|
172
|
+
if (renderOpts.mode === 'instant' && (t.cardsCache === 'hit' || t.cardsCache === 'miss')) cardsCache = t.cardsCache;
|
|
166
173
|
} catch { debug.log('field.reel', 'vision-unread', { event }); }
|
|
174
|
+
// A custom runner without an explicit mode may render any way — say «unknown» instead of guessing the default.
|
|
175
|
+
const mode = renderOpts.mode ?? (renderOpts.runner ? 'unknown' : 'standard');
|
|
176
|
+
debug.log('field.reel', 'rendered', { mode, seconds: result.seconds, cardsCache });
|
|
167
177
|
}
|
|
168
178
|
// 뒤이은 업로드로 다시 렌더할 예정이면(dirty) 피드는 그 마지막 렌더 뒤에 한 번만 띄운다.
|
|
169
179
|
if (result.ok && !job.dirty) result = { ...result, feed: (renderOpts.startFeed ?? ((d: string) => startFieldFeed(d, { enabled: renderOpts.autoFeed })))(dir) };
|
package/src/git-fs/worktree.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// the pattern; we trim the hook-based fallback (sandboxed envs)
|
|
10
10
|
// because elanous's primary surface runs native git.
|
|
11
11
|
|
|
12
|
-
import { spawnSync, execFileSync } from 'node:child_process';
|
|
12
|
+
import { spawnSync, execFileSync, type SpawnSyncReturns } from 'node:child_process';
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync, rmSync, symlinkSync, lstatSync } from 'node:fs';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
@@ -17,8 +17,33 @@ import { findGitDir } from './locate.js';
|
|
|
17
17
|
import { isTransientGitError } from './retry.js';
|
|
18
18
|
import { runGitCommand } from './runner.js';
|
|
19
19
|
import { debug } from '../debug/log.js';
|
|
20
|
+
import { getUserConfig } from '../user-config.js';
|
|
20
21
|
|
|
21
22
|
const GIT_TIMEOUT_MS = 30_000;
|
|
23
|
+
const WORKTREE_ADD_TIMEOUT_MS = 5 * 60_000;
|
|
24
|
+
|
|
25
|
+
export interface WorktreeAddDeps {
|
|
26
|
+
spawn?: (command: string, args: string[], options: { cwd: string; encoding: 'utf8'; timeout: number; maxBuffer: number }) => SpawnSyncReturns<string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function worktreeAddTimeoutMs(): number {
|
|
30
|
+
const seconds = getUserConfig().harness?.worktreeAddTimeoutSec;
|
|
31
|
+
return typeof seconds === 'number' && Number.isSafeInteger(seconds) && seconds > 0 && seconds <= 2_147_483
|
|
32
|
+
? seconds * 1000 : WORKTREE_ADD_TIMEOUT_MS;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function worktreeAddFailureLines(stderr: string): { lastProgress: string | null; details: string[] } {
|
|
36
|
+
let lastProgress: string | null = null;
|
|
37
|
+
const details: string[] = [];
|
|
38
|
+
for (const line of stderr.split(/[\r\n]+/).map((value) => value.trim()).filter(Boolean)) {
|
|
39
|
+
if (/^(?:Updating files|Checking out files|Receiving objects|Resolving deltas):\s*\d+%/.test(line)) {
|
|
40
|
+
lastProgress = line;
|
|
41
|
+
} else if (!/^Preparing worktree\b/.test(line)) {
|
|
42
|
+
details.push(line);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { lastProgress, details: details.slice(-3) };
|
|
46
|
+
}
|
|
22
47
|
|
|
23
48
|
/** Git metadata left by an interrupted operation in one checkout. */
|
|
24
49
|
/** ⚠️ 공개인 이유(무인 리뷰 질의) — `GitResidueObservation.residues` 의 **원소 타입**이라,
|
|
@@ -618,7 +643,7 @@ function worktreeHoldingBranch(repoRoot: string, branch: string): string | null
|
|
|
618
643
|
return null;
|
|
619
644
|
}
|
|
620
645
|
|
|
621
|
-
export function createWorktree(opts: CreateWorktreeOpts): CreateWorktreeResult {
|
|
646
|
+
export function createWorktree(opts: CreateWorktreeOpts, deps: WorktreeAddDeps = {}): CreateWorktreeResult {
|
|
622
647
|
const { repoRoot, branch, worktreeRoot } = opts;
|
|
623
648
|
validateBranchName(branch);
|
|
624
649
|
const branchHolder = worktreeHoldingBranch(repoRoot, branch);
|
|
@@ -684,25 +709,39 @@ export function createWorktree(opts: CreateWorktreeOpts): CreateWorktreeResult {
|
|
|
684
709
|
// ★ 병렬 안전 재시도(2026-07-21) — 동시 worktree add 가 git 락에서 경쟁하면 즉시 실패 → 락/일시적
|
|
685
710
|
// 에러면 백오프 재시도(패자가 승자의 락 해제를 기다렸다 성공). non-transient 는 재시도 없이 throw.
|
|
686
711
|
const MAX_ATTEMPTS = 6;
|
|
687
|
-
|
|
712
|
+
const addTimeoutMs = worktreeAddTimeoutMs();
|
|
713
|
+
const addSpawn = deps.spawn ?? spawnSync;
|
|
688
714
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
689
|
-
res =
|
|
715
|
+
const res = addSpawn(
|
|
690
716
|
'git',
|
|
691
717
|
['worktree', 'add', branchFlag, branch, wtPath, baseCheckout],
|
|
692
|
-
{ cwd: repoRoot, encoding: 'utf8', timeout:
|
|
718
|
+
{ cwd: repoRoot, encoding: 'utf8', timeout: addTimeoutMs, maxBuffer: 4 * 1024 * 1024 },
|
|
693
719
|
);
|
|
694
720
|
if (res.status === 0) break;
|
|
695
721
|
const err = (res.stderr || res.stdout || '').toString();
|
|
722
|
+
const { lastProgress, details } = worktreeAddFailureLines(err);
|
|
723
|
+
const timedOut = (res.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
|
|
724
|
+
if (timedOut) {
|
|
725
|
+
debug.log('git-fs.worktree', 'add.failed', { reason: 'timeout', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
|
|
726
|
+
throw new Error(`git worktree add timed out after ${addTimeoutMs / 1000}s (${res.signal ?? 'none'}) — ${[...details, ...(lastProgress ? [`checkout was at «${lastProgress}»`] : [])].join(' | ') || `git exited ${res.status}`}`);
|
|
727
|
+
}
|
|
728
|
+
if (res.signal) {
|
|
729
|
+
debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal, lastProgress }, { level: 'warn' });
|
|
730
|
+
throw new Error(`git worktree add failed (${res.signal}) — ${details.join(' | ') || lastProgress || `git exited ${res.status}`}`);
|
|
731
|
+
}
|
|
696
732
|
const blockingPath = blockingWorktreePath(err);
|
|
697
733
|
if (blockingPath) {
|
|
734
|
+
debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
|
|
698
735
|
debug.log('git-fs.worktree', 'add.branch-held', { attempt, branch, blockingPath }, { level: 'warn' });
|
|
699
736
|
throw new Error(`git worktree add failed — branch ${branch} is already used by worktree at ${blockingPath}`);
|
|
700
737
|
}
|
|
701
738
|
if (isUnbornHeadError(err)) {
|
|
739
|
+
debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
|
|
702
740
|
throw new Error('git worktree add failed — this repository has no commits yet; create the first commit with: git commit --allow-empty -m "Initial commit"');
|
|
703
741
|
}
|
|
704
742
|
if (!isTransientGitError(err) || attempt === MAX_ATTEMPTS) {
|
|
705
|
-
|
|
743
|
+
debug.log('git-fs.worktree', 'add.failed', { reason: 'error', ms: addTimeoutMs, signal: res.signal ?? null, lastProgress }, { level: 'warn' });
|
|
744
|
+
throw new Error(`git worktree add failed — ${details.join(' | ') || lastProgress || `git exited ${res.status}`}`);
|
|
706
745
|
}
|
|
707
746
|
debug.log('git-fs.worktree', 'add.retry', { attempt, branch, err: err.trim().slice(0, 120) });
|
|
708
747
|
sleepSyncMs(80 * attempt); // 선형 백오프(80·160·240…ms)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
5
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
|
+
import { decideGraphApproval, type GraphRunState } from './runner.js';
|
|
7
|
+
|
|
8
|
+
export interface GraphApprovalCardDeps {
|
|
9
|
+
root?: string;
|
|
10
|
+
ledger?: Pick<DecisionLedger, 'list' | 'raise'>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Pending = NonNullable<GraphRunState['pending']>;
|
|
14
|
+
const REF = /^graph-approval:([A-Za-z0-9][A-Za-z0-9._-]*):([A-Za-z0-9][A-Za-z0-9._-]*):([A-Za-z0-9][A-Za-z0-9._-]*):([1-9]\d*)$/;
|
|
15
|
+
|
|
16
|
+
function ledgerFor(deps: GraphApprovalCardDeps, root: string): Pick<DecisionLedger, 'list' | 'raise'> {
|
|
17
|
+
return deps.ledger ?? new DecisionLedger({ stateDir: root });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function observation(event: 'raised' | 'dedup' | 'applied' | 'skipped', state: Pick<GraphRunState, 'graphId' | 'runId'> & { nodeId: string }, decisionId?: string, outcome?: string, reason?: string): void {
|
|
21
|
+
debug.log('graph.approval-card', event, { graphId: state.graphId, runId: state.runId, nodeId: state.nodeId, decisionId, outcome, ...(reason ? { reason } : {}) });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function raiseApprovalCard(state: GraphRunState, pending: Pending, deps: GraphApprovalCardDeps = {}): DecisionEntry | undefined {
|
|
25
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
26
|
+
const nodeId = pending.nodeId;
|
|
27
|
+
const identity = { graphId: state.graphId, runId: state.runId, nodeId };
|
|
28
|
+
const visit = state.path.filter(node => node === nodeId).length;
|
|
29
|
+
if (state.dryRun || state.status !== 'awaiting-approval' || state.pending?.nodeId !== nodeId ||
|
|
30
|
+
state.pending.decision || state.path.at(-1) !== nodeId || visit < 1 ||
|
|
31
|
+
existsSync(`${join(root, 'graph-runs', state.graphId, `${state.runId}.json`)}.${state.path.length}.decision.json`)) {
|
|
32
|
+
observation('skipped', identity);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const ref = `graph-approval:${state.graphId}:${state.runId}:${nodeId}:${visit}`;
|
|
36
|
+
const ledger = ledgerFor(deps, root);
|
|
37
|
+
const existing = ledger.list({ status: 'all' }).find(entry => entry.refs?.includes(ref));
|
|
38
|
+
if (existing) {
|
|
39
|
+
observation('dedup', identity, existing.id, existing.choice);
|
|
40
|
+
return existing;
|
|
41
|
+
}
|
|
42
|
+
const publish = state.graphId === 'release-loop' && nodeId === 'approve-publish';
|
|
43
|
+
const firstLine = pending.message.split(/\r?\n/, 1)[0]?.trim();
|
|
44
|
+
const version = state.input && typeof state.input === 'object' && 'version' in state.input && typeof state.input.version === 'string'
|
|
45
|
+
? state.input.version : state.runId;
|
|
46
|
+
const title = publish && firstLine ? firstLine : `${state.graphId} ${version} — ${nodeId} 승인`;
|
|
47
|
+
const entry = ledger.raise({ title, category: publish ? 'publish' : 'scope',
|
|
48
|
+
scqa: { s: `${state.graphId} / ${state.runId} 런이 ${nodeId} 승인에서 대기 중이다.`, c: pending.message.slice(0, 220) || '승인 대기 중이다.' },
|
|
49
|
+
options: [{ key: 'a', label: '승인', consequence: `${nodeId} 승인 후 런을 계속할 수 있다` },
|
|
50
|
+
{ key: 'b', label: '거절', consequence: `${nodeId} 거절로 기록한다` }],
|
|
51
|
+
recommendation: { skipped: true, reason: '사람 판단' }, raisedBy: { agent: 'graph-runner' }, refs: [ref] });
|
|
52
|
+
observation('raised', identity, entry.id);
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A card answer only claims the same pending visit; runner owns the atomic decision file. */
|
|
57
|
+
export function applyDecidedApprovals(deps: GraphApprovalCardDeps = {}): number {
|
|
58
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
59
|
+
let applied = 0;
|
|
60
|
+
for (const entry of ledgerFor(deps, root).list({ status: 'decided' })) {
|
|
61
|
+
for (const ref of entry.refs ?? []) {
|
|
62
|
+
const match = REF.exec(ref);
|
|
63
|
+
if (!match) continue;
|
|
64
|
+
const [, graphId, runId, nodeId, rawVisit] = match;
|
|
65
|
+
const identity = { graphId: graphId!, runId: runId!, nodeId: nodeId! };
|
|
66
|
+
// One unreadable run must not block every later card (L11 review round 1 should-fix).
|
|
67
|
+
try { if (applyOne(entry, identity, Number(rawVisit), root)) applied++; }
|
|
68
|
+
catch (error) {
|
|
69
|
+
observation('skipped', identity, entry.id, entry.choice, error instanceof Error ? error.message : String(error));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return applied;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function applyOne(entry: DecisionEntry, identity: { graphId: string; runId: string; nodeId: string }, visit: number, root: string): boolean {
|
|
77
|
+
const { graphId, runId, nodeId } = identity;
|
|
78
|
+
// Graph approvals gate irreversible steps (publish) — only a person's answer approves or rejects, never a delegated auto decision.
|
|
79
|
+
if ((entry.choice !== 'a' && entry.choice !== 'b') || entry.decidedBy?.kind !== 'human' || !Number.isSafeInteger(visit)) {
|
|
80
|
+
observation('skipped', identity, entry.id, entry.choice);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const statePath = join(root, 'graph-runs', graphId, `${runId}.json`);
|
|
84
|
+
let state: GraphRunState;
|
|
85
|
+
try { state = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState; }
|
|
86
|
+
catch (error) {
|
|
87
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
88
|
+
observation('skipped', identity, entry.id, entry.choice);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (state.graphId !== graphId || state.runId !== runId || state.dryRun || state.status !== 'awaiting-approval' ||
|
|
92
|
+
!state.pending || state.pending.nodeId !== nodeId || state.pending.decision || state.path.at(-1) !== nodeId ||
|
|
93
|
+
state.path.filter(node => node === nodeId).length !== visit ||
|
|
94
|
+
existsSync(`${statePath}.${state.path.length}.decision.json`)) {
|
|
95
|
+
observation('skipped', identity, entry.id, entry.choice);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const outcome = entry.choice === 'a' ? 'approved' : 'rejected';
|
|
99
|
+
try { decideGraphApproval(graphId, runId, outcome, `owner:${entry.decidedBy.kind}`, root,
|
|
100
|
+
{ nodeId, visit: visit }); }
|
|
101
|
+
catch (error) {
|
|
102
|
+
// A CLI approver (or another notifier) may win the atomic hard-link claim.
|
|
103
|
+
// A resumed run may also have left this visit before the writer reads it.
|
|
104
|
+
const current = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState;
|
|
105
|
+
if (!existsSync(`${statePath}.${state.path.length}.decision.json`) &&
|
|
106
|
+
current.status === 'awaiting-approval' && current.pending?.nodeId === nodeId && !current.pending.decision &&
|
|
107
|
+
current.path.at(-1) === nodeId && current.path.filter(node => node === nodeId).length === visit) throw error;
|
|
108
|
+
observation('skipped', identity, entry.id, outcome);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
observation('applied', identity, entry.id, outcome);
|
|
112
|
+
return true;
|
|
113
|
+
}
|