elanous 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/package.json +2 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +219 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/gate-node.ts +61 -17
- package/scripts/release-loop/ops-upgrade-node.ts +59 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/version-node.ts +13 -3
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-deterministic.ts +53 -3
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/ux/demo-rehearsal.ts +17 -13
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
- package/skills/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -0
- package/src/agent/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +150 -23
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +2 -2
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +1 -0
- package/src/cli/decisions-cli.ts +51 -19
- package/src/cli/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +45 -13
- package/src/cli/pod-cli.ts +27 -3
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/pr-cli.ts +15 -1
- package/src/cli/release-cli.ts +135 -15
- package/src/context-bus/condense.ts +194 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-surfaces.ts +32 -2
- package/src/context-bus/context-now.ts +2 -2
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/long-term-memory.ts +118 -22
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +8 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
- package/src/decisions/decision-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +43 -17
- package/src/domains/outbound-alert.ts +99 -31
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/execution-loop/launch-gate.ts +17 -8
- package/src/field-note/telegram.ts +142 -0
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow-llm.ts +23 -13
- package/src/graph-runner/graph-grow.ts +69 -13
- package/src/graph-runner/graph-tick.ts +9 -2
- package/src/graph-runner/runner.ts +92 -24
- package/src/harness/harness-cli-command.ts +165 -17
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +306 -0
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +82 -3
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/index.ts +232 -16
- package/src/intake-plane/discord-seat-work.ts +36 -3
- package/src/intake-plane/linear-wish.ts +44 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/llm/difficulty.ts +1 -1
- package/src/loops/checker.ts +178 -0
- package/src/loops/loop-cli.ts +37 -3
- package/src/loops/observe.ts +49 -0
- package/src/loops/orchestrator/overlap.ts +141 -0
- package/src/loops/orchestrator/traffic.ts +150 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/verdict.ts +22 -0
- package/src/maturity/feature-maturity.ts +5 -4
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +78 -8
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/personas.ts +97 -54
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflows.ts +52 -0
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +10 -2
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/plugins/maker/market-bundle.ts +2 -1
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/checklist.ts +15 -3
- package/src/release-loop/feature-store.ts +24 -10
- package/src/release-loop/landed-but-yellow.ts +60 -0
- package/src/seat-dispatch/seat-ask.ts +57 -12
- package/src/seat-loop/seat-loop.ts +253 -18
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/draft-sweep.ts +44 -10
- package/src/self-dev/draft-triage-rules.ts +54 -2
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-scope.ts +9 -8
- package/src/self-implement/headless-elanous-driver.ts +7 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +15 -0
- package/src/self-implement/test-seams.ts +1 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +78 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
- package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
- package/src/telegram-commands.ts +21 -0
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-project-command.ts +84 -4
- package/src/telegram.ts +37 -5
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +101 -13
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { debug } from '../../debug/log.js';
|
|
5
|
+
import { loopAgentContractErrors } from '../contract/validate.js';
|
|
6
|
+
|
|
7
|
+
const contractSchema = JSON.parse(readFileSync(resolve(import.meta.dir, '../contract/loop-agent.schema.json'), 'utf8')) as { $id: string };
|
|
8
|
+
const allowedOverlayFields = new Set(['neighbors', 'cadence', 'grounding', 'budget']);
|
|
9
|
+
type RecordValue = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
function record(value: unknown): value is RecordValue {
|
|
12
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function schemaMajor(value: string): number | null {
|
|
16
|
+
const match = /^(?:https:\/\/elanous\.ai\/schemas\/loop-agent\/)?(\d+)\.\d+\.\d+$/.exec(value);
|
|
17
|
+
return match ? Number(match[1]) : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The package version is independent of the declared contract schema version. */
|
|
21
|
+
export function contractMajor(manifest: RecordValue): { ok: boolean; error?: string } {
|
|
22
|
+
const declared = manifest.schema ?? manifest.$schema ?? contractSchema.$id;
|
|
23
|
+
const expected = schemaMajor(contractSchema.$id);
|
|
24
|
+
const actual = typeof declared === 'string' ? schemaMajor(declared) : null;
|
|
25
|
+
const other = manifest.schema !== undefined && manifest.$schema !== undefined ? manifest.$schema : undefined;
|
|
26
|
+
if (actual !== null && expected !== null && actual === expected &&
|
|
27
|
+
(other === undefined || typeof other === 'string' && schemaMajor(other) === expected)) return { ok: true };
|
|
28
|
+
return { ok: false, error: `contract major mismatch: declared ${String(declared)}${other === undefined ? '' : ` / ${String(other)}`}, expected ${contractSchema.$id}` };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Replace only allowlisted top-level fields; refused fields never enter the merged manifest. */
|
|
32
|
+
export function mergeOverlay(manifest: RecordValue, overlay: unknown): { merged: RecordValue; refused: string[] } {
|
|
33
|
+
const merged = { ...manifest };
|
|
34
|
+
const refused: string[] = [];
|
|
35
|
+
if (!record(overlay)) return { merged, refused: ['overlay'] };
|
|
36
|
+
for (const [field, value] of Object.entries(overlay)) {
|
|
37
|
+
if (!allowedOverlayFields.has(field)) refused.push(field);
|
|
38
|
+
else merged[field] = value;
|
|
39
|
+
}
|
|
40
|
+
return { merged, refused };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function inside(root: string, path: string): boolean {
|
|
44
|
+
const part = relative(root, path);
|
|
45
|
+
return part !== '..' && !part.startsWith(`..${sep}`) && !isAbsolute(part);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readReference(root: string, name: string, path: unknown, errors: string[]): void {
|
|
49
|
+
if (typeof path !== 'string' || !path || isAbsolute(path) || /^[a-zA-Z]:[\\/]/.test(path) || path.split(/[\\/]/).includes('..')) {
|
|
50
|
+
errors.push(`${name} path escape: ${String(path)}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const candidate = resolve(root, path);
|
|
54
|
+
if (!inside(root, candidate)) {
|
|
55
|
+
errors.push(`${name} path escape: ${path}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const actual = realpathSync(candidate);
|
|
60
|
+
if (!inside(root, actual)) errors.push(`${name} path escape: ${path}`);
|
|
61
|
+
else if (!statSync(actual).isFile()) errors.push(`${name} file missing: ${path}`);
|
|
62
|
+
} catch {
|
|
63
|
+
errors.push(`${name} file missing: ${path}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readLoopPackage(dir: string): { manifest: RecordValue | null; overlay?: unknown; errors: string[] } {
|
|
68
|
+
const errors: string[] = [];
|
|
69
|
+
let manifest: RecordValue | null = null;
|
|
70
|
+
let overlay: unknown;
|
|
71
|
+
const root = realpathSync(dir);
|
|
72
|
+
if (!statSync(root).isDirectory()) throw new Error(`${dir} is not a directory`);
|
|
73
|
+
const readYaml = (name: string): unknown => {
|
|
74
|
+
const path = resolve(root, name);
|
|
75
|
+
const actual = realpathSync(path);
|
|
76
|
+
if (!inside(root, actual) || !statSync(actual).isFile()) throw new Error(`${name} path escape or not a file`);
|
|
77
|
+
return parseYaml(readFileSync(actual, 'utf8'));
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const parsed = readYaml('loop.yaml');
|
|
81
|
+
if (!record(parsed)) errors.push('loop.yaml must contain a manifest object');
|
|
82
|
+
else manifest = parsed;
|
|
83
|
+
} catch (error) { errors.push(`loop.yaml: ${error instanceof Error ? error.message : String(error)}`); }
|
|
84
|
+
try { overlay = readYaml('overlay.yaml'); }
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (!(record(error) && error.code === 'ENOENT')) errors.push(`overlay.yaml: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
if (manifest) {
|
|
89
|
+
readReference(root, 'graph', manifest.graph, errors);
|
|
90
|
+
readReference(root, 'role', manifest.role, errors);
|
|
91
|
+
}
|
|
92
|
+
return { manifest, overlay, errors };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function verifyLoopPackage(dir: string): {
|
|
96
|
+
ok: boolean; errors: string[]; refusedOverlayFields: string[]; merged: RecordValue | null;
|
|
97
|
+
} {
|
|
98
|
+
let manifest: RecordValue | null = null;
|
|
99
|
+
let overlay: unknown;
|
|
100
|
+
const errors: string[] = [];
|
|
101
|
+
try {
|
|
102
|
+
const read = readLoopPackage(dir);
|
|
103
|
+
manifest = read.manifest;
|
|
104
|
+
overlay = read.overlay;
|
|
105
|
+
errors.push(...read.errors);
|
|
106
|
+
} catch (error) { errors.push(`package directory: ${error instanceof Error ? error.message : String(error)}`); }
|
|
107
|
+
let merged: RecordValue | null = null;
|
|
108
|
+
let refusedOverlayFields: string[] = [];
|
|
109
|
+
if (manifest) {
|
|
110
|
+
const major = contractMajor(manifest);
|
|
111
|
+
if (major.error) errors.push(major.error);
|
|
112
|
+
const result = overlay === undefined ? { merged: { ...manifest }, refused: [] } : mergeOverlay(manifest, overlay);
|
|
113
|
+
refusedOverlayFields = result.refused;
|
|
114
|
+
merged = result.merged;
|
|
115
|
+
if (refusedOverlayFields.length) errors.push(`overlay refused: ${refusedOverlayFields.join(', ')}`);
|
|
116
|
+
// Schema declaration is package metadata, not a field in the loop-agent contract.
|
|
117
|
+
const { schema: _schema, $schema: _dollarSchema, ...contractInput } = merged;
|
|
118
|
+
errors.push(...loopAgentContractErrors(contractInput));
|
|
119
|
+
}
|
|
120
|
+
const ok = errors.length === 0;
|
|
121
|
+
debug.log('loop.package', ok ? 'verified' : 'refused', {
|
|
122
|
+
id: manifest?.id, version: manifest?.version, errors: errors.length, refusedOverlayFields,
|
|
123
|
+
});
|
|
124
|
+
return { ok, errors, refusedOverlayFields, merged };
|
|
125
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type LoopCronVerdict = 'alive' | 'late' | 'failed' | 'off';
|
|
2
|
+
|
|
3
|
+
/** Shared display inputs for a graph loop or a cron job. The caller supplies
|
|
4
|
+
* the cadence in milliseconds (or null when it is not known). */
|
|
5
|
+
export interface LoopCronVerdictInput {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
lastRunAt: string | null;
|
|
8
|
+
lastStatus: string | null;
|
|
9
|
+
intervalMs: number | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Pure health classification; a failed last execution takes precedence over age.
|
|
13
|
+
* With no valid timestamp or positive cadence, lateness cannot be established. */
|
|
14
|
+
export function loopCronVerdict(entry: LoopCronVerdictInput, nowMs: number): LoopCronVerdict {
|
|
15
|
+
if (!entry.enabled) return 'off';
|
|
16
|
+
if (entry.lastStatus === 'failed' || entry.lastStatus === 'error' || entry.lastStatus === 'failure') return 'failed';
|
|
17
|
+
const lastRunMs = entry.lastRunAt === null ? NaN : Date.parse(entry.lastRunAt);
|
|
18
|
+
if (Number.isFinite(nowMs) && Number.isFinite(lastRunMs) &&
|
|
19
|
+
entry.intervalMs !== null && Number.isFinite(entry.intervalMs) && entry.intervalMs > 0 &&
|
|
20
|
+
nowMs - lastRunMs > 2 * entry.intervalMs) return 'late';
|
|
21
|
+
return 'alive';
|
|
22
|
+
}
|
|
@@ -57,11 +57,11 @@ export const FEATURE_MATURITY = {
|
|
|
57
57
|
pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta',
|
|
58
58
|
where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
|
|
59
59
|
pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
|
|
60
|
-
research: 'beta', release: 'beta', seat: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
|
|
60
|
+
research: 'beta', release: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
|
|
61
61
|
control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
|
|
62
62
|
grounding: 'beta', graph: 'beta', loop: 'stable', card: 'stable', 'launch-head': 'beta',
|
|
63
63
|
a2a: 'beta', plugin: 'stable', make: 'beta', skills: 'beta', connect: 'beta', import: 'beta',
|
|
64
|
-
persona: 'beta', 'team-set': 'beta', market: 'beta', directive: 'stable', connector: 'beta', storyboard: 'beta',
|
|
64
|
+
persona: 'beta', team: 'beta', 'team-set': 'beta', market: 'beta', directive: 'stable', connector: 'beta', storyboard: 'beta',
|
|
65
65
|
python: 'beta', 'self-update': 'stable', 'measure-fabric-arc-ab': 'beta', mcp: 'beta',
|
|
66
66
|
relay: 'beta', 'telegram-test': 'beta', telegram: 'beta', 'discord-test': 'beta',
|
|
67
67
|
sync: 'beta', status: 'beta', git: 'beta', gh: 'beta', repro: 'system',
|
|
@@ -71,7 +71,7 @@ export const FEATURE_MATURITY = {
|
|
|
71
71
|
provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
|
|
72
72
|
'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
|
|
73
73
|
schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
|
|
74
|
-
decisions: 'beta', directives: 'beta', fleet: 'beta', ops: 'stable', publish: 'beta',
|
|
74
|
+
decisions: 'beta', directives: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
|
|
75
75
|
env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
|
|
76
76
|
config: 'stable', tasks: 'stable', labels: 'beta', msg: 'beta', ask: 'stable',
|
|
77
77
|
onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
|
|
@@ -89,6 +89,7 @@ export const FEATURE_MATURITY = {
|
|
|
89
89
|
clear: 'stable',
|
|
90
90
|
status: 'stable',
|
|
91
91
|
now: 'beta', // CTX2 «지금 무엇이 돌고 있나» (#23379)
|
|
92
|
+
wish: 'beta',
|
|
92
93
|
remaining: 'stable',
|
|
93
94
|
setup: 'stable',
|
|
94
95
|
quit: 'stable',
|
|
@@ -152,7 +153,7 @@ export const FEATURE_MATURITY = {
|
|
|
152
153
|
ping: 'stable', provider: 'stable', sessions: 'stable', fork: 'stable', resume: 'stable',
|
|
153
154
|
decisions: 'stable', work: 'stable', cancel: 'stable',
|
|
154
155
|
skills: 'beta', skill: 'beta', digest: 'beta', intake: 'beta', ad: 'beta',
|
|
155
|
-
taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', project: 'beta',
|
|
156
|
+
taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', project: 'beta', wish: 'beta',
|
|
156
157
|
brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool', local: 'tool',
|
|
157
158
|
harness: 'ops',
|
|
158
159
|
// Existing commands outside the CXO four-grade menu list are owner-only.
|
package/src/memory.ts
CHANGED
|
@@ -63,6 +63,16 @@ export function writeFileDurable(path: string, data: string, fsync: (fd: number)
|
|
|
63
63
|
|
|
64
64
|
export type MemoryType = 'user' | 'feedback' | 'project' | 'reference';
|
|
65
65
|
|
|
66
|
+
/** Context-condense metadata travels with the existing curated memory file, not a parallel store. */
|
|
67
|
+
export interface ContextMemoryCard {
|
|
68
|
+
project: string;
|
|
69
|
+
seat: string;
|
|
70
|
+
topic: string;
|
|
71
|
+
source: string;
|
|
72
|
+
updatedAt: string;
|
|
73
|
+
status: 'active' | 'retired';
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
export interface MemoryEntry {
|
|
67
77
|
id: string; // uuid v4
|
|
68
78
|
filename: string; // "<uuid>-<slug>.md"
|
|
@@ -89,6 +99,7 @@ export interface MemoryEntry {
|
|
|
89
99
|
pinned: boolean;
|
|
90
100
|
/** Optional pinned-memory freshness window; absent memories retain their original injection text. */
|
|
91
101
|
staleAfterMinutes?: number;
|
|
102
|
+
contextCard?: ContextMemoryCard;
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
// ── Paths ────────────────────────────────────────────────────────────
|
|
@@ -192,6 +203,7 @@ export interface SaveMemoryOpts {
|
|
|
192
203
|
staleAfterMinutes?: number;
|
|
193
204
|
/** Override the generated id (tests). */
|
|
194
205
|
id?: string;
|
|
206
|
+
contextCard?: ContextMemoryCard;
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): MemoryEntry {
|
|
@@ -207,6 +219,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
207
219
|
let existingPriority: number | undefined;
|
|
208
220
|
let existingPinned: boolean | undefined;
|
|
209
221
|
let existingStaleAfterMinutes: number | undefined;
|
|
222
|
+
let existingContextCard: ContextMemoryCard | undefined;
|
|
210
223
|
if (existsSync(path)) {
|
|
211
224
|
try {
|
|
212
225
|
const parsed = parseFrontmatter(readFileSync(path, 'utf-8'));
|
|
@@ -214,6 +227,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
214
227
|
if (parsed.fm.priority) existingPriority = parseInt(parsed.fm.priority, 10) || 0;
|
|
215
228
|
if (parsed.fm.pinned) existingPinned = parsed.fm.pinned === 'true';
|
|
216
229
|
existingStaleAfterMinutes = parseStaleAfterMinutes(parsed.fm['stale-after-minutes']);
|
|
230
|
+
existingContextCard = parseContextCard(parsed.fm['context-card']);
|
|
217
231
|
} catch { /* keep new createdAt */ }
|
|
218
232
|
}
|
|
219
233
|
const priority = opts.priority ?? existingPriority ?? 0;
|
|
@@ -229,6 +243,8 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
229
243
|
if (priority > 0) frontmatter.priority = String(Math.floor(priority));
|
|
230
244
|
if (pinned) frontmatter.pinned = 'true';
|
|
231
245
|
if (staleAfterMinutes !== undefined) frontmatter['stale-after-minutes'] = String(staleAfterMinutes);
|
|
246
|
+
const contextCard = opts.contextCard ?? existingContextCard;
|
|
247
|
+
if (contextCard) frontmatter['context-card'] = JSON.stringify(contextCard);
|
|
232
248
|
const raw = renderFrontmatter(frontmatter, opts.body);
|
|
233
249
|
writeFileDurable(path, raw);
|
|
234
250
|
|
|
@@ -248,9 +264,23 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
|
|
|
248
264
|
priority,
|
|
249
265
|
pinned,
|
|
250
266
|
staleAfterMinutes,
|
|
267
|
+
...(contextCard ? { contextCard } : {}),
|
|
251
268
|
};
|
|
252
269
|
}
|
|
253
270
|
|
|
271
|
+
function parseContextCard(raw: string | undefined): ContextMemoryCard | undefined {
|
|
272
|
+
if (!raw) return undefined;
|
|
273
|
+
try {
|
|
274
|
+
const card: unknown = JSON.parse(raw);
|
|
275
|
+
if (card && typeof card === 'object' && !Array.isArray(card)) {
|
|
276
|
+
const c = card as Record<string, unknown>;
|
|
277
|
+
if (['project', 'seat', 'topic', 'source', 'updatedAt'].every(key => typeof c[key] === 'string')
|
|
278
|
+
&& (c.status === 'active' || c.status === 'retired')) return c as unknown as ContextMemoryCard;
|
|
279
|
+
}
|
|
280
|
+
} catch { /* malformed metadata is not a context memory */ }
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
254
284
|
/** Parse one memory file. Returns null on read/parse error. */
|
|
255
285
|
function parseMemoryFile(path: string): MemoryEntry | null {
|
|
256
286
|
if (!existsSync(path)) return null;
|
|
@@ -261,6 +291,7 @@ function parseMemoryFile(path: string): MemoryEntry | null {
|
|
|
261
291
|
const basename = path.split(/[\\/]/).pop() ?? '';
|
|
262
292
|
const match = basename.match(/^([0-9a-f-]{36})-(.+)\.md$/i);
|
|
263
293
|
const id = match ? match[1] : createHash('sha1').update(basename).digest('hex').slice(0, 36);
|
|
294
|
+
const contextCard = parseContextCard(fm['context-card']);
|
|
264
295
|
return {
|
|
265
296
|
id,
|
|
266
297
|
filename: basename,
|
|
@@ -274,6 +305,7 @@ function parseMemoryFile(path: string): MemoryEntry | null {
|
|
|
274
305
|
priority: fm.priority ? (parseInt(fm.priority, 10) || 0) : 0,
|
|
275
306
|
pinned: fm.pinned === 'true',
|
|
276
307
|
staleAfterMinutes: parseStaleAfterMinutes(fm['stale-after-minutes']),
|
|
308
|
+
...(contextCard ? { contextCard } : {}),
|
|
277
309
|
};
|
|
278
310
|
}
|
|
279
311
|
|
|
@@ -364,6 +396,7 @@ export function searchMemories(
|
|
|
364
396
|
const entries = listMemories({ type: opts.type }, root);
|
|
365
397
|
const hits: SearchHit[] = [];
|
|
366
398
|
for (const entry of entries) {
|
|
399
|
+
if (entry.contextCard?.status === 'retired') continue;
|
|
367
400
|
// 정확-토큰 매칭(2026-07-19) — 이전 substring `includes(kw)` 는 "ref"가 "reference"·
|
|
368
401
|
// "prefer" 안에까지 걸려 오차용(false-recall)을 냈다(openclaw tool-search.ts 이식:
|
|
369
402
|
// tokenize→Set.has 전체토큰 동등). name/description 은 토큰집합 정확매칭, body 는
|
|
@@ -415,7 +448,7 @@ export function rebuildIndex(root: string = memoryRoot()): void {
|
|
|
415
448
|
const byType: Record<MemoryType, MemoryEntry[]> = {
|
|
416
449
|
user: [], feedback: [], project: [], reference: [],
|
|
417
450
|
};
|
|
418
|
-
for (const e of entries) byType[e.type].push(e);
|
|
451
|
+
for (const e of entries) if (e.contextCard?.status !== 'retired') byType[e.type].push(e);
|
|
419
452
|
|
|
420
453
|
const lines: string[] = [];
|
|
421
454
|
lines.push('# elanous memory index');
|
|
@@ -515,7 +548,7 @@ export function buildMemoryInjection(
|
|
|
515
548
|
|
|
516
549
|
// 📌 pinned 기억 — 키워드 무관 항상 주입(relevance gate bypass · Letta pinned block /
|
|
517
550
|
// claude-code MEMORY.md tier 동형). priority 높은 순. core/safety/ref-location 사실용.
|
|
518
|
-
const pinnedMems = listMemories({}, root).filter(m => m.pinned).sort((a, b) => b.priority - a.priority);
|
|
551
|
+
const pinnedMems = listMemories({}, root).filter(m => m.pinned && m.contextCard?.status !== 'retired').sort((a, b) => b.priority - a.priority);
|
|
519
552
|
const pinnedParts: string[] = [];
|
|
520
553
|
for (const e of pinnedMems) {
|
|
521
554
|
const chunk = pinnedChunk(e);
|
|
@@ -678,7 +711,7 @@ export async function buildMemoryInjectionLLM(
|
|
|
678
711
|
}
|
|
679
712
|
|
|
680
713
|
// pinned 항상주입(키워드/판정 무관).
|
|
681
|
-
const pinnedMems = listMemories({}, root).filter((m) => m.pinned).sort((a, b) => b.priority - a.priority);
|
|
714
|
+
const pinnedMems = listMemories({}, root).filter((m) => m.pinned && m.contextCard?.status !== 'retired').sort((a, b) => b.priority - a.priority);
|
|
682
715
|
const pinnedParts: string[] = [];
|
|
683
716
|
for (const e of pinnedMems) {
|
|
684
717
|
const chunk = pinnedChunk(e);
|
|
@@ -693,7 +726,7 @@ export async function buildMemoryInjectionLLM(
|
|
|
693
726
|
let judged: MemoryEntry[] = [];
|
|
694
727
|
if (!lowContext) {
|
|
695
728
|
const candidates = listMemories({}, root)
|
|
696
|
-
.filter((m) => !m.pinned && !injected.includes(m.id))
|
|
729
|
+
.filter((m) => !m.pinned && m.contextCard?.status !== 'retired' && !injected.includes(m.id))
|
|
697
730
|
.slice(0, opts.maxCandidates ?? 60);
|
|
698
731
|
if (candidates.length > 0) {
|
|
699
732
|
try {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
4
|
+
import { defaultAttachmentBaseDir, resolveAttachmentPath } from '../../boot/attachment-store.js';
|
|
5
|
+
import { detectCard, NotACardError, runCardFollowup } from '../../card-followup/core.js';
|
|
6
|
+
import { debug } from '../../debug/log.js';
|
|
7
|
+
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
8
|
+
import { localOcrText } from '../../telegram-card-followup.js';
|
|
9
|
+
import { jsonResponse } from './json-response.js';
|
|
10
|
+
|
|
11
|
+
export const CARD_FOLLOWUP_PATH = '/v1/card-followup';
|
|
12
|
+
|
|
13
|
+
type CardJob = { id: string; status: 'judging' | 'running' | 'done' | 'not-card' | 'failed'; updatedAt: number; replies?: [string, string, string]; error?: string };
|
|
14
|
+
type CardDeps = {
|
|
15
|
+
rootDir?: string;
|
|
16
|
+
resolveAttachment?: typeof resolveAttachmentPath;
|
|
17
|
+
ocr?: typeof localOcrText;
|
|
18
|
+
detect?: typeof detectCard;
|
|
19
|
+
run?: typeof runCardFollowup;
|
|
20
|
+
now?: () => number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function withinAttachmentStore(path: string): boolean {
|
|
24
|
+
try {
|
|
25
|
+
const relativePath = relative(realpathSync(defaultAttachmentBaseDir()), realpathSync(path));
|
|
26
|
+
return relativePath !== '' && relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
27
|
+
} catch { return false; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The in-memory set identifies only work launched by this daemon; disk is the pollable ledger. */
|
|
31
|
+
export function createCardFollowupJobs({
|
|
32
|
+
rootDir = effectiveInstanceRoot(),
|
|
33
|
+
resolveAttachment = resolveAttachmentPath,
|
|
34
|
+
ocr = localOcrText,
|
|
35
|
+
detect = detectCard,
|
|
36
|
+
run = runCardFollowup,
|
|
37
|
+
now = Date.now,
|
|
38
|
+
}: CardDeps = {}) {
|
|
39
|
+
const base = join(rootDir, 'graph-runs', 'card-followup');
|
|
40
|
+
const active = new Set<string>();
|
|
41
|
+
const unavailable = new Set<string>();
|
|
42
|
+
const file = (id: string) => join(base, `${id}.pwa.json`);
|
|
43
|
+
const save = (job: CardJob) => {
|
|
44
|
+
mkdirSync(base, { recursive: true, mode: 0o700 });
|
|
45
|
+
chmodSync(base, 0o700);
|
|
46
|
+
writeFileSync(file(job.id), JSON.stringify(job), { mode: 0o600 });
|
|
47
|
+
chmodSync(file(job.id), 0o600);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
async post(req: Request): Promise<Response> {
|
|
52
|
+
let body: unknown;
|
|
53
|
+
try { body = await req.json(); } catch { body = null; }
|
|
54
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
55
|
+
const { attachmentId, context } = body as Record<string, unknown>;
|
|
56
|
+
if (context !== undefined && typeof context !== 'string') return jsonResponse({ error: 'bad_request' }, 400);
|
|
57
|
+
const imagePath = typeof attachmentId === 'string' ? resolveAttachment(attachmentId) : null;
|
|
58
|
+
if (!imagePath || (resolveAttachment === resolveAttachmentPath && !withinAttachmentStore(imagePath))) {
|
|
59
|
+
return jsonResponse({ error: 'attachment-not-found' }, 400);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const id = randomUUID();
|
|
63
|
+
const update = (status: CardJob['status'], fields: Partial<Pick<CardJob, 'replies' | 'error'>> = {}) =>
|
|
64
|
+
save({ id, status, updatedAt: now(), ...fields });
|
|
65
|
+
update('judging');
|
|
66
|
+
active.add(id);
|
|
67
|
+
debug.log('card-followup.pwa', 'started', { id });
|
|
68
|
+
// Start OCR on the next event-loop turn so the acceptance response precedes the work.
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
void (async () => {
|
|
71
|
+
try {
|
|
72
|
+
const ocrText = await ocr(imagePath);
|
|
73
|
+
if (detect({ ocrText }).decision !== 'card') {
|
|
74
|
+
update('not-card');
|
|
75
|
+
debug.log('card-followup.pwa', 'not-card', { id });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
update('running');
|
|
79
|
+
const result = await run({ imagePath, ocrText, rootDir, runId: id, ...(context === undefined ? {} : { context }) });
|
|
80
|
+
update('done', { replies: result.replies });
|
|
81
|
+
debug.log('card-followup.pwa', 'done', { id });
|
|
82
|
+
} catch (cause) {
|
|
83
|
+
const notCard = cause instanceof NotACardError;
|
|
84
|
+
// OCR·그래프 예외 문구엔 명함 원문이 섞일 수 있다 — 원장·응답엔 고정 코드만 싣는다(리뷰 must-fix).
|
|
85
|
+
try {
|
|
86
|
+
update(notCard ? 'not-card' : 'failed', notCard ? {} : { error: 'card-followup-failed' });
|
|
87
|
+
debug.log('card-followup.pwa', notCard ? 'not-card' : 'failed', { id });
|
|
88
|
+
} catch {
|
|
89
|
+
unavailable.add(id);
|
|
90
|
+
debug.log('card-followup.pwa', 'failed', { id });
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
active.delete(id);
|
|
94
|
+
}
|
|
95
|
+
})().catch(() => {
|
|
96
|
+
unavailable.add(id);
|
|
97
|
+
active.delete(id);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
return jsonResponse({ id }, 202);
|
|
101
|
+
},
|
|
102
|
+
get(id: string): Response {
|
|
103
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) return jsonResponse({ error: 'not-found' }, 404);
|
|
104
|
+
if (unavailable.has(id)) return jsonResponse({ id, status: 'failed', error: 'card-followup-failed' });
|
|
105
|
+
let job: CardJob;
|
|
106
|
+
try { job = JSON.parse(readFileSync(file(id), 'utf8')) as CardJob; }
|
|
107
|
+
catch { return jsonResponse({ error: 'not-found' }, 404); }
|
|
108
|
+
if (job.id !== id) return jsonResponse({ error: 'not-found' }, 404);
|
|
109
|
+
if ((job.status === 'judging' || job.status === 'running') && !active.has(id)) {
|
|
110
|
+
return jsonResponse({ id, status: 'failed', error: 'daemon-restarted' });
|
|
111
|
+
}
|
|
112
|
+
const { status, replies, error } = job;
|
|
113
|
+
return jsonResponse({ id, status, ...(replies ? { replies } : {}), ...(error ? { error } : {}) });
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type CardFollowupJobs = ReturnType<typeof createCardFollowupJobs>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { debug } from '../../debug/log.js';
|
|
2
|
+
import { DecisionLedger, type DecisionEntry } from '../../decisions/decision-ledger.js';
|
|
3
|
+
import { jsonResponse } from './json-response.js';
|
|
4
|
+
|
|
5
|
+
export const DECISIONS_PATH = '/v1/decisions';
|
|
6
|
+
|
|
7
|
+
export interface DecisionsRouteDeps {
|
|
8
|
+
authorize?: (request: Request) => boolean;
|
|
9
|
+
ledger?: Pick<DecisionLedger, 'list' | 'decide'>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function handleDecisions(req: Request, deps: DecisionsRouteDeps = {}): Promise<Response> {
|
|
13
|
+
const pathname = new URL(req.url).pathname;
|
|
14
|
+
if (pathname !== DECISIONS_PATH && !/^\/v1\/decisions\/[^/]+\/decide$/.test(pathname)) return jsonResponse({ error: 'not-found' }, 404);
|
|
15
|
+
if (!deps.authorize?.(req)) {
|
|
16
|
+
debug.log('decisions.api', 'rejected', { reason: 'unauthorized' });
|
|
17
|
+
return jsonResponse({ error: 'unauthorized' }, 401);
|
|
18
|
+
}
|
|
19
|
+
const ledger = deps.ledger ?? new DecisionLedger();
|
|
20
|
+
if (pathname === DECISIONS_PATH) {
|
|
21
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
22
|
+
if (new URL(req.url).searchParams.get('status') !== 'open') {
|
|
23
|
+
debug.log('decisions.api', 'rejected', { reason: 'invalid-status' });
|
|
24
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
25
|
+
}
|
|
26
|
+
const decisions = ledger.list({ status: 'open' }).map(entry => ({
|
|
27
|
+
id: entry.id,
|
|
28
|
+
title: entry.title,
|
|
29
|
+
situation: entry.scqa.s.slice(0, 200),
|
|
30
|
+
options: entry.options.map(option => ({ id: option.key, label: option.label })),
|
|
31
|
+
recommendation: entry.recommendation,
|
|
32
|
+
raisedAt: entry.raisedAt,
|
|
33
|
+
dueAt: entry.dueAt,
|
|
34
|
+
}));
|
|
35
|
+
debug.log('decisions.api', 'listed', { count: decisions.length });
|
|
36
|
+
return jsonResponse({ decisions });
|
|
37
|
+
}
|
|
38
|
+
if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
39
|
+
let id: string;
|
|
40
|
+
try { id = decodeURIComponent(pathname.slice(DECISIONS_PATH.length + 1, -'/decide'.length)); }
|
|
41
|
+
catch {
|
|
42
|
+
debug.log('decisions.api', 'rejected', { reason: 'invalid-id' });
|
|
43
|
+
return jsonResponse({ error: 'not-found' }, 404);
|
|
44
|
+
}
|
|
45
|
+
let body: unknown;
|
|
46
|
+
try { body = await req.json(); } catch { body = null; }
|
|
47
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
48
|
+
|| !('choice' in body) || typeof body.choice !== 'string' || !body.choice
|
|
49
|
+
|| ('note' in body && body.note !== undefined && typeof body.note !== 'string')) {
|
|
50
|
+
debug.log('decisions.api', 'rejected', { id, reason: 'invalid-body' });
|
|
51
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
52
|
+
}
|
|
53
|
+
const { choice, note } = body as { choice: string; note?: string };
|
|
54
|
+
try {
|
|
55
|
+
const entry: DecisionEntry = ledger.decide(id, choice, { kind: 'human' }, note);
|
|
56
|
+
debug.log('decisions.api', 'decided', { id });
|
|
57
|
+
return jsonResponse(entry);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : '';
|
|
60
|
+
const status = message.startsWith('decision not found:') ? 404
|
|
61
|
+
: message.startsWith('decision already closed:') ? 409
|
|
62
|
+
: message.startsWith('option not found:') || message.startsWith('note ') ? 400 : 0;
|
|
63
|
+
if (!status) throw error;
|
|
64
|
+
const reason = status === 404 ? 'not-found' : status === 409 ? 'already-decided' : 'bad_request';
|
|
65
|
+
debug.log('decisions.api', 'rejected', { id, reason });
|
|
66
|
+
return jsonResponse({ error: reason }, status);
|
|
67
|
+
}
|
|
68
|
+
}
|