elanous 0.2.8 → 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-d4b6df905f185055.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-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- 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-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- 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-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.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-7464c1987dd76e6d.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-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- 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-26102fd7350f171e.js +1 -0
- 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-139ba5c367bbf60a.js +1 -0
- 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 +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- 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 +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- 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 +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- 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 +14 -7
- 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 +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- 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 +31 -1
- package/scripts/test-diet/node.ts +134 -17
- 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.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- 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/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- 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/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- 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/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- 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 +172 -41
- 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 +100 -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 +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- 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 +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- 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/skills/tools/usage-slash.ts +8 -7
- 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-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- 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/4836-3084c58c1725a40b.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/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.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-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -8,7 +8,7 @@ export interface DashboardProviderRotationEntryView {
|
|
|
8
8
|
export interface DashboardProviderAvailabilityView {
|
|
9
9
|
available: boolean;
|
|
10
10
|
name: string;
|
|
11
|
-
model: string;
|
|
11
|
+
model: string | undefined;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface DashboardProviderSlashRuntimeDeps {
|
|
@@ -31,6 +31,7 @@ export interface DashboardProviderSlashRuntime {
|
|
|
31
31
|
overviewLines(
|
|
32
32
|
rotationEntries: readonly DashboardProviderRotationEntryView[],
|
|
33
33
|
providers: readonly DashboardProviderAvailabilityView[],
|
|
34
|
+
catalogModelIds?: ReadonlySet<string>,
|
|
34
35
|
): string[];
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -48,25 +49,41 @@ export function createDashboardProviderSlashRuntime(
|
|
|
48
49
|
switchedLine: (label, provider, model) => deps.success(` ✓ switched → ${label} (${providerDetail(provider, model)})`),
|
|
49
50
|
resetEmptyLine: () => deps.warning(' rotation empty — nothing to reset'),
|
|
50
51
|
resetLine: (label) => deps.success(` ✓ reset → ${label}`),
|
|
51
|
-
overviewLines: (rotationEntries, providers) => {
|
|
52
|
+
overviewLines: (rotationEntries, providers, catalogModelIds) => {
|
|
52
53
|
const lines = ['', deps.accent('\u276f /provider')];
|
|
53
54
|
if (rotationEntries.length > 0) {
|
|
54
|
-
lines.push(deps.muted('
|
|
55
|
+
lines.push(deps.muted(' 회전(바꾸기: /provider next · /provider use <이름>)'));
|
|
55
56
|
for (const entry of rotationEntries) {
|
|
56
57
|
const marker = entry.current ? deps.success('▸') : ' ';
|
|
57
|
-
|
|
58
|
+
const missing = catalogModelIds && entry.model !== '(provider default)' && !catalogModelIds.has(entry.model)
|
|
59
|
+
? deps.muted(' · 목록에 없는 모델 — /provider use 로 바꾸기')
|
|
60
|
+
: '';
|
|
61
|
+
lines.push(` ${marker} ${entry.label.padEnd(14)} ${deps.subtext(entry.provider)} ${deps.muted(entry.model)}${missing}`);
|
|
58
62
|
}
|
|
59
63
|
lines.push('');
|
|
60
64
|
}
|
|
61
|
-
lines.push(deps.muted('
|
|
65
|
+
lines.push(deps.muted(' 쓸 수 있는 공급자'));
|
|
66
|
+
// PROVIDERS has anthropic, not claude; collapse the legacy claude display alias.
|
|
67
|
+
const canonicalName = (name: string): string => {
|
|
68
|
+
const lower = name.toLowerCase();
|
|
69
|
+
return lower === 'claude' ? 'anthropic' : lower;
|
|
70
|
+
};
|
|
71
|
+
const unique = new Map<string, DashboardProviderAvailabilityView>();
|
|
62
72
|
for (const provider of providers) {
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
const key = canonicalName(provider.name);
|
|
74
|
+
const previous = unique.get(key);
|
|
75
|
+
if (!previous || (!previous.available && provider.available) || (key === provider.name && previous.name !== key && previous.available === provider.available)) {
|
|
76
|
+
unique.set(key, provider);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
let unavailable = 0;
|
|
80
|
+
for (const provider of unique.values()) {
|
|
81
|
+
if (!provider.available) { unavailable++; continue; }
|
|
82
|
+
lines.push(` ${deps.success('\u2713')} ${deps.text(provider.name).padEnd(20)} ${deps.subtext(provider.model ?? '')}`);
|
|
69
83
|
}
|
|
84
|
+
if (unavailable > 0) lines.push(deps.muted(` 그 밖 ${unavailable}개는 키·로그인 필요 — elanous setup llm`));
|
|
85
|
+
// Keep the old English search terms as a subordinate hint, not section headings.
|
|
86
|
+
lines.push(deps.muted(' · Rotation (cycle with /provider next) · Available providers'));
|
|
70
87
|
return lines;
|
|
71
88
|
},
|
|
72
89
|
};
|
|
@@ -301,6 +301,7 @@ import {
|
|
|
301
301
|
import type { LLMProviderName } from '../../user-config.js';
|
|
302
302
|
import { lookupLlmTierSpec } from '../../model-tier/index.js';
|
|
303
303
|
import { listProviders } from '../../llm.js';
|
|
304
|
+
import { enabledModels, loadCatalog } from '../../intelligence-map/model-catalog.js';
|
|
304
305
|
import { selfOrchestrateRuntime } from '../../self-dev/self-orchestrate-runtime.js';
|
|
305
306
|
import type { ToolRuntime } from '../../tool-runtime/types.js';
|
|
306
307
|
import { getSessionCwd } from '../../session/working-dir.js';
|
|
@@ -742,6 +743,7 @@ export interface DashboardSlashContext {
|
|
|
742
743
|
overviewLines(
|
|
743
744
|
rotation: readonly { label: string; provider: string; model: string; current: boolean }[],
|
|
744
745
|
providers: readonly { available: boolean; name: string; model: string | undefined }[],
|
|
746
|
+
catalogModelIds?: ReadonlySet<string>,
|
|
745
747
|
): readonly string[];
|
|
746
748
|
};
|
|
747
749
|
/** 2026-05-05 — open the visual model picker programmatically.
|
|
@@ -3993,6 +3995,13 @@ export function buildDashboardSlashRegistry(): SlashCommandRegistry<DashboardSla
|
|
|
3993
3995
|
}
|
|
3994
3996
|
|
|
3995
3997
|
if (seSub === 'new' || seSub === 'clear') {
|
|
3998
|
+
if (seSub === 'new') {
|
|
3999
|
+
try {
|
|
4000
|
+
const { suggestProjectForFolder } = await import('../../project/project-store.js');
|
|
4001
|
+
const project = suggestProjectForFolder(process.cwd());
|
|
4002
|
+
if (project) ctx.chatLines.push(ctx.muted(` Project suggestion: ${project.name} (${project.id})`));
|
|
4003
|
+
} catch { /* suggestions must not interrupt a new conversation */ }
|
|
4004
|
+
}
|
|
3996
4005
|
// Clear the in-memory history and detach if attached. Doesn't
|
|
3997
4006
|
// delete any session files — those stay on disk for
|
|
3998
4007
|
// /session list to enumerate.
|
|
@@ -5281,6 +5290,13 @@ export function buildDashboardSlashRegistry(): SlashCommandRegistry<DashboardSla
|
|
|
5281
5290
|
const cfgNow = getUserConfig();
|
|
5282
5291
|
const rot = cfgNow.llm.rotation;
|
|
5283
5292
|
const curIdx = rot && rot.length > 0 ? currentRotationIndex(cfgNow) : -1;
|
|
5293
|
+
let catalogModelIds: Set<string> | undefined;
|
|
5294
|
+
try {
|
|
5295
|
+
const loaded = loadCatalog();
|
|
5296
|
+
if (loaded.source !== 'fallback') {
|
|
5297
|
+
catalogModelIds = new Set(enabledModels(loaded.catalog).map((model) => model.id));
|
|
5298
|
+
}
|
|
5299
|
+
} catch { /* catalog unreadable: no stale-model verdict */ }
|
|
5284
5300
|
ctx.chatLines.push(...ctx.provider.slashRuntime.overviewLines(
|
|
5285
5301
|
(rot ?? []).map((entry, index) => ({
|
|
5286
5302
|
label: rotationEntryLabel(entry),
|
|
@@ -5293,6 +5309,7 @@ export function buildDashboardSlashRegistry(): SlashCommandRegistry<DashboardSla
|
|
|
5293
5309
|
name: provider.name,
|
|
5294
5310
|
model: provider.model,
|
|
5295
5311
|
})),
|
|
5312
|
+
catalogModelIds,
|
|
5296
5313
|
));
|
|
5297
5314
|
const currentRoute = currentRouteDecision('dashboard');
|
|
5298
5315
|
if (currentRoute) ctx.chatLines.push(ctx.muted(` current route: ${formatRouteDecisionSummary(currentRoute)}`));
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { SlashCommand } from '../../chat/index.js';
|
|
2
|
+
import { readTuiSlashAudience, slashMaturity, slashVisibleFor } from '../../maturity/tui-slash-maturity.js';
|
|
3
|
+
import type { Role } from '../../maturity/feature-maturity.js';
|
|
2
4
|
import { visibleWidth } from '../../tui.js';
|
|
3
5
|
|
|
4
6
|
const ESSENTIAL_KEYS = [
|
|
@@ -34,46 +36,59 @@ export function buildEssentialHelpLines({
|
|
|
34
36
|
names,
|
|
35
37
|
descriptions,
|
|
36
38
|
width,
|
|
39
|
+
audience = readTuiSlashAudience(),
|
|
37
40
|
}: {
|
|
38
41
|
names: readonly string[];
|
|
39
42
|
descriptions: readonly Pick<SlashCommand, 'name' | 'aliases' | 'description'>[];
|
|
40
43
|
width: number;
|
|
44
|
+
audience?: { role: Role; showBeta: boolean };
|
|
41
45
|
}): string[] {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
const
|
|
46
|
+
const registered = new Set(names);
|
|
47
|
+
const entries = descriptions
|
|
48
|
+
.filter((command) => registered.has(command.name) &&
|
|
49
|
+
((audience.role === 'owner' && slashMaturity(command.name) === undefined) || slashVisibleFor(command.name, audience.role, { showBeta: audience.showBeta })))
|
|
50
|
+
.map((command) => {
|
|
51
|
+
const aliases = [...new Set(command.aliases ?? [])].filter((alias) => registered.has(alias) && alias !== command.name);
|
|
52
|
+
const label = `/${command.name}${aliases.length ? ` (${aliases.join(', ')})` : ''}`;
|
|
53
|
+
const description = command.description.replace(/\s+/g, ' ').trim()
|
|
54
|
+
.replace(/^(?:(?:H\d+\s+P\d+|CV-\d+|Sprint\s+\d+|Showroom\s+v2)(?:\s*[·—:]\s*|\s+))+/i, '')
|
|
55
|
+
.trim();
|
|
56
|
+
return { label, name: command.name, description };
|
|
57
|
+
})
|
|
58
|
+
.filter(({ label, description }) => description && !/[│╭╮╰╯┌┐└┘─]/.test(label))
|
|
59
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
60
|
+
// Reserve the longest label, 28 description characters, and an ellipsis
|
|
61
|
+
// in each cell. Never trade a missing label for a shorter modal.
|
|
62
|
+
const widestLabel = Math.max(0, ...entries.map(({ label }) => visibleWidth(label)));
|
|
63
|
+
const readableCellWidth = widestLabel + 2 + 28 + 1;
|
|
64
|
+
const columns = entries.length >= 40 && width >= 100
|
|
65
|
+
? Math.max(1, Math.floor(width / (readableCellWidth + 1)))
|
|
66
|
+
: 1;
|
|
60
67
|
const columnWidth = Math.floor(width / columns);
|
|
61
68
|
const commandLines: string[] = [];
|
|
62
69
|
for (let start = 0; start < entries.length; start += columns) {
|
|
63
|
-
const cells = entries.slice(start, start + columns).map(({
|
|
70
|
+
const cells = entries.slice(start, start + columns).map(({ label, description }) => {
|
|
64
71
|
const cellWidth = columnWidth - (columns > 1 ? 1 : 0);
|
|
65
|
-
|
|
66
|
-
return fitLine(`/${name} ${description}`, cellWidth);
|
|
72
|
+
return visibleWidth(`${label} `) > cellWidth ? '' : fitLine(`${label} ${description}`, cellWidth);
|
|
67
73
|
});
|
|
68
74
|
commandLines.push(cells.map((cell, index) =>
|
|
69
75
|
index === cells.length - 1 ? cell : cell + ' '.repeat(columnWidth - visibleWidth(cell)),
|
|
70
76
|
).join('').trimEnd());
|
|
71
77
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
''
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
const keyItems = ESSENTIAL_KEYS.map(([key, description]) => `${key} ${description}`);
|
|
79
|
+
const keyLines: string[] = [];
|
|
80
|
+
if (entries.length >= 40 && width >= 100) {
|
|
81
|
+
let keyRow = '';
|
|
82
|
+
for (const item of keyItems) {
|
|
83
|
+
if (keyRow && visibleWidth(keyRow) + 2 + visibleWidth(item) > width) {
|
|
84
|
+
keyLines.push(keyRow);
|
|
85
|
+
keyRow = '';
|
|
86
|
+
}
|
|
87
|
+
keyRow += (keyRow ? ' ' : '') + fitLine(item, width);
|
|
88
|
+
}
|
|
89
|
+
if (keyRow) keyLines.push(keyRow);
|
|
90
|
+
} else {
|
|
91
|
+
keyLines.push(...keyItems.map((item) => fitLine(item, width)));
|
|
92
|
+
}
|
|
93
|
+
return [fitLine('Commands', width), ...commandLines, '', fitLine('Keys', width), ...keyLines];
|
|
79
94
|
}
|
|
@@ -236,7 +236,7 @@ export function createTurnStreamFormatter(
|
|
|
236
236
|
},
|
|
237
237
|
onToolCall(call: TurnStreamCall): void {
|
|
238
238
|
toolCallCount += 1;
|
|
239
|
-
deps.thinking.update(`Streaming ${call.name} (${toolCallCount} tools)`);
|
|
239
|
+
deps.thinking.update(`Streaming ${call.name} (${toolCallCount} ${toolCallCount === 1 ? 'tool' : 'tools'})`);
|
|
240
240
|
const ptyLine = deps.ptyCallLine(call.name, call.args);
|
|
241
241
|
if (ptyLine) {
|
|
242
242
|
nonGenericCallIds.add(call.id);
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { debug } from '../debug/log.js';
|
|
9
|
+
import { readPendingQuestions } from '../ask-user-question/pending-questions.js';
|
|
9
10
|
import { DecisionLedger, type DecisionEntry } from './decision-ledger.js';
|
|
10
11
|
|
|
11
|
-
export type CardPlatform = 'telegram' | 'discord';
|
|
12
|
+
export type CardPlatform = 'telegram' | 'discord' | 'webpush';
|
|
12
13
|
export interface CardButton { label: string; data: string }
|
|
13
14
|
export interface CardView { text: string; buttons: CardButton[][] }
|
|
14
15
|
export interface CardRef { chat: string; message: string }
|
|
@@ -99,6 +100,7 @@ export interface DecisionCardServiceOptions {
|
|
|
99
100
|
/** Owner user ids for this platform — taps from anyone else are refused and observed. */
|
|
100
101
|
ownerIds: readonly string[];
|
|
101
102
|
ledger?: DecisionLedger;
|
|
103
|
+
pendingQuestions?: typeof readPendingQuestions;
|
|
102
104
|
/** Where this platform keeps which card it sent (one file per platform — the two bots may run in different processes). */
|
|
103
105
|
statePath?: string;
|
|
104
106
|
now?: () => Date;
|
|
@@ -149,6 +151,52 @@ export class DecisionCardService {
|
|
|
149
151
|
}
|
|
150
152
|
let dirty = false;
|
|
151
153
|
if (!state.since) { state.since = this.now().toISOString(); dirty = true; debug.log('decisions.telegram', 'baseline', { platform, since: state.since }); }
|
|
154
|
+
// Same state root as the ledger — the H2 answer writer delivers under the ledger's stateDir, so the questions are read there too.
|
|
155
|
+
const pending = this.opts.pendingQuestions ? this.opts.pendingQuestions() : readPendingQuestions({ root: () => join(this.ledger.path, '..', '..') });
|
|
156
|
+
if (pending.ok) {
|
|
157
|
+
const seen = new Set(all.flatMap(entry => entry.resume ? [entry.resume.questionId] : []));
|
|
158
|
+
for (const record of pending.questions) {
|
|
159
|
+
for (const question of record.questions) {
|
|
160
|
+
const data = { questionId: record.id, runId: record.runId };
|
|
161
|
+
if (record.expiresAt && record.expiresAt <= this.now().toISOString()) {
|
|
162
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'expired' });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (question.impact !== 'high' && question.impact !== 'critical') {
|
|
166
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'impact' });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (record.questions.length !== 1 || question.options.length < 2 || question.options.length > 4) {
|
|
170
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'options' });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (seen.has(record.id)) {
|
|
174
|
+
debug.log('hitl.card-bridge', 'dedup', { ...data, decisionId: all.find(entry => entry.resume?.questionId === record.id)?.id, reason: 'already-raised' });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const keys = ['a', 'b', 'c', 'd'];
|
|
179
|
+
const recommended = question.recommendedIndex;
|
|
180
|
+
const entry = this.ledger.raise({
|
|
181
|
+
title: question.question.split(/\r?\n/, 1)[0]!.slice(0, 120),
|
|
182
|
+
category: question.impact === 'critical' ? 'irreversible' : 'scope',
|
|
183
|
+
scqa: { s: question.question, c: question.question },
|
|
184
|
+
options: question.options.map((option, index) => ({ key: keys[index]!, label: option.label, consequence: option.description })),
|
|
185
|
+
recommendation: recommended !== undefined && Number.isInteger(recommended) && recommended >= 0 && recommended < question.options.length
|
|
186
|
+
? { option: keys[recommended]!, why: '런이 추천' } : { skipped: true, reason: '추천 없음' },
|
|
187
|
+
raisedBy: { agent: 'harness' },
|
|
188
|
+
resume: { questionId: record.id, ...(record.runId === undefined ? {} : { runId: record.runId }) },
|
|
189
|
+
...(record.expiresAt ? { dueAt: record.expiresAt } : {}),
|
|
190
|
+
});
|
|
191
|
+
seen.add(record.id);
|
|
192
|
+
all.unshift(entry);
|
|
193
|
+
debug.log('hitl.card-bridge', 'raised', { ...data, decisionId: entry.id });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: error instanceof Error ? error.name : 'unknown' });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else debug.log('hitl.card-bridge', 'skipped', { reason: 'pending-unreadable' });
|
|
152
200
|
const chats = await this.opts.transport.ownerChats().catch(() => [] as string[]);
|
|
153
201
|
for (const e of all) {
|
|
154
202
|
const card = state.cards[e.id];
|
|
@@ -277,7 +325,7 @@ export function ghPrReplier(target: string, run: (args: string[], stdin: string)
|
|
|
277
325
|
|
|
278
326
|
/** Post as the automation App (not the machine's personal gh login — that would make 대표 a participant and mail them),
|
|
279
327
|
* with proxy variables removed (they break gh on the ops host). TC review #22660. */
|
|
280
|
-
async function runGh(args: string[], stdin: string): Promise<number> {
|
|
328
|
+
export async function runGh(args: string[], stdin: string): Promise<number> {
|
|
281
329
|
const env: Record<string, string> = {};
|
|
282
330
|
for (const [k, v] of Object.entries(process.env)) if (v !== undefined && !/^(https?|all|no)_proxy$/i.test(k)) env[k] = v;
|
|
283
331
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { closeSync, existsSync, fstatSync, ftruncateSync, mkdirSync, openSync, readFileSync, realpathSync, statSync, unlinkSync, writeSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
4
|
+
import { readPendingQuestionAnswer, readPendingQuestions, writePendingQuestionAnswer } from '../ask-user-question/pending-questions.js';
|
|
4
5
|
import { debug, redactSecretText } from '../debug/log.js';
|
|
5
6
|
import { createVersionResolver, type VersionOptions, type Versions } from '../directives/version-at.js';
|
|
6
7
|
import { getUserConfig } from '../user-config.js';
|
|
@@ -15,15 +16,27 @@ export interface DecisionEntry {
|
|
|
15
16
|
options: DecisionOption[]; recommendation: Recommendation; raisedAt?: string; importedAt?: string;
|
|
16
17
|
raisedBy: { agent: string; track?: DecisionTrack; session?: string }; version?: Versions;
|
|
17
18
|
status: 'open' | 'decided' | 'withdrawn'; refs?: string[];
|
|
19
|
+
resume?: { questionId: string; runId?: string };
|
|
18
20
|
/** DEC-TG — optional deadline (UTC). Cards remind the owner two hours before it. */
|
|
19
21
|
dueAt?: string;
|
|
20
22
|
decidedAt?: string; decidedBy?: DecisionActor; choice?: string; note?: string; versionAtDecision?: Versions;
|
|
21
23
|
withdrawnAt?: string; withdrawReason?: string;
|
|
22
24
|
history: Array<{ type: 'raised' | 'options-added' | 'decided' | 'withdrawn'; at?: string; by: string; version?: Versions; choice?: string; reason?: string }>;
|
|
23
25
|
}
|
|
24
|
-
export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt'> & { raisedAt?: string };
|
|
26
|
+
export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume'> & { raisedAt?: string };
|
|
25
27
|
type Event = { type: 'raised'; entry: DecisionEntry } | { type: 'options-added'; id: string; at: string; options: DecisionOption[]; by: string } | { type: 'decided'; id: string; at?: string; by: DecisionActor; choice?: string; version?: Versions; note?: string } | { type: 'withdrawn'; id: string; at: string; reason: string; version: Versions };
|
|
26
|
-
export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions }
|
|
28
|
+
export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions; writeAnswer?: typeof writePendingQuestionAnswer }
|
|
29
|
+
|
|
30
|
+
/** null = the decision resumes no run; otherwise whether the answer file was written (or already matched). */
|
|
31
|
+
export type AnswerDelivery = null | { ok: true; questionId: string } | { ok: false; questionId: string; reason: string };
|
|
32
|
+
|
|
33
|
+
/** The decision is durable even when delivery fails; retryAnswer(id) only retries delivery. */
|
|
34
|
+
export class DecisionAnswerDeliveryError extends Error {
|
|
35
|
+
constructor(readonly decisionId: string, readonly questionId: string) {
|
|
36
|
+
super(`decision ${decisionId} recorded but answer delivery failed for ${questionId}; retry with decisions retry-answer ${decisionId}`);
|
|
37
|
+
this.name = 'DecisionAnswerDeliveryError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
const CATEGORIES: readonly string[] = ['secret', 'publish', 'money', 'security', 'scope', 'irreversible', 'other'];
|
|
28
41
|
const TRACKS: readonly string[] = ['S', 'T', 'F', 'O'];
|
|
29
42
|
|
|
@@ -73,7 +86,11 @@ function validate(input: RaiseInput, historical = false): RaiseInput {
|
|
|
73
86
|
? { skipped: true as const, reason: safe(single(input.recommendation.reason, 'skip reason')) }
|
|
74
87
|
: { option: input.recommendation.option, why: safe(single(input.recommendation.why, 'recommendation why')) };
|
|
75
88
|
if ('option' in recommendation && !options.some(o => o.key === recommendation.option)) throw new Error('recommended option not found');
|
|
89
|
+
if (input.resume && !/^auq:[0-9a-z]+:[0-9a-z]{5}(?![\s\S])/.test(input.resume.questionId)) throw new Error('invalid resume questionId');
|
|
90
|
+
const resume = input.resume ? { questionId: input.resume.questionId,
|
|
91
|
+
...(input.resume.runId === undefined ? {} : { runId: single(input.resume.runId, 'resume runId') }) } : undefined;
|
|
76
92
|
return { ...input, title, scqa: { s, c, ...(q ? { q } : {}), ...(a ? { a } : {}) }, options, recommendation,
|
|
93
|
+
...(resume ? { resume } : {}),
|
|
77
94
|
raisedBy: { agent: safe(single(input.raisedBy.agent, 'agent')), ...(input.raisedBy.track ? { track: input.raisedBy.track } : {}), ...(input.raisedBy.session ? { session: safe(single(input.raisedBy.session, 'session')) } : {}) },
|
|
78
95
|
...(input.refs ? { refs: input.refs.map(r => safe(single(r, 'ref'))) } : {}),
|
|
79
96
|
...(input.dueAt ? { dueAt: utc(input.dueAt) } : {}) };
|
|
@@ -83,12 +100,16 @@ export class DecisionLedger {
|
|
|
83
100
|
readonly path: string;
|
|
84
101
|
private readonly lockPath: string;
|
|
85
102
|
private readonly now: () => Date;
|
|
103
|
+
private readonly writeAnswer: typeof writePendingQuestionAnswer;
|
|
104
|
+
private readonly stateDir: string;
|
|
86
105
|
private versionResolver?: (at: string) => Versions;
|
|
87
106
|
private readonly versionOptions: DecisionLedgerOptions;
|
|
88
107
|
constructor(options: DecisionLedgerOptions = {}) {
|
|
89
|
-
this.
|
|
108
|
+
this.stateDir = options.stateDir ?? elanousStateRoot();
|
|
109
|
+
this.path = join(this.stateDir, 'decisions', 'decisions.jsonl');
|
|
90
110
|
this.lockPath = `${this.path}.lock`;
|
|
91
111
|
this.now = options.now ?? (() => new Date());
|
|
112
|
+
this.writeAnswer = options.writeAnswer ?? writePendingQuestionAnswer;
|
|
92
113
|
this.versionOptions = options;
|
|
93
114
|
this.versionResolver = options.resolveVersion;
|
|
94
115
|
}
|
|
@@ -196,7 +217,14 @@ export class DecisionLedger {
|
|
|
196
217
|
return entry;
|
|
197
218
|
});
|
|
198
219
|
}
|
|
220
|
+
/** Record the decision and say whether its answer reached the waiting run (null = the decision resumes nothing). */
|
|
221
|
+
decideWithDelivery(id: string, choice: string, by: DecisionActor, note?: string, decidedAt?: string): { entry: DecisionEntry; delivery: AnswerDelivery } {
|
|
222
|
+
return this.decideLocked(id, choice, by, note, decidedAt);
|
|
223
|
+
}
|
|
199
224
|
decide(id: string, choice: string, by: DecisionActor, note?: string, decidedAt?: string): DecisionEntry {
|
|
225
|
+
return this.decideLocked(id, choice, by, note, decidedAt).entry;
|
|
226
|
+
}
|
|
227
|
+
private decideLocked(id: string, choice: string, by: DecisionActor, note?: string, decidedAt?: string): { entry: DecisionEntry; delivery: AnswerDelivery } {
|
|
200
228
|
if (!by || (by.kind !== 'human' && by.kind !== 'auto')) throw new Error('decidedBy is required');
|
|
201
229
|
if (by.kind === 'auto') {
|
|
202
230
|
single(by.delegation, 'delegation'); single(by.agent, 'agent');
|
|
@@ -211,9 +239,52 @@ export class DecisionLedger {
|
|
|
211
239
|
if (old.raisedAt && at < old.raisedAt) throw new Error('decision before raise');
|
|
212
240
|
this.append({ type: 'decided', id, at, by, choice, version: this.version(at), ...(note ? { note: safe(single(note, 'note')) } : {}) });
|
|
213
241
|
debug.log('decisions', 'decided', { id, category: old.category, by: by.kind });
|
|
214
|
-
|
|
242
|
+
const decided = this.show(id);
|
|
243
|
+
return { entry: decided, delivery: this.deliverAnswer(decided) };
|
|
215
244
|
});
|
|
216
245
|
}
|
|
246
|
+
/** Retry delivery of the recorded choice without adding another decided event or changing its actor/note. */
|
|
247
|
+
retryAnswer(id: string): DecisionEntry {
|
|
248
|
+
return this.locked(() => {
|
|
249
|
+
const entry = this.show(id);
|
|
250
|
+
if (entry.status !== 'decided' || !entry.resume || !entry.choice || !entry.decidedBy) throw new Error(`decision has no recorded resume answer: ${id}`);
|
|
251
|
+
this.deliverAnswer(entry, true);
|
|
252
|
+
return entry;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
private deliverAnswer(entry: DecisionEntry, throwOnFailure = false): AnswerDelivery {
|
|
256
|
+
if (!entry.resume) return null;
|
|
257
|
+
const { questionId, runId } = entry.resume;
|
|
258
|
+
try {
|
|
259
|
+
const label = entry.options.find(option => option.key === entry.choice)?.label;
|
|
260
|
+
if (!label || !entry.choice) throw new Error('recorded choice not found');
|
|
261
|
+
const deps = { root: () => this.stateDir };
|
|
262
|
+
const pending = readPendingQuestions(deps);
|
|
263
|
+
if (!pending.ok) throw new Error(pending.error);
|
|
264
|
+
const target = pending.questions.find(item => item.id === questionId);
|
|
265
|
+
if (!target || target.questions.length !== 1 || !target.questions[0]?.id) throw new Error('pending question target not found or ambiguous');
|
|
266
|
+
const question = target.questions[0];
|
|
267
|
+
if (question.options.length !== entry.options.length || question.options.some((option, index) =>
|
|
268
|
+
entry.options[index]?.key !== String.fromCharCode(97 + index) || entry.options[index]?.label !== option.label)) {
|
|
269
|
+
throw new Error('pending question options differ from decision');
|
|
270
|
+
}
|
|
271
|
+
const answerKey = question.id;
|
|
272
|
+
const answer = { id: questionId, result: { answers: { [answerKey]: `${entry.choice}) ${label}` },
|
|
273
|
+
...(entry.note ? { otherText: { [answerKey]: entry.note } } : {}) } };
|
|
274
|
+
const existing = readPendingQuestionAnswer(questionId, deps);
|
|
275
|
+
if (!existing.ok) throw new Error('existing answer unreadable');
|
|
276
|
+
if (existing.answer) {
|
|
277
|
+
if (JSON.stringify(existing.answer) !== JSON.stringify(answer)) throw new Error('pending question already answered differently');
|
|
278
|
+
} else this.writeAnswer(answer, deps);
|
|
279
|
+
debug.log('hitl.card-bridge', 'answered', { decisionId: entry.id, questionId, runId, via: entry.decidedBy?.kind });
|
|
280
|
+
return { ok: true, questionId };
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const reason = error instanceof Error ? (error as NodeJS.ErrnoException).code ?? error.name : typeof error;
|
|
283
|
+
debug.log('hitl.card-bridge', 'answer-write-failed', { decisionId: entry.id, questionId, reason });
|
|
284
|
+
if (throwOnFailure) throw new DecisionAnswerDeliveryError(entry.id, questionId);
|
|
285
|
+
return { ok: false, questionId, reason: error instanceof Error ? error.message.slice(0, 120) : String(reason) };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
217
288
|
/** Import an open historical request with known alternatives and an unknown original time. */
|
|
218
289
|
importOpen(input: RaiseInput): DecisionEntry {
|
|
219
290
|
const clean = validate(input);
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
import { createLinearIssue, commentLinearIssue, findLinearDecisionIssue, moveLinearIssueToStateType, resolveLinearProjectId } from '../connectors/linear.js';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
8
|
+
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
9
|
+
import { DecisionLedger, type DecisionEntry } from './decision-ledger.js';
|
|
10
|
+
|
|
11
|
+
interface ProjectionRecord { issue: string; closedAt?: string; commentPosted?: boolean }
|
|
12
|
+
type ProjectionState = Record<string, ProjectionRecord>;
|
|
13
|
+
export interface DecisionLinearProjectionDeps {
|
|
14
|
+
stateDir?: string;
|
|
15
|
+
ledger?: Pick<DecisionLedger, 'list'>;
|
|
16
|
+
config?: Pick<UserConfig, 'decisions' | 'coo'>;
|
|
17
|
+
getSecret?: (id: string) => Promise<string | undefined>;
|
|
18
|
+
fetch?: typeof fetch;
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface DecisionLinearProjectionResult {
|
|
22
|
+
created: number; closed: number; skipped: number; failed: number;
|
|
23
|
+
reason?: string;
|
|
24
|
+
plan?: Array<{ decisionId: string; action: 'create' | 'close'; issue?: string }>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readState(path: string): ProjectionState {
|
|
28
|
+
if (!existsSync(path)) return {};
|
|
29
|
+
const value: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
30
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.entries(value).some(([id, row]) =>
|
|
31
|
+
!/^D-\d{8}-\d+$/.test(id) || !row || typeof row !== 'object' || Array.isArray(row) ||
|
|
32
|
+
typeof (row as ProjectionRecord).issue !== 'string' || !(row as ProjectionRecord).issue ||
|
|
33
|
+
((row as ProjectionRecord).closedAt !== undefined && typeof (row as ProjectionRecord).closedAt !== 'string') ||
|
|
34
|
+
((row as ProjectionRecord).commentPosted !== undefined && typeof (row as ProjectionRecord).commentPosted !== 'boolean'))) {
|
|
35
|
+
throw new Error('invalid decision Linear projection state');
|
|
36
|
+
}
|
|
37
|
+
return value as ProjectionState;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function saveState(path: string, state: ProjectionState): void {
|
|
41
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
42
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
43
|
+
try {
|
|
44
|
+
writeFileSync(temp, JSON.stringify(state), { mode: 0o600 });
|
|
45
|
+
renameSync(temp, path);
|
|
46
|
+
} finally { if (existsSync(temp)) unlinkSync(temp); }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function description(entry: DecisionEntry): string {
|
|
50
|
+
return [`카테고리: ${entry.category}`, ...(entry.dueAt ? [`기한: ${entry.dueAt}`] : []),
|
|
51
|
+
`결정 id: ${entry.id}`, '텔레그램/디스코드 카드에서 정한다',
|
|
52
|
+
...(entry.refs ?? []).filter(ref => ref.startsWith('http'))].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A single writer holds the file lock across remote creation and local persistence. */
|
|
56
|
+
export async function projectDecisionsToLinear(deps: DecisionLinearProjectionDeps = {}): Promise<DecisionLinearProjectionResult> {
|
|
57
|
+
const result: DecisionLinearProjectionResult = { created: 0, closed: 0, skipped: 0, failed: 0 };
|
|
58
|
+
const config = deps.config ?? getUserConfig();
|
|
59
|
+
if (config.decisions?.linearProjection?.enabled !== true) {
|
|
60
|
+
result.reason = 'decisions.linearProjection.enabled 꺼짐';
|
|
61
|
+
debug.log('decisions.linear', 'skipped', { decisionId: null, issue: null, reason: 'disabled' });
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
// A dry run only reads the local ledger and state — it must work before a key is set.
|
|
65
|
+
const apiKey = (deps.dryRun ? '' : await (deps.getSecret ?? getSecretAsync)('connector.linear.apiKey')) ?? '';
|
|
66
|
+
if (!deps.dryRun && !apiKey) {
|
|
67
|
+
result.reason = 'Linear 키가 없습니다 — `elanous connector linear set-key`';
|
|
68
|
+
debug.log('decisions.linear', 'skipped', { decisionId: null, issue: null, reason: 'missing-key' });
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
const stateDir = deps.stateDir ?? elanousStateRoot();
|
|
72
|
+
const path = join(stateDir, 'decisions', 'linear-projection.json');
|
|
73
|
+
const ledger = deps.ledger ?? new DecisionLedger({ stateDir });
|
|
74
|
+
const entries = ledger.list({ status: 'all' });
|
|
75
|
+
const fetchFn = deps.fetch ?? fetch;
|
|
76
|
+
const lockPath = `${path}.lock`;
|
|
77
|
+
let lock: number | undefined;
|
|
78
|
+
if (!deps.dryRun) {
|
|
79
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
80
|
+
const deadline = Date.now() + 10_000;
|
|
81
|
+
for (;;) {
|
|
82
|
+
try { lock = openSync(lockPath, 'wx', 0o600); writeSync(lock, `${process.pid}`); break; }
|
|
83
|
+
catch (error) {
|
|
84
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
85
|
+
try {
|
|
86
|
+
const pid = Number(readFileSync(lockPath, 'utf8'));
|
|
87
|
+
if (Number.isSafeInteger(pid) && pid > 0 && Date.now() - statSync(lockPath).mtimeMs > 30_000) {
|
|
88
|
+
try { process.kill(pid, 0); }
|
|
89
|
+
catch (e) { if ((e as NodeJS.ErrnoException).code === 'ESRCH') { unlinkSync(lockPath); continue; } }
|
|
90
|
+
}
|
|
91
|
+
} catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') continue; }
|
|
92
|
+
if (Date.now() >= deadline) throw new Error('decision Linear projection lock timeout');
|
|
93
|
+
await Bun.sleep(25);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const state = readState(path);
|
|
99
|
+
if (deps.dryRun) result.plan = [];
|
|
100
|
+
let projectId: string | undefined;
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
let issue = state[entry.id]?.issue;
|
|
103
|
+
try {
|
|
104
|
+
const record = state[entry.id];
|
|
105
|
+
if (entry.status === 'open' && !record) {
|
|
106
|
+
if (deps.dryRun) {
|
|
107
|
+
result.plan!.push({ decisionId: entry.id, action: 'create' });
|
|
108
|
+
result.created++;
|
|
109
|
+
} else {
|
|
110
|
+
projectId ??= await resolveLinearProjectId({ apiKey, project: config.coo?.linearProject ?? '외부 행정·큰 일 (COO)', fetch: fetchFn });
|
|
111
|
+
const existing = await findLinearDecisionIssue({ apiKey, projectId, decisionId: entry.id, fetch: fetchFn });
|
|
112
|
+
issue = existing ?? await createLinearIssue({ apiKey, projectId, title: `[결정] ${entry.title}`, description: description(entry),
|
|
113
|
+
...(entry.dueAt ? { dueDate: entry.dueAt.slice(0, 10) } : {}), fetch: fetchFn });
|
|
114
|
+
state[entry.id] = { issue };
|
|
115
|
+
saveState(path, state);
|
|
116
|
+
if (existing) {
|
|
117
|
+
result.skipped++;
|
|
118
|
+
debug.log('decisions.linear', 'skipped', { decisionId: entry.id, issue, reason: 'recovered' });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
result.created++;
|
|
122
|
+
}
|
|
123
|
+
debug.log('decisions.linear', 'created', { decisionId: entry.id, issue: issue ?? null, reason: deps.dryRun ? 'dry-run' : 'created' });
|
|
124
|
+
} else if (entry.status !== 'open' && record && !record.closedAt) {
|
|
125
|
+
if (deps.dryRun) {
|
|
126
|
+
result.plan!.push({ decisionId: entry.id, action: 'close', issue: record.issue });
|
|
127
|
+
} else {
|
|
128
|
+
if (!record.commentPosted) {
|
|
129
|
+
const body = entry.status === 'withdrawn' ? '철회됨'
|
|
130
|
+
: `결정됨: ${entry.choice ?? '선택 키 미상'} · ${entry.decidedBy?.kind ?? '주체 미상'} · ${entry.decidedAt ?? '시각 미상'}`;
|
|
131
|
+
await commentLinearIssue({ apiKey, issueId: record.issue, body, fetch: fetchFn });
|
|
132
|
+
record.commentPosted = true;
|
|
133
|
+
saveState(path, state);
|
|
134
|
+
}
|
|
135
|
+
await moveLinearIssueToStateType({ apiKey, issueId: record.issue, type: entry.status === 'decided' ? 'completed' : 'canceled', fetch: fetchFn });
|
|
136
|
+
record.closedAt = new Date().toISOString();
|
|
137
|
+
saveState(path, state);
|
|
138
|
+
}
|
|
139
|
+
result.closed++;
|
|
140
|
+
debug.log('decisions.linear', 'closed', { decisionId: entry.id, issue: record.issue, reason: deps.dryRun ? 'dry-run' : entry.status });
|
|
141
|
+
} else {
|
|
142
|
+
result.skipped++;
|
|
143
|
+
debug.log('decisions.linear', 'skipped', { decisionId: entry.id, issue: issue ?? null, reason: record?.closedAt ? 'already-closed' : record ? 'already-created' : 'not-projected' });
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
result.failed++;
|
|
147
|
+
const reason = error instanceof Error
|
|
148
|
+
? (error as NodeJS.ErrnoException).code ?? (/^Linear GraphQL HTTP \d{3}$/.test(error.message) ? error.message : error.name)
|
|
149
|
+
: 'unknown';
|
|
150
|
+
debug.log('decisions.linear', 'failed', { decisionId: entry.id, issue: issue ?? null, reason });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
} finally {
|
|
155
|
+
if (lock !== undefined) { closeSync(lock); unlinkSync(lockPath); }
|
|
156
|
+
}
|
|
157
|
+
}
|