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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import type { MissionOrigin } from '../autopilot/mission-origin.js';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
@@ -15,7 +15,7 @@ import { answerAsSeat, isNoGraphFailure } from './seat-answer.js';
|
|
|
15
15
|
const POLL_MS = 5_000;
|
|
16
16
|
const TIMEOUT_MS = 30 * 60_000;
|
|
17
17
|
const DOC = /전략|한\s*장|원페이저|기획|보도자료|글|메모|초안|\b(?:one-pager|strategy|post|memo|draft)\b/i;
|
|
18
|
-
const CODE = /구현|버그|고쳐|\b(?:fix|implement
|
|
18
|
+
const CODE = /구현|버그|고쳐|\b(?:fix|implement)\b|\bPR\b(?!\s*#?\d+\b)/i;
|
|
19
19
|
const RESEARCH = /전략|시장|경쟁|\bstrategy\b/i;
|
|
20
20
|
/** EV12a — a question or a request for status / a report is answered, never turned into a code goal. */
|
|
21
21
|
const ASK = /상황|현황|진행|준비|어때|어떻게|알려|보고|정리|요약|공유|\?|?|\b(?:status|update|report|summary|summari[sz]e|how|what)\b/i;
|
|
@@ -30,6 +30,8 @@ export interface SeatDocDeps {
|
|
|
30
30
|
submitExec?: (text: string) => Promise<{ id: string }>;
|
|
31
31
|
getExec?: (id: string) => Promise<ExecSnapshot | null>;
|
|
32
32
|
getFile?: (path: string) => Promise<string>;
|
|
33
|
+
/** Resolve only references explicitly supplied in the request; null means their claims cannot be verified. */
|
|
34
|
+
readSource?: (ref: string) => Promise<string | null>;
|
|
33
35
|
publishFile?: (content: string, key: string) => string | null;
|
|
34
36
|
sendOutbound?: typeof sendOutbound;
|
|
35
37
|
sleep?: (ms: number) => Promise<void>;
|
|
@@ -97,6 +99,160 @@ async function execFile(path: string): Promise<string> {
|
|
|
97
99
|
return (await execResponse(path.slice('/v1/exec-requests'.length))).text();
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
const SUPPLIED_REF = /https?:\/\/[^\s<>()[\]]+|\bPR\s*#?\d+\b|(?:\.?\.?\/|[\w.-]+\/)[\w./-]+\.(?:md|txt|csv|json|pdf)\b|\b[\w.-]+\.(?:md|txt|csv|json|pdf)\b/gi;
|
|
103
|
+
// Keep units and grammatical particles with the figure; never backtrack from 42% to 42.
|
|
104
|
+
const NUMBER = /(?<![\p{L}\p{N}_#.,])([+-]?\d+(?:[,.]\d+)*(?:(?:%|년|월|일|명|건|원|배)(?![\p{N}_%A-Za-z]|[.,]\d)|(?![\p{L}\p{N}_%]|[.,]\d)))(은|는|이|가|을|를|에|의|도|만|에서|으로|와|과)?/gu;
|
|
105
|
+
|
|
106
|
+
function suppliedReferences(text: string): string[] {
|
|
107
|
+
return [...new Set([...text.matchAll(SUPPLIED_REF)].map(([ref]) => ref!.replace(/[.,;]+$/, '')))];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const SOURCE_TIMEOUT_MS = 5_000;
|
|
111
|
+
const MAX_SOURCE_BYTES = 200_000;
|
|
112
|
+
// The default resolver only reads repository-local files and these two public HTTPS hosts.
|
|
113
|
+
const SOURCE_URL_HOSTS = new Set(['github.com', 'raw.githubusercontent.com']);
|
|
114
|
+
|
|
115
|
+
function allowedSourceUrl(value: string): boolean {
|
|
116
|
+
try {
|
|
117
|
+
const url = new URL(value);
|
|
118
|
+
return url.protocol === 'https:' && SOURCE_URL_HOSTS.has(url.hostname)
|
|
119
|
+
&& !url.username && !url.password && !url.port;
|
|
120
|
+
} catch { return false; }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function readPublicSource(ref: string): Promise<string | null> {
|
|
124
|
+
let url = ref;
|
|
125
|
+
const signal = AbortSignal.timeout(SOURCE_TIMEOUT_MS);
|
|
126
|
+
for (let redirects = 0; redirects < 3; redirects++) {
|
|
127
|
+
if (!allowedSourceUrl(url)) return null;
|
|
128
|
+
const response = await fetch(url, { signal, redirect: 'manual' });
|
|
129
|
+
if (response.status >= 300 && response.status < 400) {
|
|
130
|
+
const location = response.headers.get('location');
|
|
131
|
+
if (!location) return null;
|
|
132
|
+
url = new URL(location, url).href;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!response.ok || !/^(?:text\/|application\/(?:json|xml))/.test(response.headers.get('content-type') ?? '')) return null;
|
|
136
|
+
if (Number(response.headers.get('content-length') ?? 0) > MAX_SOURCE_BYTES) {
|
|
137
|
+
await response.body?.cancel();
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (!response.body) return null;
|
|
141
|
+
const reader = response.body.getReader();
|
|
142
|
+
const chunks: Uint8Array[] = [];
|
|
143
|
+
let size = 0;
|
|
144
|
+
try {
|
|
145
|
+
while (true) {
|
|
146
|
+
const { done, value } = await reader.read();
|
|
147
|
+
if (done) break;
|
|
148
|
+
size += value.byteLength;
|
|
149
|
+
if (size > MAX_SOURCE_BYTES) return null;
|
|
150
|
+
chunks.push(value);
|
|
151
|
+
}
|
|
152
|
+
const bytes = new Uint8Array(size);
|
|
153
|
+
let offset = 0;
|
|
154
|
+
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
|
|
155
|
+
return new TextDecoder().decode(bytes);
|
|
156
|
+
} finally {
|
|
157
|
+
if (size > MAX_SOURCE_BYTES) await reader.cancel();
|
|
158
|
+
reader.releaseLock();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function defaultReadSource(ref: string): Promise<string | null> {
|
|
165
|
+
try {
|
|
166
|
+
if (/^https?:\/\//i.test(ref)) return await readPublicSource(ref);
|
|
167
|
+
if (/^PR\s*#?\d+$/i.test(ref)) {
|
|
168
|
+
const number = ref.match(/\d+/)![0];
|
|
169
|
+
const child = Bun.spawn(['gh', 'pr', 'view', number, '--json', 'title,body'], { stdout: 'pipe', stderr: 'ignore' });
|
|
170
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
171
|
+
const timeout = new Promise<null>(finish => {
|
|
172
|
+
timer = setTimeout(() => { child.kill(9); finish(null); }, SOURCE_TIMEOUT_MS);
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
const result = await Promise.race([
|
|
176
|
+
Promise.all([new Response(child.stdout).text(), child.exited]).then(([out, code]) => ({ out, code })),
|
|
177
|
+
timeout,
|
|
178
|
+
]);
|
|
179
|
+
if (!result || result.code !== 0 || result.out.length > MAX_SOURCE_BYTES) return null;
|
|
180
|
+
const pr = JSON.parse(result.out) as { title?: string; body?: string };
|
|
181
|
+
return `${pr.title ?? ''}\n${pr.body ?? ''}`;
|
|
182
|
+
} finally { clearTimeout(timer); }
|
|
183
|
+
}
|
|
184
|
+
const root = realpathSync(process.cwd());
|
|
185
|
+
const actual = realpathSync(resolve(root, ref));
|
|
186
|
+
const within = relative(root, actual);
|
|
187
|
+
if (isAbsolute(within) || within === '..' || within.startsWith('../')) return null;
|
|
188
|
+
const file = statSync(actual);
|
|
189
|
+
if (!file.isFile() || file.size > MAX_SOURCE_BYTES || /\.pdf$/i.test(actual)) return null;
|
|
190
|
+
return readFileSync(actual, 'utf8');
|
|
191
|
+
} catch { return null; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function citationFor(ref: string): string {
|
|
195
|
+
return /^PR\s*#?(\d+)$/i.test(ref) ? `PR #${ref.match(/\d+/)![0]}` : ref;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function claimLabel(line: string, offset: number, length: number): string | null {
|
|
199
|
+
const before = line.slice(0, offset).split(/[,;:,;。.!?\n]/).at(-1) ?? '';
|
|
200
|
+
const after = (line.slice(offset + length).split(/[,;:,;。.!?\n]/)[0] ?? '').replace(/^\s*(?:\[출처: [^\]]+\]|«확인 필요»)/, '');
|
|
201
|
+
// Never borrow a label from another figure in the same clause.
|
|
202
|
+
if ([...before.matchAll(NUMBER), ...after.matchAll(NUMBER)].length) return null;
|
|
203
|
+
const left = before.trim().replace(/^(?:[#*>-]+\s*)+/, '');
|
|
204
|
+
const right = after.match(/^\s*([\p{L}]{2,}(?:\s+[\p{L}]{2,})*)/u)?.[1];
|
|
205
|
+
return /[\p{L}]{2,}/u.test(left) ? left : right ?? null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const CLAIM_DIRECTION = /증가|상승|성장|확대|개선|감소|하락|축소|악화/g;
|
|
209
|
+
const CLAIM_FORECAST = /예상|전망|예측|추정|계획|예정|기대|목표|가능성/;
|
|
210
|
+
// Range and approximation words change what a figure claims («42%» ≠ «42% 미만»); both sides must carry the same set.
|
|
211
|
+
const CLAIM_QUALIFIER = /미만|이하|이상|초과|이내|약|최대|최소|가량|정도|내외|안팎|넘게|넘는|남짓|까지|부터|대략|거의|이상의|under|over|about|up to|at least|at most|less than|more than|nearly|almost/giu;
|
|
212
|
+
|
|
213
|
+
function claimDirection(segment: string): string | undefined {
|
|
214
|
+
const directions = [...segment.matchAll(CLAIM_DIRECTION)];
|
|
215
|
+
if (directions.length > 1) return undefined;
|
|
216
|
+
const direction = directions[0];
|
|
217
|
+
const after = direction ? segment.slice(direction.index + direction[0].length) : '';
|
|
218
|
+
const negated = direction && /^\s*(?:하지\s*(?:않|못)|않|못|안\s*(?:했|함)|없|아니)/u.test(after);
|
|
219
|
+
// Polarity outside the recognized predicate is ambiguous; do not use it to establish provenance.
|
|
220
|
+
if (/않|못|안\s|없|아니/u.test(segment) && !negated) return undefined;
|
|
221
|
+
const qualifiers = [...new Set([...segment.matchAll(CLAIM_QUALIFIER)].map(match => match[0].toLowerCase().replace(/^이상의$/, '이상')))].sort().join(',');
|
|
222
|
+
return `${direction?.[0] ?? ''}|${negated ? 'negated' : 'affirmed'}|${CLAIM_FORECAST.test(segment) ? 'forecast' : 'observed'}|${qualifiers}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function hasClaimPair(source: string, number: string, label: string, direction: string | undefined): boolean {
|
|
226
|
+
if (direction === undefined) return false;
|
|
227
|
+
return source.split(/\n|,(?=\s)|[;:,;。!?]|\.(?!\d)/).some(segment => {
|
|
228
|
+
const figures = [...segment.matchAll(NUMBER)];
|
|
229
|
+
if (figures.length !== 1 || figures[0]![1] !== number) return false;
|
|
230
|
+
return claimLabel(segment, figures[0]!.index, figures[0]![0].length) === label
|
|
231
|
+
&& claimDirection(segment) === direction;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function citeDocumentNumbers(content: string, sources: Map<string, string | null>): string {
|
|
236
|
+
let fenced = false;
|
|
237
|
+
return content.split('\n').map(line => {
|
|
238
|
+
if (/^\s*```/.test(line)) { fenced = !fenced; return line; }
|
|
239
|
+
if (fenced || /^\s*(?:참고 자료|출처:)/.test(line)) return line;
|
|
240
|
+
const occupied = [...line.matchAll(/https?:\/\/\S+|\[출처: [^\]]+\]|(?:\.?\.?\/|[\w.-]+\/)[\w./-]+\.(?:md|txt|csv|json|pdf)|\b[\w.-]+\.(?:md|txt|csv|json|pdf)\b/g)]
|
|
241
|
+
.map(match => [match.index, match.index + match[0].length] as const);
|
|
242
|
+
const annotated = new RegExp(`${NUMBER.source}(?:\\s*(?:\\[출처: [^\\]]+\\]|«확인 필요»))?`, 'gu');
|
|
243
|
+
return line.replace(annotated, (claim, number: string, particle: string | undefined, offset: number) => {
|
|
244
|
+
if (occupied.some(([start, end]) => offset >= start && offset < end)) return claim;
|
|
245
|
+
const figure = `${number}${particle ?? ''}`;
|
|
246
|
+
const label = claimLabel(line, offset, claim.length);
|
|
247
|
+
const before = line.slice(0, offset).split(/[,;:,;。.!?]/).at(-1) ?? '';
|
|
248
|
+
const after = line.slice(offset + claim.length).split(/[,;:,;。.!?]/)[0] ?? '';
|
|
249
|
+
const direction = claimDirection(`${before} ${after}`);
|
|
250
|
+
const matches = label ? [...sources].filter(([, source]) => source && hasClaimPair(source, number, label, direction)) : [];
|
|
251
|
+
return matches.length === 1 ? `${figure} [출처: ${citationFor(matches[0]![0])}]` : `${figure} «확인 필요»`;
|
|
252
|
+
});
|
|
253
|
+
}).join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
100
256
|
function chatPwaUrl(): string | undefined {
|
|
101
257
|
const endpoint = resolveDaemonEndpoint({ purpose: 'write' });
|
|
102
258
|
return endpoint?.pwaUrl;
|
|
@@ -115,14 +271,15 @@ function accessiblePwaUrl(value: string | undefined): string | undefined {
|
|
|
115
271
|
} catch { return undefined; }
|
|
116
272
|
}
|
|
117
273
|
|
|
118
|
-
async function fileLinks(id: string, result: ExecSnapshot, deps: SeatDocDeps): Promise<Map<string, string>> {
|
|
274
|
+
async function fileLinks(id: string, result: ExecSnapshot, deps: SeatDocDeps, sources: Map<string, string | null>): Promise<Map<string, string>> {
|
|
119
275
|
const links = new Map<string, string>();
|
|
120
276
|
for (const entry of result.results) {
|
|
121
277
|
if (entry.kind !== 'report' && entry.kind !== 'text') continue;
|
|
122
278
|
if (!entry.url.startsWith(`/v1/exec-requests/${encodeURIComponent(id)}/files/`)) continue;
|
|
123
279
|
try {
|
|
124
280
|
// The authenticated daemon route is the only source of bytes; a raw file path is never trusted.
|
|
125
|
-
const
|
|
281
|
+
const raw = await (deps.getFile ?? execFile)(entry.url);
|
|
282
|
+
const content = sources.size ? citeDocumentNumbers(raw, sources) : raw;
|
|
126
283
|
const ext = entry.kind === 'report' ? 'md' : 'txt';
|
|
127
284
|
const digest = createHash('sha256').update(content).digest('hex');
|
|
128
285
|
const key = `seat-doc/${encodeURIComponent(id)}/${digest}.${ext}`;
|
|
@@ -158,6 +315,13 @@ export async function submitSeatDocRequest({ text, reportTo, deps = {} }: {
|
|
|
158
315
|
const body = parseSeatAddress(text)?.body.trim() ?? text.trim();
|
|
159
316
|
let withoutResearch = false;
|
|
160
317
|
let requestText = text;
|
|
318
|
+
const refs = suppliedReferences(body);
|
|
319
|
+
const sources = new Map<string, string | null>();
|
|
320
|
+
for (const ref of refs) {
|
|
321
|
+
try { sources.set(ref, await (deps.readSource ?? defaultReadSource)(ref)); }
|
|
322
|
+
catch { sources.set(ref, null); }
|
|
323
|
+
}
|
|
324
|
+
if (refs.length) requestText += '\n\n문서 수치 근거 규칙: 제공된 파일·URL·PR 번호의 실제 내용을 확인하고, 근거가 확인된 각 수치 바로 뒤에 [출처: <파일|PR|명령>] 형식으로 실물 참조를 붙이세요. 근거를 확인할 수 없는 수치는 삭제하거나 바꾸지 말고 «확인 필요»로 표시하세요. 서로 다른 출처에 같은 숫자가 있다는 이유만으로 출처를 추정하지 마세요.';
|
|
161
325
|
if (RESEARCH.test(body)) {
|
|
162
326
|
try {
|
|
163
327
|
const sources = (await (deps.research ?? research)(body)).slice(0, 5);
|
|
@@ -196,7 +360,7 @@ export async function submitSeatDocRequest({ text, reportTo, deps = {} }: {
|
|
|
196
360
|
if (now() < deadline) await sleep(Math.min(POLL_MS, Math.max(0, deadline - now())));
|
|
197
361
|
}
|
|
198
362
|
log('settled', { id, status: result?.status ?? (lookupAvailable ? 'timeout' : 'lookup-unavailable'), lastConfirmedStatus });
|
|
199
|
-
const links = result?.status === 'done' ? await fileLinks(id, result, deps) : new Map<string, string>();
|
|
363
|
+
const links = result?.status === 'done' ? await fileLinks(id, result, deps, sources) : new Map<string, string>();
|
|
200
364
|
let reply = message(id, result, withoutResearch, lookupAvailable, accessiblePwaUrl((deps.pwaUrl ?? chatPwaUrl)()), links);
|
|
201
365
|
if (isNoGraphFailure(result)) {
|
|
202
366
|
// EV12d — a question with no fitting graph gets the seat's own answer, not «문서 요청 실패».
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { debug } from '../debug/log.js';
|
|
2
|
+
import { telegramDecisionOwner } from '../decisions/telegram-decision-cards.js';
|
|
3
|
+
import { canonicalSeatId } from '../msg/msg-store.js';
|
|
4
|
+
import { dispatchCeoTask, type CeoCommandDeps } from '../seat-dispatch/ceo-commands.js';
|
|
5
|
+
import { ceoTaskDeps, classifyCeoIntent } from '../seat-dispatch/ceo-intent.js';
|
|
2
6
|
import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
|
|
7
|
+
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
8
|
+
import { answerAsSeat } from './seat-answer.js';
|
|
3
9
|
import {
|
|
4
10
|
submitIntakeWork,
|
|
5
11
|
type SubmitIntakeWorkDeps,
|
|
@@ -8,15 +14,39 @@ import type { TelegramWorkMessage } from './telegram-work.js';
|
|
|
8
14
|
|
|
9
15
|
export interface TelegramSeatWorkDeps extends SubmitIntakeWorkDeps {
|
|
10
16
|
submit?: typeof submitIntakeWork;
|
|
17
|
+
config?: UserConfig;
|
|
18
|
+
answer?: typeof answerAsSeat;
|
|
19
|
+
dispatch?: typeof dispatchCeoTask;
|
|
20
|
+
commandDeps?: CeoCommandDeps;
|
|
11
21
|
}
|
|
12
22
|
|
|
13
|
-
/** Addressed
|
|
23
|
+
/** Addressed work preserves graph intake except for one owner's private seat request. */
|
|
14
24
|
export async function handleTelegramSeatWork(
|
|
15
25
|
text: string,
|
|
16
26
|
msg: TelegramWorkMessage,
|
|
17
27
|
deps: TelegramSeatWorkDeps = {},
|
|
18
28
|
): Promise<string | null> {
|
|
19
|
-
|
|
29
|
+
const owner = (): { cfg: UserConfig; id: string } | null => {
|
|
30
|
+
if (msg.userId === undefined || String(msg.chatId) !== String(msg.userId)) return null;
|
|
31
|
+
const cfg = deps.config ?? getUserConfig();
|
|
32
|
+
const id = telegramDecisionOwner(cfg);
|
|
33
|
+
return id && id === String(msg.userId) ? { cfg, id } : null;
|
|
34
|
+
};
|
|
35
|
+
const sendTask = async (seat: string, body: string, cfg: UserConfig, id: string): Promise<string> => {
|
|
36
|
+
const result = await (deps.dispatch ?? dispatchCeoTask)(seat, body, deps.commandDeps ?? ceoTaskDeps(cfg, id), { via: 'telegram' });
|
|
37
|
+
debug.log('seat.dispatch', 'intent', { seat, intent: 'task', via: 'telegram', outcome: result.channel });
|
|
38
|
+
return result.reply;
|
|
39
|
+
};
|
|
40
|
+
if (!text.startsWith('@')) {
|
|
41
|
+
if (!text.trim() || /^\s*[/@@]/.test(text)) return null;
|
|
42
|
+
const auth = owner();
|
|
43
|
+
const configured = (auth?.cfg.raw?.seatDispatch as { defaultSeat?: unknown } | undefined)?.defaultSeat;
|
|
44
|
+
if (typeof configured !== 'string' || classifyCeoIntent(text) !== 'task') return null;
|
|
45
|
+
const seat = resolveSeat(configured);
|
|
46
|
+
if (!seat) return null;
|
|
47
|
+
const id = canonicalSeatId(seat.id);
|
|
48
|
+
return ['OP', 'TC', 'MK', 'UX'].includes(id) ? sendTask(id, text, auth!.cfg, auth!.id) : null;
|
|
49
|
+
}
|
|
20
50
|
const address = parseSeatAddress(text);
|
|
21
51
|
if (!address) return null;
|
|
22
52
|
|
|
@@ -34,6 +64,18 @@ export async function handleTelegramSeatWork(
|
|
|
34
64
|
return '어떤 일을 맡길까요? 좌석 주소 뒤에 요청 내용을 적어 다시 보내 주세요.';
|
|
35
65
|
}
|
|
36
66
|
|
|
67
|
+
if (seats.length === 1 && seats[0]?.seat) {
|
|
68
|
+
const auth = owner();
|
|
69
|
+
const seatId = canonicalSeatId(seats[0].seat.id);
|
|
70
|
+
if (auth && ['OP', 'TC', 'MK', 'UX'].includes(seatId)) {
|
|
71
|
+
const intent = classifyCeoIntent(body);
|
|
72
|
+
if (intent === 'task') return sendTask(seatId, body, auth.cfg, auth.id);
|
|
73
|
+
const answer = await (deps.answer ?? answerAsSeat)(seats[0].name, body);
|
|
74
|
+
debug.log('seat.dispatch', 'intent', { seat: seatId, intent, via: 'telegram', outcome: answer ? 'answered' : 'intake' });
|
|
75
|
+
if (answer) return answer.text;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
37
79
|
const reportTo = {
|
|
38
80
|
channel: 'telegram' as const,
|
|
39
81
|
chatId: msg.chatId,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { program } from '../index.js';
|
|
3
|
+
import { FEATURE_MATURITY, type Maturity, type Role } from './feature-maturity.js';
|
|
4
|
+
|
|
5
|
+
/** Commander owns aliases; the table contains canonical command names only. */
|
|
6
|
+
export function cliRootMaturity(name: string, commands: readonly Command[] = program.commands): Maturity | undefined {
|
|
7
|
+
const command = commands.find((entry) => entry.name() === name || entry.aliases().includes(name));
|
|
8
|
+
const canonical = command?.name() ?? name;
|
|
9
|
+
return Object.prototype.hasOwnProperty.call(FEATURE_MATURITY.cliRoot, canonical)
|
|
10
|
+
? FEATURE_MATURITY.cliRoot[canonical as keyof typeof FEATURE_MATURITY.cliRoot]
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function cliRootVisibleFor(name: string, role: Role, { showAll }: { showAll: boolean }, commands: readonly Command[] = program.commands): boolean {
|
|
15
|
+
const grade = cliRootMaturity(name, commands);
|
|
16
|
+
if (!grade || grade === 'system') return false;
|
|
17
|
+
if (showAll || role === 'owner') return true;
|
|
18
|
+
return grade === 'stable' || (role === 'contributor' && (grade === 'beta' || grade === 'tool'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Only help is filtered: Commander still dispatches explicitly typed commands. */
|
|
22
|
+
export function filterCliRootHelp(commands: readonly Command[], role: Role, showAll: boolean): { shown: number; hidden: number } {
|
|
23
|
+
let shown = 0;
|
|
24
|
+
let hidden = 0;
|
|
25
|
+
for (const command of commands) {
|
|
26
|
+
const grade = cliRootMaturity(command.name(), commands);
|
|
27
|
+
if (!grade) throw new Error(`cliRoot 에 등급 없는 명령: ${command.name()}`);
|
|
28
|
+
const visible = cliRootVisibleFor(command.name(), role, { showAll }, commands);
|
|
29
|
+
(command as Command & { _hidden: boolean })._hidden = !visible;
|
|
30
|
+
if (visible) shown++;
|
|
31
|
+
else hidden++;
|
|
32
|
+
}
|
|
33
|
+
return { shown, hidden };
|
|
34
|
+
}
|
|
@@ -13,6 +13,7 @@ export const FEATURE_MATURITY = {
|
|
|
13
13
|
'/setup/done': { pwa: 'stable' },
|
|
14
14
|
'/consult': { pwa: 'stable' },
|
|
15
15
|
'/chat': { pwa: 'stable' },
|
|
16
|
+
'/today': { pwa: 'stable' },
|
|
16
17
|
'/term': { pwa: 'stable' },
|
|
17
18
|
'/live': { pwa: 'stable' },
|
|
18
19
|
'/market': { pwa: 'stable' },
|
|
@@ -38,6 +39,9 @@ export const FEATURE_MATURITY = {
|
|
|
38
39
|
'/design-check': { pwa: 'tool' },
|
|
39
40
|
'/control': { pwa: 'tool' },
|
|
40
41
|
'/scheduler': { pwa: 'ops' },
|
|
42
|
+
'/ops/release': { pwa: 'ops' },
|
|
43
|
+
'/ops/seats': { pwa: 'ops' },
|
|
44
|
+
'/ops/checklist': { pwa: 'ops' },
|
|
41
45
|
'/dashboard': { pwa: 'ops' },
|
|
42
46
|
'/bots': { pwa: 'ops' },
|
|
43
47
|
'/botlab': { pwa: 'ops' },
|
|
@@ -46,7 +50,101 @@ export const FEATURE_MATURITY = {
|
|
|
46
50
|
'/workflows/chat-ui': { pwa: 'broken' },
|
|
47
51
|
'/share': { pwa: 'system' },
|
|
48
52
|
},
|
|
49
|
-
|
|
53
|
+
// Canonical CLI command names only; aliases inherit their command's grade.
|
|
54
|
+
cliRoot: {
|
|
55
|
+
pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta',
|
|
56
|
+
where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
|
|
57
|
+
pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
|
|
58
|
+
research: 'beta', release: 'beta', seat: 'beta', coord: 'beta', 'model-watch': 'beta', doctor: 'stable',
|
|
59
|
+
control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
|
|
60
|
+
grounding: 'beta', graph: 'beta', loop: 'stable', card: 'stable', 'launch-head': 'beta',
|
|
61
|
+
a2a: 'beta', plugin: 'stable', skills: 'beta', connect: 'beta', import: 'beta',
|
|
62
|
+
persona: 'beta', market: 'beta', directive: 'stable', connector: 'beta', storyboard: 'beta',
|
|
63
|
+
python: 'beta', 'self-update': 'stable', 'measure-fabric-arc-ab': 'beta', mcp: 'beta',
|
|
64
|
+
relay: 'beta', 'telegram-test': 'beta', telegram: 'beta', 'discord-test': 'beta',
|
|
65
|
+
sync: 'beta', status: 'beta', git: 'beta', gh: 'beta', repro: 'system',
|
|
66
|
+
theme: 'beta', history: 'beta', inspect: 'beta', autopilot: 'beta', memory: 'beta',
|
|
67
|
+
decide: 'beta', 'ax-screen': 'beta', 'decide-recipe': 'beta', signals: 'beta',
|
|
68
|
+
loops: 'beta', buzz: 'beta', harness: 'stable', self: 'beta', factcheck: 'beta',
|
|
69
|
+
provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
|
|
70
|
+
'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
|
|
71
|
+
schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
|
|
72
|
+
decisions: 'beta', directives: 'beta', fleet: 'beta', ops: 'stable', publish: 'beta',
|
|
73
|
+
env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
|
|
74
|
+
config: 'stable', tasks: 'stable', labels: 'beta', msg: 'beta', ask: 'stable',
|
|
75
|
+
onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
|
|
76
|
+
agent: 'stable', registry: 'beta', tier: 'stable', local: 'beta', scheduler: 'beta',
|
|
77
|
+
attach: 'beta', wf: 'beta', nexus: 'stable', phone: 'beta', token: 'beta',
|
|
78
|
+
acp: 'beta', voice: 'beta',
|
|
79
|
+
'run-detached': 'system', dogfood: 'system', run: 'system',
|
|
80
|
+
},
|
|
81
|
+
// Canonical names only; aliases inherit their command's grade at lookup.
|
|
82
|
+
// Count: bun -e "import('./src/maturity/feature-maturity.ts').then(m=>console.log(Object.keys(m.FEATURE_MATURITY.tuiSlash).length))" (= SLASH_COMMANDS canonical names).
|
|
83
|
+
tuiSlash: {
|
|
84
|
+
help: 'stable',
|
|
85
|
+
resume: 'stable',
|
|
86
|
+
model: 'stable',
|
|
87
|
+
clear: 'stable',
|
|
88
|
+
status: 'stable',
|
|
89
|
+
remaining: 'stable',
|
|
90
|
+
setup: 'stable',
|
|
91
|
+
quit: 'stable',
|
|
92
|
+
'run-skill': 'stable',
|
|
93
|
+
ad: 'beta',
|
|
94
|
+
design: 'beta',
|
|
95
|
+
provider: 'stable',
|
|
96
|
+
reasoning: 'stable',
|
|
97
|
+
local: 'tool',
|
|
98
|
+
session: 'stable',
|
|
99
|
+
fork: 'stable',
|
|
100
|
+
rewind: 'stable',
|
|
101
|
+
mission: 'beta',
|
|
102
|
+
'resume-turn': 'beta',
|
|
103
|
+
context: 'stable',
|
|
104
|
+
paste: 'stable',
|
|
105
|
+
sync: 'tool',
|
|
106
|
+
plugin: 'tool',
|
|
107
|
+
widget: 'tool',
|
|
108
|
+
log: 'tool',
|
|
109
|
+
memory: 'beta',
|
|
110
|
+
export: 'stable',
|
|
111
|
+
delta: 'tool',
|
|
112
|
+
theme: 'stable',
|
|
113
|
+
debug: 'tool',
|
|
114
|
+
rebind: 'tool',
|
|
115
|
+
'api-allow': 'tool',
|
|
116
|
+
prompt: 'beta',
|
|
117
|
+
history: 'stable',
|
|
118
|
+
research: 'beta',
|
|
119
|
+
harness: 'ops',
|
|
120
|
+
plan: 'beta',
|
|
121
|
+
chat: 'stable',
|
|
122
|
+
dashboard: 'stable',
|
|
123
|
+
telegram: 'ops',
|
|
124
|
+
tablet: 'ops',
|
|
125
|
+
surface: 'ops',
|
|
126
|
+
term: 'tool',
|
|
127
|
+
claude: 'tool',
|
|
128
|
+
codex: 'tool',
|
|
129
|
+
gemini: 'tool',
|
|
130
|
+
acp: 'beta',
|
|
131
|
+
conv: 'beta',
|
|
132
|
+
handoff: 'ops',
|
|
133
|
+
'agent-room': 'ops',
|
|
134
|
+
showroom: 'ops',
|
|
135
|
+
reply: 'ops',
|
|
136
|
+
capture: 'ops',
|
|
137
|
+
inject: 'ops',
|
|
138
|
+
relay: 'ops',
|
|
139
|
+
lane: 'ops',
|
|
140
|
+
control: 'ops',
|
|
141
|
+
default: 'ops',
|
|
142
|
+
qc: 'ops',
|
|
143
|
+
'voice-chat': 'beta',
|
|
144
|
+
'auto-tts': 'beta',
|
|
145
|
+
directive: 'ops',
|
|
146
|
+
},
|
|
147
|
+
} as const satisfies { pwaRoute: Record<string, SurfaceMaturity>; cliRoot: Record<string, Maturity>; tuiSlash: Record<string, Maturity> };
|
|
50
148
|
|
|
51
149
|
/** Unregistered routes and surfaces are not implicitly mature. Desktop shares the PWA implementation. */
|
|
52
150
|
export function maturityOn(route: string, surface: Surface): Maturity | undefined {
|
|
@@ -63,3 +161,4 @@ export function visibleOn(route: string, surface: Surface, role: Role): boolean
|
|
|
63
161
|
if (route === '/settings') return true;
|
|
64
162
|
return maturity === 'stable' || (role === 'contributor' && (maturity === 'beta' || maturity === 'tool'));
|
|
65
163
|
}
|
|
164
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// MAT1b — TUI slash suggestions use the same maturity source as PWA routes.
|
|
2
|
+
// Kept out of feature-maturity.ts: the PWA bundles that file, and this one
|
|
3
|
+
// needs the TUI command list.
|
|
4
|
+
import { SLASH_COMMANDS } from '../chat/index.js';
|
|
5
|
+
import { getUserConfig } from '../user-config.js';
|
|
6
|
+
import { FEATURE_MATURITY, type Maturity, type Role } from './feature-maturity.js';
|
|
7
|
+
|
|
8
|
+
export function readTuiSlashAudience(raw: unknown = getUserConfig().raw?.tui): { role: Role; showBeta: boolean } {
|
|
9
|
+
const tui = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
|
|
10
|
+
const role = tui.role === 'general' || tui.role === 'contributor' || tui.role === 'owner' ? tui.role : 'owner';
|
|
11
|
+
return { role, showBeta: tui.showBeta === true };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function slashMaturity(name: string): Maturity | undefined {
|
|
15
|
+
const command = SLASH_COMMANDS.find((entry) => entry.name === name || entry.aliases?.includes(name));
|
|
16
|
+
if (!command) return undefined;
|
|
17
|
+
return FEATURE_MATURITY.tuiSlash[command.name as keyof typeof FEATURE_MATURITY.tuiSlash];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Visibility of a TUI slash suggestion, not permission to execute a typed command.
|
|
21
|
+
* general = stable (+ beta with showBeta) · contributor = stable·beta·tool · owner = all.
|
|
22
|
+
* Registered commands without a maturity grade remain visible to owner only in picker/help;
|
|
23
|
+
* unregistered names are never visible through this predicate. */
|
|
24
|
+
export function slashVisibleFor(name: string, role: Role, { showBeta }: { showBeta: boolean }): boolean {
|
|
25
|
+
const grade = slashMaturity(name);
|
|
26
|
+
if (!grade) return false;
|
|
27
|
+
if (role === 'owner') return true;
|
|
28
|
+
if (grade === 'stable') return true;
|
|
29
|
+
if (role === 'contributor') return grade === 'beta' || grade === 'tool';
|
|
30
|
+
return grade === 'beta' && showBeta;
|
|
31
|
+
}
|
package/src/memory.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { randomUUID, createHash } from 'node:crypto';
|
|
|
35
35
|
import { debug } from './debug/log.js';
|
|
36
36
|
import { estimateTokens } from './tokens.js';
|
|
37
37
|
import { budgetModel } from './llm/model-defaults.js';
|
|
38
|
+
import { getAppliedGlobalTestRoot } from './cli/test-flag.js';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* 임시 파일 → fsync → rename → 폴더 fsync. rename 만으로는 «원자적»이지만 «내구적»이 아니다.
|
|
@@ -86,14 +87,16 @@ export interface MemoryEntry {
|
|
|
86
87
|
* Use for core / safety / reference-location facts that must always be
|
|
87
88
|
* in context. Costs tokens every turn, so keep pinned bodies short. */
|
|
88
89
|
pinned: boolean;
|
|
90
|
+
/** Optional pinned-memory freshness window; absent memories retain their original injection text. */
|
|
91
|
+
staleAfterMinutes?: number;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
// ── Paths ────────────────────────────────────────────────────────────
|
|
92
95
|
|
|
93
96
|
export function memoryRoot(): string {
|
|
94
|
-
const base = process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME.trim()
|
|
97
|
+
const base = getAppliedGlobalTestRoot() ?? (process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME.trim()
|
|
95
98
|
? process.env.XDG_DATA_HOME
|
|
96
|
-
: join(homedir(), '.local', 'share');
|
|
99
|
+
: join(homedir(), '.local', 'share'));
|
|
97
100
|
return join(base, 'elanous', 'memory');
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -124,6 +127,12 @@ function parseFrontmatter(raw: string): { fm: Record<string, string>; body: stri
|
|
|
124
127
|
return { fm, body };
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
function parseStaleAfterMinutes(value: string | number | undefined): number | undefined {
|
|
131
|
+
if (value === undefined || value === '') return undefined;
|
|
132
|
+
const n = Number(value);
|
|
133
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
127
136
|
function renderFrontmatter(fm: Record<string, string>, body: string): string {
|
|
128
137
|
const lines = ['---'];
|
|
129
138
|
for (const [k, v] of Object.entries(fm)) lines.push(`${k}: ${v}`);
|
|
@@ -179,6 +188,8 @@ export interface SaveMemoryOpts {
|
|
|
179
188
|
priority?: number;
|
|
180
189
|
/** Always-inject (bypass keyword gate). Default false — or preserve on update. */
|
|
181
190
|
pinned?: boolean;
|
|
191
|
+
/** Pinned injection warns after this many minutes; preserved on update. */
|
|
192
|
+
staleAfterMinutes?: number;
|
|
182
193
|
/** Override the generated id (tests). */
|
|
183
194
|
id?: string;
|
|
184
195
|
}
|
|
@@ -195,16 +206,19 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
195
206
|
let createdAt = now;
|
|
196
207
|
let existingPriority: number | undefined;
|
|
197
208
|
let existingPinned: boolean | undefined;
|
|
209
|
+
let existingStaleAfterMinutes: number | undefined;
|
|
198
210
|
if (existsSync(path)) {
|
|
199
211
|
try {
|
|
200
212
|
const parsed = parseFrontmatter(readFileSync(path, 'utf-8'));
|
|
201
213
|
if (parsed.fm.created) createdAt = parsed.fm.created;
|
|
202
214
|
if (parsed.fm.priority) existingPriority = parseInt(parsed.fm.priority, 10) || 0;
|
|
203
215
|
if (parsed.fm.pinned) existingPinned = parsed.fm.pinned === 'true';
|
|
216
|
+
existingStaleAfterMinutes = parseStaleAfterMinutes(parsed.fm['stale-after-minutes']);
|
|
204
217
|
} catch { /* keep new createdAt */ }
|
|
205
218
|
}
|
|
206
219
|
const priority = opts.priority ?? existingPriority ?? 0;
|
|
207
220
|
const pinned = opts.pinned ?? existingPinned ?? false;
|
|
221
|
+
const staleAfterMinutes = parseStaleAfterMinutes(opts.staleAfterMinutes) ?? existingStaleAfterMinutes;
|
|
208
222
|
const frontmatter: Record<string, string> = {
|
|
209
223
|
name: opts.name,
|
|
210
224
|
description: opts.description,
|
|
@@ -214,6 +228,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
214
228
|
};
|
|
215
229
|
if (priority > 0) frontmatter.priority = String(Math.floor(priority));
|
|
216
230
|
if (pinned) frontmatter.pinned = 'true';
|
|
231
|
+
if (staleAfterMinutes !== undefined) frontmatter['stale-after-minutes'] = String(staleAfterMinutes);
|
|
217
232
|
const raw = renderFrontmatter(frontmatter, opts.body);
|
|
218
233
|
writeFileDurable(path, raw);
|
|
219
234
|
|
|
@@ -232,6 +247,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
232
247
|
keywords: extractKeywords(`${opts.name} ${opts.description}`),
|
|
233
248
|
priority,
|
|
234
249
|
pinned,
|
|
250
|
+
staleAfterMinutes,
|
|
235
251
|
};
|
|
236
252
|
}
|
|
237
253
|
|
|
@@ -257,6 +273,7 @@ function parseMemoryFile(path: string): MemoryEntry | null {
|
|
|
257
273
|
keywords: extractKeywords(`${fm.name} ${fm.description ?? ''}`),
|
|
258
274
|
priority: fm.priority ? (parseInt(fm.priority, 10) || 0) : 0,
|
|
259
275
|
pinned: fm.pinned === 'true',
|
|
276
|
+
staleAfterMinutes: parseStaleAfterMinutes(fm['stale-after-minutes']),
|
|
260
277
|
};
|
|
261
278
|
}
|
|
262
279
|
|
|
@@ -432,6 +449,17 @@ export function readIndex(root: string = memoryRoot()): string {
|
|
|
432
449
|
|
|
433
450
|
// ── Injection heuristic ──────────────────────────────────────────────
|
|
434
451
|
|
|
452
|
+
function pinnedChunk(e: MemoryEntry): string {
|
|
453
|
+
const header = `### 📌 [${e.type}] ${e.name}\n\n`;
|
|
454
|
+
const body = (e.body || '').trim() || e.description;
|
|
455
|
+
const updated = Date.parse(e.updatedAt);
|
|
456
|
+
if (e.staleAfterMinutes === undefined || !Number.isFinite(updated)) return header + body;
|
|
457
|
+
const ageMinutes = (Date.now() - updated) / 60_000;
|
|
458
|
+
if (ageMinutes <= e.staleAfterMinutes) return header + body;
|
|
459
|
+
const hours = Math.floor(ageMinutes / 60);
|
|
460
|
+
return header + `⚠️ 이 기억은 ${hours}시간 전 것이다 — 답하기 전에 원장 도구 release_status(판 일정·칸) · decisions_pending(미결 결정) · ops_seats(자리 현황)로 다시 확인하고, 확인 전에는 «확인 중»이라고 말한다.\n${body}`;
|
|
461
|
+
}
|
|
462
|
+
|
|
435
463
|
export interface InjectionResult {
|
|
436
464
|
/** Markdown block to prepend to the system prompt (or '' when empty). */
|
|
437
465
|
block: string;
|
|
@@ -490,7 +518,7 @@ export function buildMemoryInjection(
|
|
|
490
518
|
const pinnedMems = listMemories({}, root).filter(m => m.pinned).sort((a, b) => b.priority - a.priority);
|
|
491
519
|
const pinnedParts: string[] = [];
|
|
492
520
|
for (const e of pinnedMems) {
|
|
493
|
-
const chunk =
|
|
521
|
+
const chunk = pinnedChunk(e);
|
|
494
522
|
const tok = estimateTokens(chunk);
|
|
495
523
|
if (usedTokens + tok > maxTokens) break;
|
|
496
524
|
pinnedParts.push(chunk);
|
|
@@ -653,7 +681,7 @@ export async function buildMemoryInjectionLLM(
|
|
|
653
681
|
const pinnedMems = listMemories({}, root).filter((m) => m.pinned).sort((a, b) => b.priority - a.priority);
|
|
654
682
|
const pinnedParts: string[] = [];
|
|
655
683
|
for (const e of pinnedMems) {
|
|
656
|
-
const chunk =
|
|
684
|
+
const chunk = pinnedChunk(e);
|
|
657
685
|
const tok = estimateTokens(chunk);
|
|
658
686
|
if (usedTokens + tok > maxTokens) break;
|
|
659
687
|
pinnedParts.push(chunk); injected.push(e.id); usedTokens += tok;
|