@jlongo78/agent-spaces 0.7.7 → 0.8.0
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +6 -0
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +19 -19
- package/.next/standalone/.next/routes-manifest.json +38 -0
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page/react-loadable-manifest.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.rsc +6 -7
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +6 -7
- package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.html +1 -1
- package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.html +1 -1
- package/.next/standalone/.next/server/app/analytics.rsc +3 -3
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route.js +7 -0
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/benchmark/lobes/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route.js +7 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/benchmark/run/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route.js +7 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/benchmark/runs/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/benchmark/status/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/import/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/timeline/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/usage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/files/route.js +1 -1
- package/.next/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/whisper/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/whisper/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/whisper/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/whisper/route.js +7 -0
- package/.next/standalone/.next/server/app/api/whisper/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/whisper/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/whisper/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/cortex.html +1 -1
- package/.next/standalone/.next/server/app/cortex.rsc +3 -3
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/login.html +1 -1
- package/.next/standalone/.next/server/app/login.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects.html +1 -1
- package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions.html +1 -1
- package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/settings.html +1 -1
- package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/terminal.html +1 -1
- package/.next/standalone/.next/server/app/m/terminal.rsc +3 -3
- package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.html +1 -1
- package/.next/standalone/.next/server/app/m.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.html +1 -1
- package/.next/standalone/.next/server/app/network.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.html +1 -1
- package/.next/standalone/.next/server/app/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.html +1 -1
- package/.next/standalone/.next/server/app/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.html +1 -1
- package/.next/standalone/.next/server/app/terminal.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr/page/react-loadable-manifest.json +1 -1
- package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/vr.html +1 -1
- package/.next/standalone/.next/server/app/vr.rsc +3 -3
- package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/vr.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/vr.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/vr.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.html +1 -1
- package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +6 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0041efe4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00bf0ace._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0a837dd9._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e71d908._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e9142f3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10e47926._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1665dc78._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__175cbabf._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adae357._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d359752._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1e8fabeb._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1f8deca0._.js +8 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__253fdda1._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__28e6434f._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a386564._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2c20fb38._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__309132cd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__33fec964._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3786d8ae._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3ae92407._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3beda9fe._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4619e9bd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4a051043._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__508002e4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5086c373._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5913e097._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5b5f68d2._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c1f2459._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5ec8c977._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__63cebc6c._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__64d30d4d._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c54fc2e._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6dc1fb7e._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6e568102._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6faa04c0._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__74a34dc3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__7819e4cf._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e7250a4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8309e0a4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__86cc0e2b._.js +6 -6
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c2565a._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d178ad9._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__93ee06f3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e1f0137._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e4c154a._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d2e1d3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ae53d343._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b3a04cef._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b4270b77._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b6b6ce60._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c0757773._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c24cfa91._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__eb8acb65._.js → [root-of-the-server]__c7c47529._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c88b63f7._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cba5f007._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cbf4ceb0._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cefdba2f._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf9e82bb._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d2897392._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d3b2d856._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d73273ca._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8417eb6._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc2a55de._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e0d4690b._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e3ea8547._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e678dd53._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e9223f55._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ea630076._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f26ca49d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f33e1101._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f515f865._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fceb5d60._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fed41403._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff2e98c2._.js +2 -2
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_benchmark_lobes_route_actions_ea7beadb.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_benchmark_run_route_actions_9ed0ba41.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_benchmark_runs_[id]_route_actions_39f90307.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_benchmark_runs_route_actions_37cf958b.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_benchmark_status_route_actions_009e2cba.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_whisper_route_actions_be9a633d.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_c1cfdd09._.js → [root-of-the-server]__16621ac5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__9c81bd86._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e3bf6054._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{_81abf587._.js → _31ada310._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_648a8a2d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_999dae61._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_c48c91e5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_f8959434._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_cortex_page_tsx_0f33d8b3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_components_terminal_terminal-pane_tsx_803c5e2c._.js +9 -0
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__32a0045c._.js +1 -1
- package/.next/standalone/.next/server/edge/chunks/_d73df637._.js +1 -1
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/157826a3253f8ccf.js +7 -0
- package/.next/standalone/.next/static/chunks/{69606d281c39f9b2.js → 2f9f09924ff9d447.js} +1 -1
- package/.next/standalone/.next/static/chunks/{84fe8d44deeeb74f.js → 4ced66ef12d91b38.js} +35 -12
- package/.next/standalone/.next/static/chunks/57d04d161b8a01b3.js +7 -0
- package/.next/standalone/.next/static/chunks/709c4608e5b935e8.js +1 -0
- package/.next/standalone/.next/static/chunks/{9cfa0291d55d8d2a.js → 7b14d4e609b55b9b.js} +1 -1
- package/.next/standalone/.next/static/chunks/7be37f4a56b8f575.js +1 -0
- package/.next/standalone/.next/static/chunks/8021d5a2269ff113.js +1 -0
- package/.next/standalone/.next/static/chunks/a3fd93a9dde3cacc.js +1 -0
- package/.next/standalone/.next/static/chunks/{412140a02893327a.js → afee31c7399daf2a.js} +1 -1
- package/.next/standalone/.next/static/chunks/b92fdbf858aeb0b3.js +1 -0
- package/.next/standalone/.next/static/chunks/c17274c2f95d4ba2.js +5 -0
- package/.next/standalone/.next/static/chunks/e82f4414650587cf.js +7 -0
- package/.next/standalone/.next/static/chunks/f6464729e7aa0da0.css +3 -0
- package/.next/standalone/LICENSE +661 -0
- package/.next/standalone/NOTICE +5 -0
- package/.next/standalone/README.md +131 -0
- package/.next/standalone/bin/cortex-hook.sh +62 -62
- package/.next/standalone/bin/cortex-mcp.js +60 -60
- package/.next/standalone/bin/fix-standalone-externals.js +79 -0
- package/.next/standalone/bin/lib/auto-setup.js +110 -0
- package/.next/standalone/bin/mdns-service.js +171 -0
- package/.next/standalone/bin/postinstall.js +35 -0
- package/.next/standalone/bin/setup-admin.js +195 -0
- package/.next/standalone/bin/spaces-dev.js +247 -0
- package/.next/standalone/bin/spaces-install.js +638 -0
- package/.next/standalone/bin/spaces-reset-totp.js +50 -0
- package/.next/standalone/bin/spaces-service.js +1020 -0
- package/.next/standalone/bin/spaces-setup.js +253 -0
- package/.next/standalone/bin/spaces.js +788 -0
- package/.next/standalone/bin/ssh-auth-keys.sh +68 -0
- package/.next/standalone/bin/terminal-server.js +1807 -0
- package/.next/standalone/docker-compose.yml +28 -0
- package/.next/standalone/docs/architecture.md +387 -0
- package/.next/standalone/docs/cortex.md +293 -0
- package/.next/standalone/docs/getting-started.md +96 -0
- package/.next/standalone/docs/plans/2026-02-24-multi-agent-sessions-design.md +133 -0
- package/.next/standalone/docs/plans/2026-02-24-multi-agent-sessions-plan.md +959 -0
- package/.next/standalone/docs/plans/2026-03-07-service-command-design.md +146 -0
- package/.next/standalone/docs/plans/2026-03-07-service-command-plan.md +254 -0
- package/.next/standalone/docs/server-install.md +564 -0
- package/.next/standalone/docs/social-card.html +150 -0
- package/.next/standalone/docs/superpowers/plans/2026-03-12-spaces-cortex.md +5270 -0
- package/.next/standalone/docs/superpowers/plans/2026-03-13-cortex-wiring.md +1387 -1387
- package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-entity-graph.md +1923 -1923
- package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-knowledge-evolution.md +1113 -1113
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-boundary-engine.md +853 -853
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-context-engine.md +1274 -1274
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-signal-ingestion.md +933 -933
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-lobes.md +1080 -1080
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-gravity-system.md +768 -768
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-ui.md +1108 -1108
- package/.next/standalone/docs/superpowers/plans/2026-03-18-cortex-ui-integration.md +1846 -1846
- package/.next/standalone/docs/superpowers/plans/2026-03-19-vr-phase1-shell.md +1639 -0
- package/.next/standalone/docs/superpowers/specs/2026-03-11-universe-view-design.md +320 -0
- package/.next/standalone/docs/superpowers/specs/2026-03-12-spaces-brain-design.md +720 -0
- package/.next/standalone/docs/superpowers/specs/2026-03-13-cortex-wiring-design.md +268 -268
- package/.next/standalone/docs/superpowers/specs/2026-03-14-cortex-v2-design.md +623 -623
- package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-lobes-design.md +263 -263
- package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md +240 -240
- package/.next/standalone/docs/superpowers/specs/2026-03-16-pane-ux-design.md +77 -0
- package/.next/standalone/docs/superpowers/specs/2026-03-18-cortex-ui-integration-design.md +341 -341
- package/.next/standalone/docs/superpowers/specs/2026-03-19-vr-phase1-shell-design.md +288 -0
- package/.next/standalone/docs/tiers.md +104 -0
- package/.next/standalone/eslint.config.mjs +18 -0
- package/.next/standalone/next.config.ts +20 -0
- package/.next/standalone/nginx.conf +53 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +46 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +221 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +1 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +42 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +46 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +221 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +1 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
- package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
- package/.next/standalone/node_modules/@img/{sharp-win32-x64 → sharp-linux-x64}/package.json +46 -39
- package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
- package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
- package/.next/standalone/package-lock.json +14154 -0
- package/.next/standalone/package.json +104 -104
- package/.next/standalone/postcss.config.mjs +7 -0
- package/.next/standalone/scripts/rebuild.cmd +65 -0
- package/.next/standalone/scripts/rebuild.sh +59 -0
- package/.next/standalone/server.js +1 -1
- package/.next/standalone/src/app/(desktop)/admin/analytics/page.tsx +266 -0
- package/.next/standalone/src/app/(desktop)/admin/users/page.tsx +399 -0
- package/.next/standalone/src/app/(desktop)/analytics/page.tsx +166 -0
- package/.next/standalone/src/app/(desktop)/cortex/page.tsx +81 -78
- package/.next/standalone/src/app/(desktop)/dashboard-client.tsx +56 -0
- package/.next/standalone/src/app/(desktop)/layout.tsx +18 -0
- package/.next/standalone/src/app/(desktop)/network/page.tsx +137 -0
- package/.next/standalone/src/app/(desktop)/page.tsx +17 -0
- package/.next/standalone/src/app/(desktop)/projects/page.tsx +68 -0
- package/.next/standalone/src/app/(desktop)/sessions/[id]/page.tsx +519 -0
- package/.next/standalone/src/app/(desktop)/sessions/page.tsx +145 -0
- package/.next/standalone/src/app/(desktop)/settings/page.tsx +446 -0
- package/.next/standalone/src/app/(desktop)/terminal/layout.tsx +7 -0
- package/.next/standalone/src/app/(desktop)/terminal/page.tsx +1151 -0
- package/.next/standalone/src/app/(desktop)/terminal/pane/[id]/page.tsx +211 -0
- package/.next/standalone/src/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.tsx +192 -0
- package/.next/standalone/src/app/(desktop)/workspaces/page.tsx +12 -0
- package/.next/standalone/src/app/api/admin/analytics/route.ts +10 -0
- package/.next/standalone/src/app/api/admin/users/[id]/route.ts +20 -0
- package/.next/standalone/src/app/api/admin/users/route.ts +15 -0
- package/.next/standalone/src/app/api/analytics/overview/route.ts +80 -0
- package/.next/standalone/src/app/api/auth/login/route.ts +10 -0
- package/.next/standalone/src/app/api/auth/logout/route.ts +9 -0
- package/.next/standalone/src/app/api/auth/me/route.ts +22 -0
- package/.next/standalone/src/app/api/auth/totp/setup/route.ts +10 -0
- package/.next/standalone/src/app/api/auth/totp/status/route.ts +10 -0
- package/.next/standalone/src/app/api/auth/totp/verify/route.ts +10 -0
- package/.next/standalone/src/app/api/benchmark/lobes/route.ts +16 -0
- package/.next/standalone/src/app/api/benchmark/run/route.ts +92 -0
- package/.next/standalone/src/app/api/benchmark/runs/[id]/route.ts +26 -0
- package/.next/standalone/src/app/api/benchmark/runs/route.ts +16 -0
- package/.next/standalone/src/app/api/benchmark/status/route.ts +35 -0
- package/.next/standalone/src/app/api/bulk/route.ts +34 -0
- package/.next/standalone/src/app/api/chat/route.ts +85 -0
- package/.next/standalone/src/app/api/config/route.ts +30 -0
- package/.next/standalone/src/app/api/cortex/context/route.ts +78 -78
- package/.next/standalone/src/app/api/cortex/curation/assess/route.ts +27 -27
- package/.next/standalone/src/app/api/cortex/curation/publish/route.ts +23 -23
- package/.next/standalone/src/app/api/cortex/curation/refine/route.ts +23 -23
- package/.next/standalone/src/app/api/cortex/curation/review/route.ts +29 -29
- package/.next/standalone/src/app/api/cortex/curation/seed/route.ts +23 -23
- package/.next/standalone/src/app/api/cortex/export/route.ts +40 -40
- package/.next/standalone/src/app/api/cortex/federation/pending/route.ts +20 -20
- package/.next/standalone/src/app/api/cortex/federation/resolve/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/federation/search/route.ts +35 -35
- package/.next/standalone/src/app/api/cortex/federation/teach/route.ts +76 -76
- package/.next/standalone/src/app/api/cortex/graph/edges/route.ts +112 -112
- package/.next/standalone/src/app/api/cortex/graph/entities/[id]/route.ts +73 -73
- package/.next/standalone/src/app/api/cortex/graph/entities/route.ts +75 -75
- package/.next/standalone/src/app/api/cortex/graph/populate/route.ts +203 -203
- package/.next/standalone/src/app/api/cortex/import/route.ts +75 -75
- package/.next/standalone/src/app/api/cortex/import/status/route.ts +15 -15
- package/.next/standalone/src/app/api/cortex/ingest/bootstrap/route.ts +29 -29
- package/.next/standalone/src/app/api/cortex/ingest/status/route.ts +15 -15
- package/.next/standalone/src/app/api/cortex/knowledge/[id]/route.ts +91 -91
- package/.next/standalone/src/app/api/cortex/knowledge/route.ts +93 -93
- package/.next/standalone/src/app/api/cortex/lobes/[id]/route.ts +67 -67
- package/.next/standalone/src/app/api/cortex/lobes/route.ts +22 -22
- package/.next/standalone/src/app/api/cortex/lobes/share/route.ts +80 -80
- package/.next/standalone/src/app/api/cortex/marketplace/browse/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/marketplace/preview/route.ts +46 -46
- package/.next/standalone/src/app/api/cortex/mcp/call/route.ts +11 -11
- package/.next/standalone/src/app/api/cortex/mcp/tools/route.ts +6 -6
- package/.next/standalone/src/app/api/cortex/search/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/settings/route.ts +33 -33
- package/.next/standalone/src/app/api/cortex/status/route.ts +169 -169
- package/.next/standalone/src/app/api/cortex/timeline/route.ts +42 -42
- package/.next/standalone/src/app/api/cortex/usage/route.ts +31 -31
- package/.next/standalone/src/app/api/cortex/workspace/[id]/context/route.ts +41 -41
- package/.next/standalone/src/app/api/events/route.ts +40 -0
- package/.next/standalone/src/app/api/files/route.ts +187 -0
- package/.next/standalone/src/app/api/folders/route.ts +97 -0
- package/.next/standalone/src/app/api/network/connect-callback/route.ts +11 -0
- package/.next/standalone/src/app/api/network/connect-request/[id]/route.ts +11 -0
- package/.next/standalone/src/app/api/network/connect-request/route.ts +17 -0
- package/.next/standalone/src/app/api/network/discovered/route.ts +9 -0
- package/.next/standalone/src/app/api/network/handshake/route.ts +25 -0
- package/.next/standalone/src/app/api/network/health/route.ts +10 -0
- package/.next/standalone/src/app/api/network/identity/route.ts +15 -0
- package/.next/standalone/src/app/api/network/keys/[id]/route.ts +10 -0
- package/.next/standalone/src/app/api/network/keys/route.ts +15 -0
- package/.next/standalone/src/app/api/network/nodes/[id]/route.ts +15 -0
- package/.next/standalone/src/app/api/network/nodes/check/route.ts +9 -0
- package/.next/standalone/src/app/api/network/nodes/route.ts +15 -0
- package/.next/standalone/src/app/api/network/projects/route.ts +25 -0
- package/.next/standalone/src/app/api/network/proxy/[nodeId]/[...path]/route.ts +15 -0
- package/.next/standalone/src/app/api/network/search/route.ts +38 -0
- package/.next/standalone/src/app/api/network/sessions/[id]/messages/route.ts +36 -0
- package/.next/standalone/src/app/api/network/sessions/[id]/route.ts +34 -0
- package/.next/standalone/src/app/api/network/sessions/route.ts +43 -0
- package/.next/standalone/src/app/api/network/terminal/token/route.ts +10 -0
- package/.next/standalone/src/app/api/network/workspaces/[id]/route.ts +34 -0
- package/.next/standalone/src/app/api/network/workspaces/route.ts +61 -0
- package/.next/standalone/src/app/api/panes/[id]/route.ts +60 -0
- package/.next/standalone/src/app/api/panes/route.ts +39 -0
- package/.next/standalone/src/app/api/projects/route.ts +13 -0
- package/.next/standalone/src/app/api/search/route.ts +47 -0
- package/.next/standalone/src/app/api/sessions/[id]/chat/route.ts +120 -0
- package/.next/standalone/src/app/api/sessions/[id]/messages/route.ts +28 -0
- package/.next/standalone/src/app/api/sessions/[id]/route.ts +73 -0
- package/.next/standalone/src/app/api/sessions/route.ts +64 -0
- package/.next/standalone/src/app/api/sync/route.ts +24 -0
- package/.next/standalone/src/app/api/tags/route.ts +35 -0
- package/.next/standalone/src/app/api/tier/route.ts +16 -0
- package/.next/standalone/src/app/api/updates/route.ts +53 -0
- package/.next/standalone/src/app/api/whisper/route.ts +90 -0
- package/.next/standalone/src/app/api/workspaces/[id]/context/[key]/route.ts +39 -0
- package/.next/standalone/src/app/api/workspaces/[id]/context/route.ts +28 -0
- package/.next/standalone/src/app/api/workspaces/[id]/messages/[msgId]/route.ts +17 -0
- package/.next/standalone/src/app/api/workspaces/[id]/messages/route.ts +39 -0
- package/.next/standalone/src/app/api/workspaces/[id]/route.ts +47 -0
- package/.next/standalone/src/app/api/workspaces/[id]/sessions/route.ts +62 -0
- package/.next/standalone/src/app/api/workspaces/route.ts +79 -0
- package/.next/standalone/src/app/globals.css +85 -0
- package/.next/standalone/src/app/icon.png +0 -0
- package/.next/standalone/src/app/layout.tsx +33 -0
- package/.next/standalone/src/app/login/layout.tsx +7 -0
- package/.next/standalone/src/app/login/page.tsx +315 -0
- package/.next/standalone/src/app/m/layout.tsx +16 -0
- package/.next/standalone/src/app/m/page.tsx +118 -0
- package/.next/standalone/src/app/m/projects/page.tsx +64 -0
- package/.next/standalone/src/app/m/sessions/[id]/page.tsx +168 -0
- package/.next/standalone/src/app/m/sessions/page.tsx +177 -0
- package/.next/standalone/src/app/m/settings/page.tsx +230 -0
- package/.next/standalone/src/app/m/terminal/page.tsx +413 -0
- package/.next/standalone/src/app/vr/page.tsx +21 -0
- package/.next/standalone/src/app/vr/vr-app.tsx +163 -0
- package/.next/standalone/src/app/vr/vr-controls.tsx +139 -0
- package/.next/standalone/src/app/vr/vr-door.tsx +82 -0
- package/.next/standalone/src/app/vr/vr-environment.tsx +71 -0
- package/.next/standalone/src/app/vr/vr-gaze.tsx +89 -0
- package/.next/standalone/src/app/vr/vr-layout.ts +49 -0
- package/.next/standalone/src/app/vr/vr-lobby.tsx +97 -0
- package/.next/standalone/src/app/vr/vr-pane.tsx +195 -0
- package/.next/standalone/src/app/vr/vr-room.tsx +79 -0
- package/.next/standalone/src/app/vr/vr-terminal.tsx +303 -0
- package/.next/standalone/src/components/auth/totp-gate.tsx +183 -0
- package/.next/standalone/src/components/bus/activity-panel.tsx +261 -0
- package/.next/standalone/src/components/common/color-picker.tsx +35 -0
- package/.next/standalone/src/components/common/dev-directory-picker.tsx +339 -0
- package/.next/standalone/src/components/common/folder-picker.tsx +200 -0
- package/.next/standalone/src/components/common/tag-picker.tsx +190 -0
- package/.next/standalone/src/components/common/workspace-picker.tsx +113 -0
- package/.next/standalone/src/components/cortex/benchmark-tab.tsx +880 -0
- package/.next/standalone/src/components/cortex/constants.ts +29 -29
- package/.next/standalone/src/components/cortex/cortex-dashboard.tsx +304 -304
- package/.next/standalone/src/components/cortex/cortex-indicator.tsx +44 -44
- package/.next/standalone/src/components/cortex/cortex-panel.tsx +140 -140
- package/.next/standalone/src/components/cortex/cortex-settings.tsx +221 -221
- package/.next/standalone/src/components/cortex/curation-tab.tsx +810 -810
- package/.next/standalone/src/components/cortex/entity-detail.tsx +101 -101
- package/.next/standalone/src/components/cortex/entity-graph.tsx +382 -382
- package/.next/standalone/src/components/cortex/import-dialog.tsx +212 -212
- package/.next/standalone/src/components/cortex/injection-badge.tsx +72 -72
- package/.next/standalone/src/components/cortex/knowledge-card.tsx +109 -109
- package/.next/standalone/src/components/cortex/knowledge-tab.tsx +158 -158
- package/.next/standalone/src/components/cortex/lobe-settings.tsx +215 -215
- package/.next/standalone/src/components/cortex/marketplace-card.tsx +126 -126
- package/.next/standalone/src/components/cortex/marketplace-tab.tsx +113 -113
- package/.next/standalone/src/components/dashboard/activity-chart.tsx +41 -0
- package/.next/standalone/src/components/dashboard/model-usage-chart.tsx +61 -0
- package/.next/standalone/src/components/dashboard/recent-sessions.tsx +68 -0
- package/.next/standalone/src/components/dashboard/stats-cards.tsx +36 -0
- package/.next/standalone/src/components/files/file-explorer.tsx +703 -0
- package/.next/standalone/src/components/layout/providers.tsx +38 -0
- package/.next/standalone/src/components/layout/sidebar.tsx +170 -0
- package/.next/standalone/src/components/layout/tier-provider.tsx +53 -0
- package/.next/standalone/src/components/layout/update-banner.tsx +92 -0
- package/.next/standalone/src/components/mobile/bottom-nav.tsx +46 -0
- package/.next/standalone/src/components/mobile/mobile-chat-input.tsx +244 -0
- package/.next/standalone/src/components/mobile/mobile-header.tsx +44 -0
- package/.next/standalone/src/components/mobile/mobile-session-card.tsx +56 -0
- package/.next/standalone/src/components/mobile/mobile-terminal-input.tsx +71 -0
- package/.next/standalone/src/components/mobile/mobile-terminal-pane.tsx +300 -0
- package/.next/standalone/src/components/mobile/mobile-terminal-toolbar.tsx +53 -0
- package/.next/standalone/src/components/mobile/pull-to-refresh.tsx +82 -0
- package/.next/standalone/src/components/mobile/voice-input.tsx +53 -0
- package/.next/standalone/src/components/network/api-key-list.tsx +190 -0
- package/.next/standalone/src/components/network/connection-requests.tsx +94 -0
- package/.next/standalone/src/components/network/node-add-dialog.tsx +131 -0
- package/.next/standalone/src/components/network/node-badge.tsx +26 -0
- package/.next/standalone/src/components/network/node-list.tsx +207 -0
- package/.next/standalone/src/components/network/node-selector.tsx +49 -0
- package/.next/standalone/src/components/sessions/session-filters.tsx +116 -0
- package/.next/standalone/src/components/sessions/session-list.tsx +485 -0
- package/.next/standalone/src/components/terminal/terminal-pane.tsx +874 -0
- package/.next/standalone/src/components/viewer/chat-input.tsx +275 -0
- package/.next/standalone/src/components/viewer/message-renderer.tsx +551 -0
- package/.next/standalone/src/components/workspace/universe-cluster.tsx +131 -0
- package/.next/standalone/src/components/workspace/universe-orb.tsx +128 -0
- package/.next/standalone/src/components/workspace/universe-types.ts +22 -0
- package/.next/standalone/src/components/workspace/universe-utils.ts +11 -0
- package/.next/standalone/src/components/workspace/universe-view.tsx +397 -0
- package/.next/standalone/src/components/workspace/workspace-chooser.tsx +616 -0
- package/.next/standalone/src/hooks/use-benchmark.ts +71 -0
- package/.next/standalone/src/hooks/use-bus.ts +147 -0
- package/.next/standalone/src/hooks/use-idle-detection.ts +79 -0
- package/.next/standalone/src/hooks/use-network.ts +229 -0
- package/.next/standalone/src/hooks/use-sessions.ts +437 -0
- package/.next/standalone/src/hooks/use-speech-recognition.ts +113 -0
- package/.next/standalone/src/hooks/use-sse.ts +35 -0
- package/.next/standalone/src/hooks/use-tier.ts +39 -0
- package/.next/standalone/src/lib/agents.ts +70 -0
- package/.next/standalone/src/lib/aider/parser.ts +111 -0
- package/.next/standalone/src/lib/api.ts +19 -0
- package/.next/standalone/src/lib/auth.ts +47 -0
- package/.next/standalone/src/lib/claude/parser.ts +212 -0
- package/.next/standalone/src/lib/claude/stats.ts +204 -0
- package/.next/standalone/src/lib/codex/parser.ts +265 -0
- package/.next/standalone/src/lib/config.ts +115 -0
- package/.next/standalone/src/lib/cortex/benchmark.ts +67 -0
- package/.next/standalone/src/lib/cortex/config.ts +40 -40
- package/.next/standalone/src/lib/cortex/debug.ts +10 -10
- package/.next/standalone/src/lib/cortex/distillation/usage-store.ts +18 -18
- package/.next/standalone/src/lib/cortex/graph/resolver.ts +10 -10
- package/.next/standalone/src/lib/cortex/graph/types.ts +22 -22
- package/.next/standalone/src/lib/cortex/index.ts +56 -56
- package/.next/standalone/src/lib/cortex/ingestion/bootstrap.ts +14 -14
- package/.next/standalone/src/lib/cortex/knowledge/compat.ts +14 -14
- package/.next/standalone/src/lib/cortex/knowledge/contradiction.ts +10 -10
- package/.next/standalone/src/lib/cortex/knowledge/types.ts +67 -67
- package/.next/standalone/src/lib/cortex/lobes/config.ts +16 -16
- package/.next/standalone/src/lib/cortex/lobes/resolver.ts +8 -8
- package/.next/standalone/src/lib/cortex/lobes/shares.ts +14 -14
- package/.next/standalone/src/lib/cortex/mcp/server.ts +8 -8
- package/.next/standalone/src/lib/cortex/portability/exporter.ts +6 -6
- package/.next/standalone/src/lib/cortex/portability/importer.ts +10 -10
- package/.next/standalone/src/lib/cortex/retrieval/context-engine.ts +10 -10
- package/.next/standalone/src/lib/cortex/types.ts +39 -39
- package/.next/standalone/src/lib/cost-calculator.ts +48 -0
- package/.next/standalone/src/lib/db/init.ts +71 -0
- package/.next/standalone/src/lib/db/queries.ts +718 -0
- package/.next/standalone/src/lib/db/schema.ts +202 -0
- package/.next/standalone/src/lib/events/sse.ts +36 -0
- package/.next/standalone/src/lib/gemini/parser.ts +216 -0
- package/.next/standalone/src/lib/license.ts +56 -0
- package/.next/standalone/src/lib/pro.ts +31 -0
- package/.next/standalone/src/lib/sync/indexer.ts +429 -0
- package/.next/standalone/src/lib/sync/watcher.ts +64 -0
- package/.next/standalone/src/lib/teams.ts +31 -0
- package/.next/standalone/src/lib/telemetry.ts +75 -0
- package/.next/standalone/src/lib/terminal/server.ts +128 -0
- package/.next/standalone/src/lib/tier.ts +38 -0
- package/.next/standalone/src/lib/utils.ts +72 -0
- package/.next/standalone/src/middleware.ts +133 -0
- package/.next/standalone/src/types/claude.ts +208 -0
- package/.next/standalone/src/types/network.ts +61 -0
- package/.next/standalone/tests/setup.ts +8 -0
- package/.next/standalone/tsconfig.json +34 -34
- package/.next/standalone/vitest.config.ts +24 -0
- package/LICENSE +661 -661
- package/README.md +131 -131
- package/bin/cortex-hook.sh +62 -62
- package/bin/cortex-mcp.js +60 -60
- package/bin/fix-standalone-externals.js +79 -79
- package/bin/lib/auto-setup.js +110 -110
- package/bin/mdns-service.js +171 -171
- package/bin/postinstall.js +35 -35
- package/bin/setup-admin.js +195 -195
- package/bin/spaces-dev.js +247 -208
- package/bin/spaces-install.js +638 -599
- package/bin/spaces-reset-totp.js +50 -50
- package/bin/spaces-service.js +1020 -1020
- package/bin/spaces-setup.js +253 -253
- package/bin/spaces.js +788 -776
- package/bin/ssh-auth-keys.sh +68 -68
- package/bin/terminal-server.js +1807 -1683
- package/package.json +104 -104
- package/.next/standalone/.claude/settings.local.json +0 -55
- package/.next/standalone/.claude/spaces-env.json +0 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e921fdfc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_2230ad2d._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_5cf334fd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_db0abd0a._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_db2fec84._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_f4e57187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_40fa36ce._.js +0 -7
- package/.next/standalone/.next/static/chunks/003e7aa1adfe577d.js +0 -1
- package/.next/standalone/.next/static/chunks/158b52b84e647ac1.js +0 -5
- package/.next/standalone/.next/static/chunks/232d8aae4fefab70.js +0 -1
- package/.next/standalone/.next/static/chunks/5325351ef49cb65f.js +0 -1
- package/.next/standalone/.next/static/chunks/559735e598ca3cbb.js +0 -1
- package/.next/standalone/.next/static/chunks/5d5d7b0095dd52ae.js +0 -1
- package/.next/standalone/.next/static/chunks/898f380eba90427a.js +0 -1
- package/.next/standalone/.next/static/chunks/95339e55722bb4ca.js +0 -5
- package/.next/standalone/.next/static/chunks/9cd594813c539df9.js +0 -1
- package/.next/standalone/.next/static/chunks/c1a95aebf6725f64.css +0 -3
- package/.next/standalone/.next/static/chunks/c515eb77d9410aa0.js +0 -5
- package/.next/standalone/.next/static/chunks/d9ae203a7f123546.js +0 -5
- package/.next/standalone/.next/static/chunks/f9f2628207848ac2.js +0 -1
- package/.next/standalone/.spaces/cortex-context.md +0 -70
- package/.next/standalone/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64.node +0 -0
- /package/.next/standalone/.next/static/{BEY-sql3lQLouidpurSQf → PcpzUspSK8QDdwzAJz8br}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{BEY-sql3lQLouidpurSQf → PcpzUspSK8QDdwzAJz8br}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{BEY-sql3lQLouidpurSQf → PcpzUspSK8QDdwzAJz8br}/_ssgManifest.js +0 -0
- /package/.next/standalone/node_modules/@img/{sharp-win32-x64 → sharp-libvips-linux-x64}/versions.json +0 -0
|
@@ -1,1080 +1,1080 @@
|
|
|
1
|
-
# Cortex Lobes Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add knowledge compartmentalization ("lobes") to Cortex so workspaces control which knowledge sources they can pull from — open by default within a user, closed by default across users, with privacy, exclusions, and cross-user sharing.
|
|
6
|
-
|
|
7
|
-
**Architecture:** A new `src/lib/cortex/lobes/` module containing lobe config types, a resolver that computes accessible lobes for a workspace, and sharing logic. The ContextEngine's `computeSourceWeights()` is modified to use the lobe resolver instead of hardcoded layers. Lobe config is stored as a JSON column on the workspaces table. Cross-user shares live in the entity graph's SQLite database.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript, better-sqlite3 (entity graph DB), vitest
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-03-16-cortex-lobes-design.md`
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## File Structure
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
New files:
|
|
19
|
-
├── src/lib/cortex/lobes/config.ts — LobeConfig types and defaults
|
|
20
|
-
├── src/lib/cortex/lobes/resolver.ts — Resolve accessible lobes for a workspace
|
|
21
|
-
├── src/lib/cortex/lobes/shares.ts — Cross-user sharing (lobe_shares table)
|
|
22
|
-
├── src/lib/cortex/lobes/index.ts — Barrel export
|
|
23
|
-
├── src/app/api/cortex/lobes/route.ts — List lobes, update config
|
|
24
|
-
├── src/app/api/cortex/lobes/share/route.ts — Share management
|
|
25
|
-
├── src/components/cortex/lobe-settings.tsx — UI component for workspace settings
|
|
26
|
-
|
|
27
|
-
Test files:
|
|
28
|
-
├── tests/lib/cortex/lobes/resolver.test.ts
|
|
29
|
-
├── tests/lib/cortex/lobes/shares.test.ts
|
|
30
|
-
|
|
31
|
-
Modified files:
|
|
32
|
-
├── src/lib/db/schema.ts — Add lobe_config column to workspaces
|
|
33
|
-
├── src/lib/cortex/retrieval/context-engine.ts — Use lobe resolver for search scopes
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Chunk 1: Types, DB Migration, and Lobe Resolver
|
|
39
|
-
|
|
40
|
-
### Task 1: Lobe config types and defaults
|
|
41
|
-
|
|
42
|
-
**Files:**
|
|
43
|
-
- Create: `src/lib/cortex/lobes/config.ts`
|
|
44
|
-
|
|
45
|
-
- [ ] **Step 1: Create the types file**
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// src/lib/cortex/lobes/config.ts
|
|
49
|
-
|
|
50
|
-
export interface LobeSubscription {
|
|
51
|
-
type: 'workspace' | 'user' | 'tag' | 'team' | 'department' | 'organization';
|
|
52
|
-
id: string; // workspace ID, user entity ID, tag name, etc.
|
|
53
|
-
label: string; // display name
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface LobeConfig {
|
|
57
|
-
isPrivate: boolean;
|
|
58
|
-
excludedFrom: number[]; // workspace IDs blocked from accessing this lobe
|
|
59
|
-
subscriptions: LobeSubscription[];
|
|
60
|
-
tags: string[];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const DEFAULT_LOBE_CONFIG: LobeConfig = {
|
|
64
|
-
isPrivate: false,
|
|
65
|
-
excludedFrom: [],
|
|
66
|
-
subscriptions: [],
|
|
67
|
-
tags: [],
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export function parseLobeConfig(raw: string | null | undefined): LobeConfig {
|
|
71
|
-
if (!raw) return { ...DEFAULT_LOBE_CONFIG };
|
|
72
|
-
try {
|
|
73
|
-
const parsed = JSON.parse(raw);
|
|
74
|
-
return {
|
|
75
|
-
isPrivate: parsed.isPrivate ?? false,
|
|
76
|
-
excludedFrom: Array.isArray(parsed.excludedFrom) ? parsed.excludedFrom : [],
|
|
77
|
-
subscriptions: Array.isArray(parsed.subscriptions) ? parsed.subscriptions : [],
|
|
78
|
-
tags: Array.isArray(parsed.tags) ? parsed.tags : [],
|
|
79
|
-
};
|
|
80
|
-
} catch {
|
|
81
|
-
return { ...DEFAULT_LOBE_CONFIG };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function serializeLobeConfig(config: LobeConfig): string {
|
|
86
|
-
return JSON.stringify(config);
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
- [ ] **Step 2: Commit**
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
git add src/lib/cortex/lobes/config.ts
|
|
94
|
-
git commit -m "feat(cortex): add lobe config types and defaults"
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
### Task 2: Database migration — add lobe_config column
|
|
100
|
-
|
|
101
|
-
**Files:**
|
|
102
|
-
- Modify: `src/lib/db/schema.ts`
|
|
103
|
-
|
|
104
|
-
- [ ] **Step 1: Read schema.ts to find the migration section**
|
|
105
|
-
|
|
106
|
-
Read `src/lib/db/schema.ts`. Find the section with `addCol` calls (around line 120+). Add:
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
addCol('workspaces', 'lobe_config', "TEXT DEFAULT '{}'");
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
This adds a JSON text column to the existing workspaces table with an empty config default.
|
|
113
|
-
|
|
114
|
-
- [ ] **Step 2: Commit**
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
git add src/lib/db/schema.ts
|
|
118
|
-
git commit -m "feat(cortex): add lobe_config column to workspaces table"
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
### Task 3: Lobe resolver — compute accessible lobes for a workspace
|
|
124
|
-
|
|
125
|
-
**Files:**
|
|
126
|
-
- Create: `src/lib/cortex/lobes/resolver.ts`
|
|
127
|
-
- Create: `tests/lib/cortex/lobes/resolver.test.ts`
|
|
128
|
-
|
|
129
|
-
- [ ] **Step 1: Write failing tests**
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// tests/lib/cortex/lobes/resolver.test.ts
|
|
133
|
-
import { describe, it, expect } from 'vitest';
|
|
134
|
-
import { resolveLobes } from '@/lib/cortex/lobes/resolver';
|
|
135
|
-
import type { LobeConfig } from '@/lib/cortex/lobes/config';
|
|
136
|
-
import { DEFAULT_LOBE_CONFIG } from '@/lib/cortex/lobes/config';
|
|
137
|
-
|
|
138
|
-
// Minimal workspace shape for testing
|
|
139
|
-
interface TestWorkspace {
|
|
140
|
-
id: number;
|
|
141
|
-
name: string;
|
|
142
|
-
lobeConfig: LobeConfig;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
describe('resolveLobes', () => {
|
|
146
|
-
const workspaces: TestWorkspace[] = [
|
|
147
|
-
{ id: 1, name: 'Auth Service', lobeConfig: DEFAULT_LOBE_CONFIG },
|
|
148
|
-
{ id: 2, name: 'Frontend', lobeConfig: DEFAULT_LOBE_CONFIG },
|
|
149
|
-
{ id: 3, name: 'Private Project', lobeConfig: { ...DEFAULT_LOBE_CONFIG, isPrivate: true } },
|
|
150
|
-
{ id: 4, name: 'Excluded', lobeConfig: { ...DEFAULT_LOBE_CONFIG, excludedFrom: [1] } },
|
|
151
|
-
];
|
|
152
|
-
|
|
153
|
-
it('includes own workspace lobe', () => {
|
|
154
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
155
|
-
const keys = lobes.map(l => l.layerKey);
|
|
156
|
-
expect(keys).toContain('workspace/1');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('includes personal lobe', () => {
|
|
160
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
161
|
-
const keys = lobes.map(l => l.layerKey);
|
|
162
|
-
expect(keys).toContain('personal');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('includes other non-private workspaces by default', () => {
|
|
166
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
167
|
-
const keys = lobes.map(l => l.layerKey);
|
|
168
|
-
expect(keys).toContain('workspace/2');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('excludes private workspaces', () => {
|
|
172
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
173
|
-
const keys = lobes.map(l => l.layerKey);
|
|
174
|
-
expect(keys).not.toContain('workspace/3');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('excludes workspaces that exclude the requester', () => {
|
|
178
|
-
// Workspace 4 excludes workspace 1
|
|
179
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
180
|
-
const keys = lobes.map(l => l.layerKey);
|
|
181
|
-
expect(keys).not.toContain('workspace/4');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('includes team lobe by default', () => {
|
|
185
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
186
|
-
const keys = lobes.map(l => l.layerKey);
|
|
187
|
-
expect(keys).toContain('team');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('includes explicit subscriptions', () => {
|
|
191
|
-
const ws: TestWorkspace[] = [
|
|
192
|
-
{
|
|
193
|
-
id: 1, name: 'Main',
|
|
194
|
-
lobeConfig: {
|
|
195
|
-
...DEFAULT_LOBE_CONFIG,
|
|
196
|
-
subscriptions: [{ type: 'tag', id: 'infrastructure', label: 'Infrastructure' }],
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
];
|
|
200
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: ws });
|
|
201
|
-
const tags = lobes.filter(l => l.type === 'tag');
|
|
202
|
-
expect(tags).toHaveLength(1);
|
|
203
|
-
expect(tags[0].id).toBe('infrastructure');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('assigns lower weight to subscribed lobes vs inherited', () => {
|
|
207
|
-
const ws: TestWorkspace[] = [
|
|
208
|
-
{
|
|
209
|
-
id: 1, name: 'Main',
|
|
210
|
-
lobeConfig: {
|
|
211
|
-
...DEFAULT_LOBE_CONFIG,
|
|
212
|
-
subscriptions: [{ type: 'workspace', id: '99', label: 'Remote' }],
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
];
|
|
216
|
-
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: ws });
|
|
217
|
-
const own = lobes.find(l => l.layerKey === 'workspace/1');
|
|
218
|
-
const subscribed = lobes.find(l => l.layerKey === 'workspace/99');
|
|
219
|
-
expect(own!.baseWeight).toBeGreaterThan(subscribed!.baseWeight);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
- [ ] **Step 2: Run tests to verify they fail**
|
|
225
|
-
|
|
226
|
-
Run: `npx vitest run tests/lib/cortex/lobes/resolver.test.ts`
|
|
227
|
-
|
|
228
|
-
- [ ] **Step 3: Implement lobe resolver**
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
// src/lib/cortex/lobes/resolver.ts
|
|
232
|
-
import type { LobeConfig, LobeSubscription } from './config';
|
|
233
|
-
|
|
234
|
-
export interface ResolvedLobe {
|
|
235
|
-
layerKey: string; // LanceDB storage path (e.g., 'workspace/42', 'personal', 'team')
|
|
236
|
-
label: string; // display name
|
|
237
|
-
type: 'own' | 'personal' | 'workspace' | 'team' | 'department' | 'organization' | 'tag' | 'user';
|
|
238
|
-
id: string; // source identifier
|
|
239
|
-
baseWeight: number; // base retrieval weight (before graph proximity)
|
|
240
|
-
inherited: boolean; // true if auto-inherited, false if explicitly subscribed
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
interface WorkspaceInfo {
|
|
244
|
-
id: number;
|
|
245
|
-
name: string;
|
|
246
|
-
lobeConfig: LobeConfig;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
interface ResolveInput {
|
|
250
|
-
workspaceId: number;
|
|
251
|
-
allWorkspaces: WorkspaceInfo[];
|
|
252
|
-
userId?: string; // for personal lobe entity ID
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Resolve the list of accessible knowledge lobes for a workspace.
|
|
257
|
-
*
|
|
258
|
-
* Default behavior: own workspace + personal + all non-private sibling workspaces + team + org.
|
|
259
|
-
* Respects privacy, exclusions, and explicit subscriptions.
|
|
260
|
-
*/
|
|
261
|
-
export function resolveLobes(input: ResolveInput): ResolvedLobe[] {
|
|
262
|
-
const { workspaceId, allWorkspaces, userId } = input;
|
|
263
|
-
const lobes: ResolvedLobe[] = [];
|
|
264
|
-
|
|
265
|
-
const thisWs = allWorkspaces.find(w => w.id === workspaceId);
|
|
266
|
-
const thisConfig = thisWs?.lobeConfig;
|
|
267
|
-
|
|
268
|
-
// 1. Own workspace lobe (always included)
|
|
269
|
-
lobes.push({
|
|
270
|
-
layerKey: `workspace/${workspaceId}`,
|
|
271
|
-
label: thisWs?.name ?? 'This workspace',
|
|
272
|
-
type: 'own',
|
|
273
|
-
id: String(workspaceId),
|
|
274
|
-
baseWeight: 1.0,
|
|
275
|
-
inherited: true,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// 2. Personal lobe (always included)
|
|
279
|
-
lobes.push({
|
|
280
|
-
layerKey: 'personal',
|
|
281
|
-
label: 'Personal',
|
|
282
|
-
type: 'personal',
|
|
283
|
-
id: userId ?? 'personal',
|
|
284
|
-
baseWeight: 0.9,
|
|
285
|
-
inherited: true,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// 3. Other workspaces (same user) — included unless private or excluded
|
|
289
|
-
for (const ws of allWorkspaces) {
|
|
290
|
-
if (ws.id === workspaceId) continue;
|
|
291
|
-
|
|
292
|
-
// Skip if the other workspace is private
|
|
293
|
-
if (ws.lobeConfig.isPrivate) continue;
|
|
294
|
-
|
|
295
|
-
// Skip if the other workspace excludes this workspace
|
|
296
|
-
if (ws.lobeConfig.excludedFrom.includes(workspaceId)) continue;
|
|
297
|
-
|
|
298
|
-
lobes.push({
|
|
299
|
-
layerKey: `workspace/${ws.id}`,
|
|
300
|
-
label: ws.name,
|
|
301
|
-
type: 'workspace',
|
|
302
|
-
id: String(ws.id),
|
|
303
|
-
baseWeight: 0.6,
|
|
304
|
-
inherited: true,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// 4. Team / org inherited lobes
|
|
309
|
-
lobes.push({
|
|
310
|
-
layerKey: 'team',
|
|
311
|
-
label: 'Team',
|
|
312
|
-
type: 'team',
|
|
313
|
-
id: 'team',
|
|
314
|
-
baseWeight: 0.5,
|
|
315
|
-
inherited: true,
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// 5. Explicit subscriptions from this workspace's config
|
|
319
|
-
if (thisConfig?.subscriptions) {
|
|
320
|
-
for (const sub of thisConfig.subscriptions) {
|
|
321
|
-
// Avoid duplicates
|
|
322
|
-
const layerKey = sub.type === 'workspace' ? `workspace/${sub.id}`
|
|
323
|
-
: sub.type === 'tag' ? `tag/${sub.id}`
|
|
324
|
-
: sub.type === 'team' ? `team/${sub.id}`
|
|
325
|
-
: sub.type === 'user' ? `user/${sub.id}`
|
|
326
|
-
: sub.id;
|
|
327
|
-
|
|
328
|
-
if (lobes.some(l => l.layerKey === layerKey)) continue;
|
|
329
|
-
|
|
330
|
-
lobes.push({
|
|
331
|
-
layerKey,
|
|
332
|
-
label: sub.label,
|
|
333
|
-
type: sub.type as ResolvedLobe['type'],
|
|
334
|
-
id: sub.id,
|
|
335
|
-
baseWeight: 0.4, // subscribed = lower weight than inherited
|
|
336
|
-
inherited: false,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return lobes;
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
- [ ] **Step 4: Run tests to verify they pass**
|
|
346
|
-
|
|
347
|
-
Run: `npx vitest run tests/lib/cortex/lobes/resolver.test.ts`
|
|
348
|
-
Expected: PASS (8 tests)
|
|
349
|
-
|
|
350
|
-
- [ ] **Step 5: Commit**
|
|
351
|
-
|
|
352
|
-
```bash
|
|
353
|
-
git add src/lib/cortex/lobes/resolver.ts tests/lib/cortex/lobes/resolver.test.ts
|
|
354
|
-
git commit -m "feat(cortex): add lobe resolver for workspace knowledge scoping"
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
---
|
|
358
|
-
|
|
359
|
-
## Chunk 2: Cross-User Sharing and Context Engine Integration
|
|
360
|
-
|
|
361
|
-
### Task 4: Cross-user lobe sharing
|
|
362
|
-
|
|
363
|
-
**Files:**
|
|
364
|
-
- Create: `src/lib/cortex/lobes/shares.ts`
|
|
365
|
-
- Create: `tests/lib/cortex/lobes/shares.test.ts`
|
|
366
|
-
|
|
367
|
-
- [ ] **Step 1: Write failing tests**
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
// tests/lib/cortex/lobes/shares.test.ts
|
|
371
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
372
|
-
import fs from 'fs';
|
|
373
|
-
import path from 'path';
|
|
374
|
-
import os from 'os';
|
|
375
|
-
import Database from 'better-sqlite3';
|
|
376
|
-
import { LobeShareStore } from '@/lib/cortex/lobes/shares';
|
|
377
|
-
|
|
378
|
-
describe('LobeShareStore', () => {
|
|
379
|
-
let tmpDir: string;
|
|
380
|
-
let store: LobeShareStore;
|
|
381
|
-
|
|
382
|
-
beforeEach(() => {
|
|
383
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lobe-shares-'));
|
|
384
|
-
const db = new Database(path.join(tmpDir, 'test.db'));
|
|
385
|
-
store = new LobeShareStore(db);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
afterEach(() => {
|
|
389
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('creates a share', () => {
|
|
393
|
-
const share = store.share({
|
|
394
|
-
ownerUserId: 'person-alice',
|
|
395
|
-
ownerWorkspaceId: 1,
|
|
396
|
-
ownerLobeName: 'Auth Service',
|
|
397
|
-
sharedWithUserId: 'person-bob',
|
|
398
|
-
});
|
|
399
|
-
expect(share.id).toBeDefined();
|
|
400
|
-
expect(share.accepted).toBe(false);
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it('accepts a share', () => {
|
|
404
|
-
const share = store.share({
|
|
405
|
-
ownerUserId: 'person-alice',
|
|
406
|
-
ownerWorkspaceId: 1,
|
|
407
|
-
ownerLobeName: 'Auth Service',
|
|
408
|
-
sharedWithUserId: 'person-bob',
|
|
409
|
-
});
|
|
410
|
-
store.accept(share.id);
|
|
411
|
-
const updated = store.getShare(share.id);
|
|
412
|
-
expect(updated!.accepted).toBe(true);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it('lists incoming shares for a user', () => {
|
|
416
|
-
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
417
|
-
store.share({ ownerUserId: 'person-charlie', ownerWorkspaceId: 2, ownerLobeName: 'WS2', sharedWithUserId: 'person-bob' });
|
|
418
|
-
const incoming = store.listIncoming('person-bob');
|
|
419
|
-
expect(incoming).toHaveLength(2);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('lists outgoing shares for a user', () => {
|
|
423
|
-
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
424
|
-
const outgoing = store.listOutgoing('person-alice');
|
|
425
|
-
expect(outgoing).toHaveLength(1);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('revokes a share', () => {
|
|
429
|
-
const share = store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
430
|
-
store.revoke(share.id);
|
|
431
|
-
expect(store.getShare(share.id)).toBeNull();
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it('prevents duplicate shares', () => {
|
|
435
|
-
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
436
|
-
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
437
|
-
const incoming = store.listIncoming('person-bob');
|
|
438
|
-
expect(incoming).toHaveLength(1);
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
- [ ] **Step 2: Implement share store**
|
|
444
|
-
|
|
445
|
-
```typescript
|
|
446
|
-
// src/lib/cortex/lobes/shares.ts
|
|
447
|
-
import type Database from 'better-sqlite3';
|
|
448
|
-
|
|
449
|
-
export interface LobeShare {
|
|
450
|
-
id: string;
|
|
451
|
-
ownerUserId: string;
|
|
452
|
-
ownerWorkspaceId: number;
|
|
453
|
-
ownerLobeName: string;
|
|
454
|
-
sharedWithUserId: string;
|
|
455
|
-
accepted: boolean;
|
|
456
|
-
created: string;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
interface ShareInput {
|
|
460
|
-
ownerUserId: string;
|
|
461
|
-
ownerWorkspaceId: number;
|
|
462
|
-
ownerLobeName: string;
|
|
463
|
-
sharedWithUserId: string;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
export class LobeShareStore {
|
|
467
|
-
private db: InstanceType<typeof Database>;
|
|
468
|
-
|
|
469
|
-
constructor(db: InstanceType<typeof Database>) {
|
|
470
|
-
this.db = db;
|
|
471
|
-
this.db.exec(`
|
|
472
|
-
CREATE TABLE IF NOT EXISTS lobe_shares (
|
|
473
|
-
id TEXT PRIMARY KEY,
|
|
474
|
-
owner_user_id TEXT NOT NULL,
|
|
475
|
-
owner_workspace_id INTEGER NOT NULL,
|
|
476
|
-
owner_lobe_name TEXT NOT NULL,
|
|
477
|
-
shared_with_user_id TEXT NOT NULL,
|
|
478
|
-
accepted INTEGER DEFAULT 0,
|
|
479
|
-
created TEXT NOT NULL,
|
|
480
|
-
UNIQUE(owner_user_id, owner_workspace_id, shared_with_user_id)
|
|
481
|
-
);
|
|
482
|
-
CREATE INDEX IF NOT EXISTS idx_lobe_shares_recipient ON lobe_shares(shared_with_user_id);
|
|
483
|
-
CREATE INDEX IF NOT EXISTS idx_lobe_shares_owner ON lobe_shares(owner_user_id);
|
|
484
|
-
`);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
share(input: ShareInput): LobeShare {
|
|
488
|
-
const id = crypto.randomUUID();
|
|
489
|
-
const now = new Date().toISOString();
|
|
490
|
-
|
|
491
|
-
this.db.prepare(`
|
|
492
|
-
INSERT INTO lobe_shares (id, owner_user_id, owner_workspace_id, owner_lobe_name, shared_with_user_id, created)
|
|
493
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
494
|
-
ON CONFLICT(owner_user_id, owner_workspace_id, shared_with_user_id) DO NOTHING
|
|
495
|
-
`).run(id, input.ownerUserId, input.ownerWorkspaceId, input.ownerLobeName, input.sharedWithUserId, now);
|
|
496
|
-
|
|
497
|
-
// Return existing if duplicate
|
|
498
|
-
const existing = this.db.prepare(
|
|
499
|
-
'SELECT * FROM lobe_shares WHERE owner_user_id = ? AND owner_workspace_id = ? AND shared_with_user_id = ?'
|
|
500
|
-
).get(input.ownerUserId, input.ownerWorkspaceId, input.sharedWithUserId) as any;
|
|
501
|
-
|
|
502
|
-
return this.rowToShare(existing);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
accept(id: string): void {
|
|
506
|
-
this.db.prepare('UPDATE lobe_shares SET accepted = 1 WHERE id = ?').run(id);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
revoke(id: string): void {
|
|
510
|
-
this.db.prepare('DELETE FROM lobe_shares WHERE id = ?').run(id);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
getShare(id: string): LobeShare | null {
|
|
514
|
-
const row = this.db.prepare('SELECT * FROM lobe_shares WHERE id = ?').get(id) as any;
|
|
515
|
-
return row ? this.rowToShare(row) : null;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
listIncoming(userId: string): LobeShare[] {
|
|
519
|
-
const rows = this.db.prepare(
|
|
520
|
-
'SELECT * FROM lobe_shares WHERE shared_with_user_id = ? ORDER BY created DESC'
|
|
521
|
-
).all(userId) as any[];
|
|
522
|
-
return rows.map(r => this.rowToShare(r));
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
listOutgoing(userId: string): LobeShare[] {
|
|
526
|
-
const rows = this.db.prepare(
|
|
527
|
-
'SELECT * FROM lobe_shares WHERE owner_user_id = ? ORDER BY created DESC'
|
|
528
|
-
).all(userId) as any[];
|
|
529
|
-
return rows.map(r => this.rowToShare(r));
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
listAcceptedForUser(userId: string): LobeShare[] {
|
|
533
|
-
const rows = this.db.prepare(
|
|
534
|
-
'SELECT * FROM lobe_shares WHERE shared_with_user_id = ? AND accepted = 1'
|
|
535
|
-
).all(userId) as any[];
|
|
536
|
-
return rows.map(r => this.rowToShare(r));
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
private rowToShare(row: any): LobeShare {
|
|
540
|
-
return {
|
|
541
|
-
id: row.id,
|
|
542
|
-
ownerUserId: row.owner_user_id,
|
|
543
|
-
ownerWorkspaceId: row.owner_workspace_id,
|
|
544
|
-
ownerLobeName: row.owner_lobe_name,
|
|
545
|
-
sharedWithUserId: row.shared_with_user_id,
|
|
546
|
-
accepted: row.accepted === 1,
|
|
547
|
-
created: row.created,
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
- [ ] **Step 3: Run tests, commit**
|
|
554
|
-
|
|
555
|
-
Run: `npx vitest run tests/lib/cortex/lobes/shares.test.ts`
|
|
556
|
-
|
|
557
|
-
```bash
|
|
558
|
-
git commit -m "feat(cortex): add cross-user lobe sharing with handshake"
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
### Task 5: Integrate lobe resolver with Context Assembly Engine
|
|
564
|
-
|
|
565
|
-
**Files:**
|
|
566
|
-
- Modify: `src/lib/cortex/retrieval/context-engine.ts`
|
|
567
|
-
|
|
568
|
-
- [ ] **Step 1: Read context-engine.ts**
|
|
569
|
-
|
|
570
|
-
Read the file. Find `computeSourceWeights()` — the private method that returns hardcoded layer definitions.
|
|
571
|
-
|
|
572
|
-
- [ ] **Step 2: Add optional lobe-aware scope computation**
|
|
573
|
-
|
|
574
|
-
Add to `ContextEngineDeps`:
|
|
575
|
-
```typescript
|
|
576
|
-
resolvedLobes?: ResolvedLobe[]; // pre-computed accessible lobes for this workspace
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
Import:
|
|
580
|
-
```typescript
|
|
581
|
-
import type { ResolvedLobe } from '../lobes/resolver';
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
Modify `computeSourceWeights()` to use resolved lobes when available:
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
private computeSourceWeights(
|
|
588
|
-
intent: IntentResult,
|
|
589
|
-
workspaceId: number | null,
|
|
590
|
-
): SourceConfig[] {
|
|
591
|
-
// If lobes are provided, use them instead of hardcoded layers
|
|
592
|
-
if (this.deps.resolvedLobes && this.deps.resolvedLobes.length > 0) {
|
|
593
|
-
return this.deps.resolvedLobes.map(lobe => {
|
|
594
|
-
const graphProximity = lobe.baseWeight; // lobes already have base weights
|
|
595
|
-
|
|
596
|
-
const weight = computeScopeWeight({
|
|
597
|
-
graphProximity,
|
|
598
|
-
scopeLevel: lobe.type === 'personal' ? 'personal'
|
|
599
|
-
: lobe.type === 'team' || lobe.type === 'department' ? 'team'
|
|
600
|
-
: lobe.type === 'organization' ? 'organization'
|
|
601
|
-
: 'team',
|
|
602
|
-
intentBiases: intent.biases,
|
|
603
|
-
authorityFactor: 1.0,
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
return {
|
|
607
|
-
layerKey: lobe.layerKey,
|
|
608
|
-
weight,
|
|
609
|
-
limit: Math.max(3, Math.round(weight * 10)),
|
|
610
|
-
};
|
|
611
|
-
}).sort((a, b) => b.weight - a.weight);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Fallback: hardcoded layers (backward compat when no lobe config)
|
|
615
|
-
const layerDefs = [
|
|
616
|
-
// ... existing code unchanged ...
|
|
617
|
-
];
|
|
618
|
-
// ... rest of existing method unchanged ...
|
|
619
|
-
}
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
- [ ] **Step 3: Run existing context-engine tests to verify no regressions**
|
|
623
|
-
|
|
624
|
-
Run: `npx vitest run tests/lib/cortex/retrieval/context-engine.test.ts`
|
|
625
|
-
|
|
626
|
-
- [ ] **Step 4: Commit**
|
|
627
|
-
|
|
628
|
-
```bash
|
|
629
|
-
git add src/lib/cortex/retrieval/context-engine.ts
|
|
630
|
-
git commit -m "feat(cortex): integrate lobe resolver into context assembly engine"
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
---
|
|
634
|
-
|
|
635
|
-
## Chunk 3: API Routes and UI
|
|
636
|
-
|
|
637
|
-
### Task 6: Lobe API endpoints
|
|
638
|
-
|
|
639
|
-
**Files:**
|
|
640
|
-
- Create: `src/app/api/cortex/lobes/route.ts`
|
|
641
|
-
- Create: `src/app/api/cortex/lobes/[id]/route.ts`
|
|
642
|
-
- Create: `src/app/api/cortex/lobes/share/route.ts`
|
|
643
|
-
|
|
644
|
-
- [ ] **Step 1: Create main lobes endpoint**
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
// src/app/api/cortex/lobes/route.ts
|
|
648
|
-
import { NextResponse } from 'next/server';
|
|
649
|
-
import type { NextRequest } from 'next/server';
|
|
650
|
-
import { getAuthUser, withUser } from '@/lib/auth';
|
|
651
|
-
import { getDb } from '@/lib/db';
|
|
652
|
-
import { parseLobeConfig } from '@/lib/cortex/lobes/config';
|
|
653
|
-
|
|
654
|
-
export async function GET(request: NextRequest) {
|
|
655
|
-
const user = getAuthUser(request);
|
|
656
|
-
return withUser(user, async () => {
|
|
657
|
-
const db = getDb();
|
|
658
|
-
const workspaces = db.prepare(
|
|
659
|
-
'SELECT id, name, color, lobe_config FROM workspaces ORDER BY name'
|
|
660
|
-
).all() as any[];
|
|
661
|
-
|
|
662
|
-
const lobes = workspaces.map(ws => ({
|
|
663
|
-
workspaceId: ws.id,
|
|
664
|
-
name: ws.name,
|
|
665
|
-
color: ws.color,
|
|
666
|
-
config: parseLobeConfig(ws.lobe_config),
|
|
667
|
-
}));
|
|
668
|
-
|
|
669
|
-
return NextResponse.json({ lobes });
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
- [ ] **Step 2: Create single workspace lobe config endpoint**
|
|
675
|
-
|
|
676
|
-
```typescript
|
|
677
|
-
// src/app/api/cortex/lobes/[id]/route.ts
|
|
678
|
-
import { NextResponse } from 'next/server';
|
|
679
|
-
import type { NextRequest } from 'next/server';
|
|
680
|
-
import { getAuthUser, withUser } from '@/lib/auth';
|
|
681
|
-
import { getDb } from '@/lib/db';
|
|
682
|
-
import { parseLobeConfig, serializeLobeConfig } from '@/lib/cortex/lobes/config';
|
|
683
|
-
import type { LobeConfig } from '@/lib/cortex/lobes/config';
|
|
684
|
-
|
|
685
|
-
export async function GET(
|
|
686
|
-
request: NextRequest,
|
|
687
|
-
{ params }: { params: Promise<{ id: string }> },
|
|
688
|
-
) {
|
|
689
|
-
const { id } = await params;
|
|
690
|
-
const user = getAuthUser(request);
|
|
691
|
-
return withUser(user, async () => {
|
|
692
|
-
const db = getDb();
|
|
693
|
-
const ws = db.prepare('SELECT id, name, lobe_config FROM workspaces WHERE id = ?').get(Number(id)) as any;
|
|
694
|
-
if (!ws) return NextResponse.json({ error: 'Workspace not found' }, { status: 404 });
|
|
695
|
-
return NextResponse.json({ workspaceId: ws.id, name: ws.name, config: parseLobeConfig(ws.lobe_config) });
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
export async function PUT(
|
|
700
|
-
request: NextRequest,
|
|
701
|
-
{ params }: { params: Promise<{ id: string }> },
|
|
702
|
-
) {
|
|
703
|
-
const { id } = await params;
|
|
704
|
-
const user = getAuthUser(request);
|
|
705
|
-
return withUser(user, async () => {
|
|
706
|
-
const db = getDb();
|
|
707
|
-
const body = await request.json() as Partial<LobeConfig>;
|
|
708
|
-
|
|
709
|
-
const ws = db.prepare('SELECT lobe_config FROM workspaces WHERE id = ?').get(Number(id)) as any;
|
|
710
|
-
if (!ws) return NextResponse.json({ error: 'Workspace not found' }, { status: 404 });
|
|
711
|
-
|
|
712
|
-
const current = parseLobeConfig(ws.lobe_config);
|
|
713
|
-
const updated: LobeConfig = {
|
|
714
|
-
isPrivate: body.isPrivate ?? current.isPrivate,
|
|
715
|
-
excludedFrom: body.excludedFrom ?? current.excludedFrom,
|
|
716
|
-
subscriptions: body.subscriptions ?? current.subscriptions,
|
|
717
|
-
tags: body.tags ?? current.tags,
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
db.prepare('UPDATE workspaces SET lobe_config = ? WHERE id = ?').run(serializeLobeConfig(updated), Number(id));
|
|
721
|
-
return NextResponse.json({ config: updated });
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
- [ ] **Step 3: Create share endpoint**
|
|
727
|
-
|
|
728
|
-
```typescript
|
|
729
|
-
// src/app/api/cortex/lobes/share/route.ts
|
|
730
|
-
import { NextResponse } from 'next/server';
|
|
731
|
-
import type { NextRequest } from 'next/server';
|
|
732
|
-
import { getAuthUser, withUser } from '@/lib/auth';
|
|
733
|
-
import { getCortex, isCortexAvailable } from '@/lib/cortex';
|
|
734
|
-
import { LobeShareStore } from '@/lib/cortex/lobes/shares';
|
|
735
|
-
import { slugify } from '@/lib/cortex/graph/types';
|
|
736
|
-
|
|
737
|
-
export async function GET(request: NextRequest) {
|
|
738
|
-
const user = getAuthUser(request);
|
|
739
|
-
return withUser(user, async () => {
|
|
740
|
-
if (!isCortexAvailable()) return NextResponse.json({ incoming: [], outgoing: [] });
|
|
741
|
-
const cortex = await getCortex();
|
|
742
|
-
if (!cortex) return NextResponse.json({ incoming: [], outgoing: [] });
|
|
743
|
-
|
|
744
|
-
const shareStore = new LobeShareStore(cortex.graph['db']);
|
|
745
|
-
const userId = `person-${slugify(user)}`;
|
|
746
|
-
|
|
747
|
-
return NextResponse.json({
|
|
748
|
-
incoming: shareStore.listIncoming(userId),
|
|
749
|
-
outgoing: shareStore.listOutgoing(userId),
|
|
750
|
-
});
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
export async function POST(request: NextRequest) {
|
|
755
|
-
const user = getAuthUser(request);
|
|
756
|
-
return withUser(user, async () => {
|
|
757
|
-
if (!isCortexAvailable()) return NextResponse.json({ error: 'Cortex unavailable' }, { status: 403 });
|
|
758
|
-
const cortex = await getCortex();
|
|
759
|
-
if (!cortex) return NextResponse.json({ error: 'Cortex unavailable' }, { status: 500 });
|
|
760
|
-
|
|
761
|
-
const body = await request.json();
|
|
762
|
-
const { action, shareId, workspaceId, lobeName, sharedWithUserId } = body;
|
|
763
|
-
const shareStore = new LobeShareStore(cortex.graph['db']);
|
|
764
|
-
const userId = `person-${slugify(user)}`;
|
|
765
|
-
|
|
766
|
-
if (action === 'share') {
|
|
767
|
-
const share = shareStore.share({
|
|
768
|
-
ownerUserId: userId,
|
|
769
|
-
ownerWorkspaceId: workspaceId,
|
|
770
|
-
ownerLobeName: lobeName,
|
|
771
|
-
sharedWithUserId,
|
|
772
|
-
});
|
|
773
|
-
return NextResponse.json({ share }, { status: 201 });
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (action === 'accept') {
|
|
777
|
-
shareStore.accept(shareId);
|
|
778
|
-
return NextResponse.json({ accepted: true });
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (action === 'revoke' || action === 'decline') {
|
|
782
|
-
shareStore.revoke(shareId);
|
|
783
|
-
return NextResponse.json({ revoked: true });
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
- [ ] **Step 4: Commit**
|
|
792
|
-
|
|
793
|
-
```bash
|
|
794
|
-
git add src/app/api/cortex/lobes/
|
|
795
|
-
git commit -m "feat(cortex): add lobe API endpoints for config and sharing"
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
---
|
|
799
|
-
|
|
800
|
-
### Task 7: Lobe settings UI component
|
|
801
|
-
|
|
802
|
-
**Files:**
|
|
803
|
-
- Create: `src/components/cortex/lobe-settings.tsx`
|
|
804
|
-
|
|
805
|
-
- [ ] **Step 1: Create the component**
|
|
806
|
-
|
|
807
|
-
A settings panel showing the workspace's knowledge sources with toggles, tags, and subscription management.
|
|
808
|
-
|
|
809
|
-
```typescript
|
|
810
|
-
'use client';
|
|
811
|
-
|
|
812
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
813
|
-
import { Shield, ShieldOff, Tag, Plus, X, Users } from 'lucide-react';
|
|
814
|
-
import { api } from '@/lib/api';
|
|
815
|
-
import type { LobeConfig, LobeSubscription } from '@/lib/cortex/lobes/config';
|
|
816
|
-
|
|
817
|
-
interface LobeSettingsProps {
|
|
818
|
-
workspaceId: number;
|
|
819
|
-
workspaceName: string;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
export function LobeSettings({ workspaceId, workspaceName }: LobeSettingsProps) {
|
|
823
|
-
const [config, setConfig] = useState<LobeConfig | null>(null);
|
|
824
|
-
const [allLobes, setAllLobes] = useState<any[]>([]);
|
|
825
|
-
const [newTag, setNewTag] = useState('');
|
|
826
|
-
const [saving, setSaving] = useState(false);
|
|
827
|
-
|
|
828
|
-
const fetchConfig = useCallback(async () => {
|
|
829
|
-
const res = await fetch(api(`/api/cortex/lobes/${workspaceId}`));
|
|
830
|
-
if (res.ok) {
|
|
831
|
-
const data = await res.json();
|
|
832
|
-
setConfig(data.config);
|
|
833
|
-
}
|
|
834
|
-
}, [workspaceId]);
|
|
835
|
-
|
|
836
|
-
const fetchAllLobes = useCallback(async () => {
|
|
837
|
-
const res = await fetch(api('/api/cortex/lobes'));
|
|
838
|
-
if (res.ok) {
|
|
839
|
-
const data = await res.json();
|
|
840
|
-
setAllLobes(data.lobes || []);
|
|
841
|
-
}
|
|
842
|
-
}, []);
|
|
843
|
-
|
|
844
|
-
useEffect(() => { fetchConfig(); fetchAllLobes(); }, [fetchConfig, fetchAllLobes]);
|
|
845
|
-
|
|
846
|
-
const save = async (updates: Partial<LobeConfig>) => {
|
|
847
|
-
setSaving(true);
|
|
848
|
-
await fetch(api(`/api/cortex/lobes/${workspaceId}`), {
|
|
849
|
-
method: 'PUT',
|
|
850
|
-
headers: { 'Content-Type': 'application/json' },
|
|
851
|
-
body: JSON.stringify(updates),
|
|
852
|
-
});
|
|
853
|
-
await fetchConfig();
|
|
854
|
-
setSaving(false);
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
if (!config) return <div className="text-gray-500 text-sm p-4">Loading...</div>;
|
|
858
|
-
|
|
859
|
-
const activeSourceCount = allLobes.filter(l =>
|
|
860
|
-
l.workspaceId !== workspaceId && !l.config.isPrivate && !l.config.excludedFrom?.includes(workspaceId)
|
|
861
|
-
).length + 2; // +2 for personal + team
|
|
862
|
-
|
|
863
|
-
return (
|
|
864
|
-
<div className="space-y-6">
|
|
865
|
-
<div>
|
|
866
|
-
<h3 className="text-sm font-medium text-gray-200 mb-1">Knowledge Lobes</h3>
|
|
867
|
-
<p className="text-xs text-gray-500">
|
|
868
|
-
This workspace draws from {activeSourceCount} lobes.
|
|
869
|
-
{config.isPrivate && ' This lobe is private — other workspaces cannot access its knowledge.'}
|
|
870
|
-
</p>
|
|
871
|
-
</div>
|
|
872
|
-
|
|
873
|
-
{/* Privacy toggle */}
|
|
874
|
-
<div className="flex items-center justify-between py-3 border-t border-white/5">
|
|
875
|
-
<div className="flex items-center gap-2">
|
|
876
|
-
{config.isPrivate ? <ShieldOff className="w-4 h-4 text-red-400" /> : <Shield className="w-4 h-4 text-green-400" />}
|
|
877
|
-
<div>
|
|
878
|
-
<div className="text-sm text-gray-200">Private lobe</div>
|
|
879
|
-
<div className="text-[10px] text-gray-500">Other workspaces cannot access this knowledge</div>
|
|
880
|
-
</div>
|
|
881
|
-
</div>
|
|
882
|
-
<button
|
|
883
|
-
onClick={() => save({ isPrivate: !config.isPrivate })}
|
|
884
|
-
disabled={saving}
|
|
885
|
-
className={`px-3 py-1 text-xs rounded ${config.isPrivate ? 'bg-red-500/20 text-red-400' : 'bg-white/5 text-gray-400'}`}
|
|
886
|
-
>
|
|
887
|
-
{config.isPrivate ? 'Private' : 'Open'}
|
|
888
|
-
</button>
|
|
889
|
-
</div>
|
|
890
|
-
|
|
891
|
-
{/* Tags */}
|
|
892
|
-
<div className="border-t border-white/5 pt-3">
|
|
893
|
-
<div className="flex items-center gap-2 mb-2">
|
|
894
|
-
<Tag className="w-3.5 h-3.5 text-gray-500" />
|
|
895
|
-
<span className="text-xs text-gray-400">Tags</span>
|
|
896
|
-
</div>
|
|
897
|
-
<div className="flex flex-wrap gap-1.5 mb-2">
|
|
898
|
-
{config.tags.map(tag => (
|
|
899
|
-
<span key={tag} className="flex items-center gap-1 text-[11px] bg-purple-500/10 text-purple-400 px-2 py-0.5 rounded">
|
|
900
|
-
{tag}
|
|
901
|
-
<button onClick={() => save({ tags: config.tags.filter(t => t !== tag) })} className="hover:text-white">
|
|
902
|
-
<X className="w-2.5 h-2.5" />
|
|
903
|
-
</button>
|
|
904
|
-
</span>
|
|
905
|
-
))}
|
|
906
|
-
</div>
|
|
907
|
-
<div className="flex gap-1.5">
|
|
908
|
-
<input
|
|
909
|
-
value={newTag}
|
|
910
|
-
onChange={e => setNewTag(e.target.value)}
|
|
911
|
-
onKeyDown={e => {
|
|
912
|
-
if (e.key === 'Enter' && newTag.trim()) {
|
|
913
|
-
save({ tags: [...config.tags, newTag.trim()] });
|
|
914
|
-
setNewTag('');
|
|
915
|
-
}
|
|
916
|
-
}}
|
|
917
|
-
placeholder="Add tag..."
|
|
918
|
-
className="flex-1 px-2 py-1 text-xs bg-white/5 border border-white/10 rounded text-gray-300 focus:outline-none focus:border-purple-500/50"
|
|
919
|
-
/>
|
|
920
|
-
</div>
|
|
921
|
-
</div>
|
|
922
|
-
|
|
923
|
-
{/* Subscriptions */}
|
|
924
|
-
<div className="border-t border-white/5 pt-3">
|
|
925
|
-
<div className="flex items-center gap-2 mb-2">
|
|
926
|
-
<Plus className="w-3.5 h-3.5 text-gray-500" />
|
|
927
|
-
<span className="text-xs text-gray-400">Additional sources</span>
|
|
928
|
-
</div>
|
|
929
|
-
{config.subscriptions.length === 0 ? (
|
|
930
|
-
<p className="text-[11px] text-gray-600">No additional subscriptions. Using defaults.</p>
|
|
931
|
-
) : (
|
|
932
|
-
<div className="space-y-1">
|
|
933
|
-
{config.subscriptions.map((sub, i) => (
|
|
934
|
-
<div key={i} className="flex items-center justify-between text-xs bg-white/[0.02] rounded px-2 py-1.5">
|
|
935
|
-
<span className="text-gray-300">{sub.label} <span className="text-gray-600">({sub.type})</span></span>
|
|
936
|
-
<button
|
|
937
|
-
onClick={() => save({ subscriptions: config.subscriptions.filter((_, j) => j !== i) })}
|
|
938
|
-
className="text-gray-600 hover:text-red-400"
|
|
939
|
-
>
|
|
940
|
-
<X className="w-3 h-3" />
|
|
941
|
-
</button>
|
|
942
|
-
</div>
|
|
943
|
-
))}
|
|
944
|
-
</div>
|
|
945
|
-
)}
|
|
946
|
-
</div>
|
|
947
|
-
|
|
948
|
-
{/* Exclusions */}
|
|
949
|
-
<div className="border-t border-white/5 pt-3">
|
|
950
|
-
<div className="flex items-center gap-2 mb-2">
|
|
951
|
-
<Users className="w-3.5 h-3.5 text-gray-500" />
|
|
952
|
-
<span className="text-xs text-gray-400">Excluded workspaces</span>
|
|
953
|
-
</div>
|
|
954
|
-
{config.excludedFrom.length === 0 ? (
|
|
955
|
-
<p className="text-[11px] text-gray-600">No exclusions. All workspaces can access this lobe.</p>
|
|
956
|
-
) : (
|
|
957
|
-
<div className="space-y-1">
|
|
958
|
-
{config.excludedFrom.map(wsId => {
|
|
959
|
-
const ws = allLobes.find(l => l.workspaceId === wsId);
|
|
960
|
-
return (
|
|
961
|
-
<div key={wsId} className="flex items-center justify-between text-xs bg-white/[0.02] rounded px-2 py-1.5">
|
|
962
|
-
<span className="text-gray-300">{ws?.name || `Workspace ${wsId}`}</span>
|
|
963
|
-
<button
|
|
964
|
-
onClick={() => save({ excludedFrom: config.excludedFrom.filter(id => id !== wsId) })}
|
|
965
|
-
className="text-gray-600 hover:text-red-400"
|
|
966
|
-
>
|
|
967
|
-
<X className="w-3 h-3" />
|
|
968
|
-
</button>
|
|
969
|
-
</div>
|
|
970
|
-
);
|
|
971
|
-
})}
|
|
972
|
-
</div>
|
|
973
|
-
)}
|
|
974
|
-
</div>
|
|
975
|
-
</div>
|
|
976
|
-
);
|
|
977
|
-
}
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
- [ ] **Step 2: Commit**
|
|
981
|
-
|
|
982
|
-
```bash
|
|
983
|
-
git add src/components/cortex/lobe-settings.tsx
|
|
984
|
-
git commit -m "feat(cortex): add lobe settings UI component"
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
---
|
|
988
|
-
|
|
989
|
-
### Task 8: Barrel export and wiring
|
|
990
|
-
|
|
991
|
-
**Files:**
|
|
992
|
-
- Create: `src/lib/cortex/lobes/index.ts`
|
|
993
|
-
- Modify: `src/app/(desktop)/cortex/page.tsx` — add Lobes section to Settings tab
|
|
994
|
-
|
|
995
|
-
- [ ] **Step 1: Create barrel export**
|
|
996
|
-
|
|
997
|
-
```typescript
|
|
998
|
-
// src/lib/cortex/lobes/index.ts
|
|
999
|
-
export { parseLobeConfig, serializeLobeConfig, DEFAULT_LOBE_CONFIG } from './config';
|
|
1000
|
-
export type { LobeConfig, LobeSubscription } from './config';
|
|
1001
|
-
export { resolveLobes } from './resolver';
|
|
1002
|
-
export type { ResolvedLobe } from './resolver';
|
|
1003
|
-
export { LobeShareStore } from './shares';
|
|
1004
|
-
export type { LobeShare } from './shares';
|
|
1005
|
-
```
|
|
1006
|
-
|
|
1007
|
-
- [ ] **Step 2: Add LobeSettings to the Cortex page Settings tab**
|
|
1008
|
-
|
|
1009
|
-
Read `src/app/(desktop)/cortex/page.tsx`. Find the Settings tab rendering. Currently it shows `<CortexSettings />`. Add `<LobeSettings>` below it, but only when a workspace is active. The workspace ID needs to come from somewhere — check how the terminal page gets `activeWorkspace`:
|
|
1010
|
-
|
|
1011
|
-
Read `src/app/(desktop)/terminal/page.tsx` to see how `activeWorkspace` is loaded. The pattern is likely a fetch to `/api/workspaces` with `is_active=1`.
|
|
1012
|
-
|
|
1013
|
-
For the Cortex page, add a simple workspace selector or use the active workspace. The simplest approach: fetch the active workspace and pass its ID to LobeSettings.
|
|
1014
|
-
|
|
1015
|
-
```typescript
|
|
1016
|
-
// In CortexPage, add state:
|
|
1017
|
-
const [activeWorkspace, setActiveWorkspace] = useState<any>(null);
|
|
1018
|
-
|
|
1019
|
-
// Fetch active workspace:
|
|
1020
|
-
useEffect(() => {
|
|
1021
|
-
fetch(api('/api/workspaces'))
|
|
1022
|
-
.then(r => r.json())
|
|
1023
|
-
.then(data => {
|
|
1024
|
-
const active = (data.workspaces || []).find((w: any) => w.isActive);
|
|
1025
|
-
setActiveWorkspace(active);
|
|
1026
|
-
})
|
|
1027
|
-
.catch(() => {});
|
|
1028
|
-
}, []);
|
|
1029
|
-
|
|
1030
|
-
// In Settings tab rendering, add LobeSettings:
|
|
1031
|
-
{tab === 'settings' && (
|
|
1032
|
-
<div className="p-6 max-w-2xl space-y-8">
|
|
1033
|
-
<CortexSettings />
|
|
1034
|
-
{activeWorkspace && (
|
|
1035
|
-
<LobeSettings
|
|
1036
|
-
workspaceId={activeWorkspace.id}
|
|
1037
|
-
workspaceName={activeWorkspace.name}
|
|
1038
|
-
/>
|
|
1039
|
-
)}
|
|
1040
|
-
</div>
|
|
1041
|
-
)}
|
|
1042
|
-
```
|
|
1043
|
-
|
|
1044
|
-
Import: `import { LobeSettings } from '@/components/cortex/lobe-settings';`
|
|
1045
|
-
|
|
1046
|
-
- [ ] **Step 3: Run full test suite**
|
|
1047
|
-
|
|
1048
|
-
Run: `npx vitest run tests/lib/cortex/`
|
|
1049
|
-
|
|
1050
|
-
- [ ] **Step 4: Commit**
|
|
1051
|
-
|
|
1052
|
-
```bash
|
|
1053
|
-
git add src/lib/cortex/lobes/index.ts src/app/(desktop)/cortex/page.tsx
|
|
1054
|
-
git commit -m "feat(cortex): wire lobe settings into Cortex page"
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
---
|
|
1058
|
-
|
|
1059
|
-
## Summary
|
|
1060
|
-
|
|
1061
|
-
| Task | Component | Tests | Status |
|
|
1062
|
-
|------|-----------|-------|--------|
|
|
1063
|
-
| 1 | Lobe config types | — | |
|
|
1064
|
-
| 2 | DB migration (lobe_config column) | — | |
|
|
1065
|
-
| 3 | Lobe resolver | 8 | |
|
|
1066
|
-
| 4 | Cross-user sharing | 6 | |
|
|
1067
|
-
| 5 | Context Engine integration | regression | |
|
|
1068
|
-
| 6 | API endpoints (3 files) | — | |
|
|
1069
|
-
| 7 | Lobe settings UI component | — | |
|
|
1070
|
-
| 8 | Barrel export + page wiring | regression | |
|
|
1071
|
-
|
|
1072
|
-
**Total: 8 tasks, ~14 new tests, 3 chunks**
|
|
1073
|
-
|
|
1074
|
-
**Key design decisions:**
|
|
1075
|
-
- Lobe config stored as JSON column on workspaces table (simple, always loaded with workspace)
|
|
1076
|
-
- Resolver computes accessible lobes at query time from workspace list + config
|
|
1077
|
-
- Cross-user shares use a separate SQLite table in the entity graph DB with two-step handshake
|
|
1078
|
-
- Context Engine uses resolved lobes when provided, falls back to hardcoded layers for backward compat
|
|
1079
|
-
- Privacy is safe-by-default: private workspaces are excluded, cross-user is closed by default
|
|
1080
|
-
- Base weights: own=1.0, personal=0.9, sibling workspaces=0.6, team=0.5, subscribed=0.4
|
|
1
|
+
# Cortex Lobes Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add knowledge compartmentalization ("lobes") to Cortex so workspaces control which knowledge sources they can pull from — open by default within a user, closed by default across users, with privacy, exclusions, and cross-user sharing.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A new `src/lib/cortex/lobes/` module containing lobe config types, a resolver that computes accessible lobes for a workspace, and sharing logic. The ContextEngine's `computeSourceWeights()` is modified to use the lobe resolver instead of hardcoded layers. Lobe config is stored as a JSON column on the workspaces table. Cross-user shares live in the entity graph's SQLite database.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, better-sqlite3 (entity graph DB), vitest
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-03-16-cortex-lobes-design.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## File Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
New files:
|
|
19
|
+
├── src/lib/cortex/lobes/config.ts — LobeConfig types and defaults
|
|
20
|
+
├── src/lib/cortex/lobes/resolver.ts — Resolve accessible lobes for a workspace
|
|
21
|
+
├── src/lib/cortex/lobes/shares.ts — Cross-user sharing (lobe_shares table)
|
|
22
|
+
├── src/lib/cortex/lobes/index.ts — Barrel export
|
|
23
|
+
├── src/app/api/cortex/lobes/route.ts — List lobes, update config
|
|
24
|
+
├── src/app/api/cortex/lobes/share/route.ts — Share management
|
|
25
|
+
├── src/components/cortex/lobe-settings.tsx — UI component for workspace settings
|
|
26
|
+
|
|
27
|
+
Test files:
|
|
28
|
+
├── tests/lib/cortex/lobes/resolver.test.ts
|
|
29
|
+
├── tests/lib/cortex/lobes/shares.test.ts
|
|
30
|
+
|
|
31
|
+
Modified files:
|
|
32
|
+
├── src/lib/db/schema.ts — Add lobe_config column to workspaces
|
|
33
|
+
├── src/lib/cortex/retrieval/context-engine.ts — Use lobe resolver for search scopes
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Chunk 1: Types, DB Migration, and Lobe Resolver
|
|
39
|
+
|
|
40
|
+
### Task 1: Lobe config types and defaults
|
|
41
|
+
|
|
42
|
+
**Files:**
|
|
43
|
+
- Create: `src/lib/cortex/lobes/config.ts`
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 1: Create the types file**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// src/lib/cortex/lobes/config.ts
|
|
49
|
+
|
|
50
|
+
export interface LobeSubscription {
|
|
51
|
+
type: 'workspace' | 'user' | 'tag' | 'team' | 'department' | 'organization';
|
|
52
|
+
id: string; // workspace ID, user entity ID, tag name, etc.
|
|
53
|
+
label: string; // display name
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface LobeConfig {
|
|
57
|
+
isPrivate: boolean;
|
|
58
|
+
excludedFrom: number[]; // workspace IDs blocked from accessing this lobe
|
|
59
|
+
subscriptions: LobeSubscription[];
|
|
60
|
+
tags: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const DEFAULT_LOBE_CONFIG: LobeConfig = {
|
|
64
|
+
isPrivate: false,
|
|
65
|
+
excludedFrom: [],
|
|
66
|
+
subscriptions: [],
|
|
67
|
+
tags: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function parseLobeConfig(raw: string | null | undefined): LobeConfig {
|
|
71
|
+
if (!raw) return { ...DEFAULT_LOBE_CONFIG };
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
return {
|
|
75
|
+
isPrivate: parsed.isPrivate ?? false,
|
|
76
|
+
excludedFrom: Array.isArray(parsed.excludedFrom) ? parsed.excludedFrom : [],
|
|
77
|
+
subscriptions: Array.isArray(parsed.subscriptions) ? parsed.subscriptions : [],
|
|
78
|
+
tags: Array.isArray(parsed.tags) ? parsed.tags : [],
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return { ...DEFAULT_LOBE_CONFIG };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function serializeLobeConfig(config: LobeConfig): string {
|
|
86
|
+
return JSON.stringify(config);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- [ ] **Step 2: Commit**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git add src/lib/cortex/lobes/config.ts
|
|
94
|
+
git commit -m "feat(cortex): add lobe config types and defaults"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### Task 2: Database migration — add lobe_config column
|
|
100
|
+
|
|
101
|
+
**Files:**
|
|
102
|
+
- Modify: `src/lib/db/schema.ts`
|
|
103
|
+
|
|
104
|
+
- [ ] **Step 1: Read schema.ts to find the migration section**
|
|
105
|
+
|
|
106
|
+
Read `src/lib/db/schema.ts`. Find the section with `addCol` calls (around line 120+). Add:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
addCol('workspaces', 'lobe_config', "TEXT DEFAULT '{}'");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This adds a JSON text column to the existing workspaces table with an empty config default.
|
|
113
|
+
|
|
114
|
+
- [ ] **Step 2: Commit**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git add src/lib/db/schema.ts
|
|
118
|
+
git commit -m "feat(cortex): add lobe_config column to workspaces table"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Task 3: Lobe resolver — compute accessible lobes for a workspace
|
|
124
|
+
|
|
125
|
+
**Files:**
|
|
126
|
+
- Create: `src/lib/cortex/lobes/resolver.ts`
|
|
127
|
+
- Create: `tests/lib/cortex/lobes/resolver.test.ts`
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 1: Write failing tests**
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// tests/lib/cortex/lobes/resolver.test.ts
|
|
133
|
+
import { describe, it, expect } from 'vitest';
|
|
134
|
+
import { resolveLobes } from '@/lib/cortex/lobes/resolver';
|
|
135
|
+
import type { LobeConfig } from '@/lib/cortex/lobes/config';
|
|
136
|
+
import { DEFAULT_LOBE_CONFIG } from '@/lib/cortex/lobes/config';
|
|
137
|
+
|
|
138
|
+
// Minimal workspace shape for testing
|
|
139
|
+
interface TestWorkspace {
|
|
140
|
+
id: number;
|
|
141
|
+
name: string;
|
|
142
|
+
lobeConfig: LobeConfig;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
describe('resolveLobes', () => {
|
|
146
|
+
const workspaces: TestWorkspace[] = [
|
|
147
|
+
{ id: 1, name: 'Auth Service', lobeConfig: DEFAULT_LOBE_CONFIG },
|
|
148
|
+
{ id: 2, name: 'Frontend', lobeConfig: DEFAULT_LOBE_CONFIG },
|
|
149
|
+
{ id: 3, name: 'Private Project', lobeConfig: { ...DEFAULT_LOBE_CONFIG, isPrivate: true } },
|
|
150
|
+
{ id: 4, name: 'Excluded', lobeConfig: { ...DEFAULT_LOBE_CONFIG, excludedFrom: [1] } },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
it('includes own workspace lobe', () => {
|
|
154
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
155
|
+
const keys = lobes.map(l => l.layerKey);
|
|
156
|
+
expect(keys).toContain('workspace/1');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('includes personal lobe', () => {
|
|
160
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
161
|
+
const keys = lobes.map(l => l.layerKey);
|
|
162
|
+
expect(keys).toContain('personal');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('includes other non-private workspaces by default', () => {
|
|
166
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
167
|
+
const keys = lobes.map(l => l.layerKey);
|
|
168
|
+
expect(keys).toContain('workspace/2');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('excludes private workspaces', () => {
|
|
172
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
173
|
+
const keys = lobes.map(l => l.layerKey);
|
|
174
|
+
expect(keys).not.toContain('workspace/3');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('excludes workspaces that exclude the requester', () => {
|
|
178
|
+
// Workspace 4 excludes workspace 1
|
|
179
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
180
|
+
const keys = lobes.map(l => l.layerKey);
|
|
181
|
+
expect(keys).not.toContain('workspace/4');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('includes team lobe by default', () => {
|
|
185
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: workspaces });
|
|
186
|
+
const keys = lobes.map(l => l.layerKey);
|
|
187
|
+
expect(keys).toContain('team');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('includes explicit subscriptions', () => {
|
|
191
|
+
const ws: TestWorkspace[] = [
|
|
192
|
+
{
|
|
193
|
+
id: 1, name: 'Main',
|
|
194
|
+
lobeConfig: {
|
|
195
|
+
...DEFAULT_LOBE_CONFIG,
|
|
196
|
+
subscriptions: [{ type: 'tag', id: 'infrastructure', label: 'Infrastructure' }],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: ws });
|
|
201
|
+
const tags = lobes.filter(l => l.type === 'tag');
|
|
202
|
+
expect(tags).toHaveLength(1);
|
|
203
|
+
expect(tags[0].id).toBe('infrastructure');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('assigns lower weight to subscribed lobes vs inherited', () => {
|
|
207
|
+
const ws: TestWorkspace[] = [
|
|
208
|
+
{
|
|
209
|
+
id: 1, name: 'Main',
|
|
210
|
+
lobeConfig: {
|
|
211
|
+
...DEFAULT_LOBE_CONFIG,
|
|
212
|
+
subscriptions: [{ type: 'workspace', id: '99', label: 'Remote' }],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
const lobes = resolveLobes({ workspaceId: 1, allWorkspaces: ws });
|
|
217
|
+
const own = lobes.find(l => l.layerKey === 'workspace/1');
|
|
218
|
+
const subscribed = lobes.find(l => l.layerKey === 'workspace/99');
|
|
219
|
+
expect(own!.baseWeight).toBeGreaterThan(subscribed!.baseWeight);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
225
|
+
|
|
226
|
+
Run: `npx vitest run tests/lib/cortex/lobes/resolver.test.ts`
|
|
227
|
+
|
|
228
|
+
- [ ] **Step 3: Implement lobe resolver**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// src/lib/cortex/lobes/resolver.ts
|
|
232
|
+
import type { LobeConfig, LobeSubscription } from './config';
|
|
233
|
+
|
|
234
|
+
export interface ResolvedLobe {
|
|
235
|
+
layerKey: string; // LanceDB storage path (e.g., 'workspace/42', 'personal', 'team')
|
|
236
|
+
label: string; // display name
|
|
237
|
+
type: 'own' | 'personal' | 'workspace' | 'team' | 'department' | 'organization' | 'tag' | 'user';
|
|
238
|
+
id: string; // source identifier
|
|
239
|
+
baseWeight: number; // base retrieval weight (before graph proximity)
|
|
240
|
+
inherited: boolean; // true if auto-inherited, false if explicitly subscribed
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface WorkspaceInfo {
|
|
244
|
+
id: number;
|
|
245
|
+
name: string;
|
|
246
|
+
lobeConfig: LobeConfig;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface ResolveInput {
|
|
250
|
+
workspaceId: number;
|
|
251
|
+
allWorkspaces: WorkspaceInfo[];
|
|
252
|
+
userId?: string; // for personal lobe entity ID
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Resolve the list of accessible knowledge lobes for a workspace.
|
|
257
|
+
*
|
|
258
|
+
* Default behavior: own workspace + personal + all non-private sibling workspaces + team + org.
|
|
259
|
+
* Respects privacy, exclusions, and explicit subscriptions.
|
|
260
|
+
*/
|
|
261
|
+
export function resolveLobes(input: ResolveInput): ResolvedLobe[] {
|
|
262
|
+
const { workspaceId, allWorkspaces, userId } = input;
|
|
263
|
+
const lobes: ResolvedLobe[] = [];
|
|
264
|
+
|
|
265
|
+
const thisWs = allWorkspaces.find(w => w.id === workspaceId);
|
|
266
|
+
const thisConfig = thisWs?.lobeConfig;
|
|
267
|
+
|
|
268
|
+
// 1. Own workspace lobe (always included)
|
|
269
|
+
lobes.push({
|
|
270
|
+
layerKey: `workspace/${workspaceId}`,
|
|
271
|
+
label: thisWs?.name ?? 'This workspace',
|
|
272
|
+
type: 'own',
|
|
273
|
+
id: String(workspaceId),
|
|
274
|
+
baseWeight: 1.0,
|
|
275
|
+
inherited: true,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// 2. Personal lobe (always included)
|
|
279
|
+
lobes.push({
|
|
280
|
+
layerKey: 'personal',
|
|
281
|
+
label: 'Personal',
|
|
282
|
+
type: 'personal',
|
|
283
|
+
id: userId ?? 'personal',
|
|
284
|
+
baseWeight: 0.9,
|
|
285
|
+
inherited: true,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// 3. Other workspaces (same user) — included unless private or excluded
|
|
289
|
+
for (const ws of allWorkspaces) {
|
|
290
|
+
if (ws.id === workspaceId) continue;
|
|
291
|
+
|
|
292
|
+
// Skip if the other workspace is private
|
|
293
|
+
if (ws.lobeConfig.isPrivate) continue;
|
|
294
|
+
|
|
295
|
+
// Skip if the other workspace excludes this workspace
|
|
296
|
+
if (ws.lobeConfig.excludedFrom.includes(workspaceId)) continue;
|
|
297
|
+
|
|
298
|
+
lobes.push({
|
|
299
|
+
layerKey: `workspace/${ws.id}`,
|
|
300
|
+
label: ws.name,
|
|
301
|
+
type: 'workspace',
|
|
302
|
+
id: String(ws.id),
|
|
303
|
+
baseWeight: 0.6,
|
|
304
|
+
inherited: true,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 4. Team / org inherited lobes
|
|
309
|
+
lobes.push({
|
|
310
|
+
layerKey: 'team',
|
|
311
|
+
label: 'Team',
|
|
312
|
+
type: 'team',
|
|
313
|
+
id: 'team',
|
|
314
|
+
baseWeight: 0.5,
|
|
315
|
+
inherited: true,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// 5. Explicit subscriptions from this workspace's config
|
|
319
|
+
if (thisConfig?.subscriptions) {
|
|
320
|
+
for (const sub of thisConfig.subscriptions) {
|
|
321
|
+
// Avoid duplicates
|
|
322
|
+
const layerKey = sub.type === 'workspace' ? `workspace/${sub.id}`
|
|
323
|
+
: sub.type === 'tag' ? `tag/${sub.id}`
|
|
324
|
+
: sub.type === 'team' ? `team/${sub.id}`
|
|
325
|
+
: sub.type === 'user' ? `user/${sub.id}`
|
|
326
|
+
: sub.id;
|
|
327
|
+
|
|
328
|
+
if (lobes.some(l => l.layerKey === layerKey)) continue;
|
|
329
|
+
|
|
330
|
+
lobes.push({
|
|
331
|
+
layerKey,
|
|
332
|
+
label: sub.label,
|
|
333
|
+
type: sub.type as ResolvedLobe['type'],
|
|
334
|
+
id: sub.id,
|
|
335
|
+
baseWeight: 0.4, // subscribed = lower weight than inherited
|
|
336
|
+
inherited: false,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return lobes;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
346
|
+
|
|
347
|
+
Run: `npx vitest run tests/lib/cortex/lobes/resolver.test.ts`
|
|
348
|
+
Expected: PASS (8 tests)
|
|
349
|
+
|
|
350
|
+
- [ ] **Step 5: Commit**
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
git add src/lib/cortex/lobes/resolver.ts tests/lib/cortex/lobes/resolver.test.ts
|
|
354
|
+
git commit -m "feat(cortex): add lobe resolver for workspace knowledge scoping"
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Chunk 2: Cross-User Sharing and Context Engine Integration
|
|
360
|
+
|
|
361
|
+
### Task 4: Cross-user lobe sharing
|
|
362
|
+
|
|
363
|
+
**Files:**
|
|
364
|
+
- Create: `src/lib/cortex/lobes/shares.ts`
|
|
365
|
+
- Create: `tests/lib/cortex/lobes/shares.test.ts`
|
|
366
|
+
|
|
367
|
+
- [ ] **Step 1: Write failing tests**
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// tests/lib/cortex/lobes/shares.test.ts
|
|
371
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
372
|
+
import fs from 'fs';
|
|
373
|
+
import path from 'path';
|
|
374
|
+
import os from 'os';
|
|
375
|
+
import Database from 'better-sqlite3';
|
|
376
|
+
import { LobeShareStore } from '@/lib/cortex/lobes/shares';
|
|
377
|
+
|
|
378
|
+
describe('LobeShareStore', () => {
|
|
379
|
+
let tmpDir: string;
|
|
380
|
+
let store: LobeShareStore;
|
|
381
|
+
|
|
382
|
+
beforeEach(() => {
|
|
383
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lobe-shares-'));
|
|
384
|
+
const db = new Database(path.join(tmpDir, 'test.db'));
|
|
385
|
+
store = new LobeShareStore(db);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
afterEach(() => {
|
|
389
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('creates a share', () => {
|
|
393
|
+
const share = store.share({
|
|
394
|
+
ownerUserId: 'person-alice',
|
|
395
|
+
ownerWorkspaceId: 1,
|
|
396
|
+
ownerLobeName: 'Auth Service',
|
|
397
|
+
sharedWithUserId: 'person-bob',
|
|
398
|
+
});
|
|
399
|
+
expect(share.id).toBeDefined();
|
|
400
|
+
expect(share.accepted).toBe(false);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('accepts a share', () => {
|
|
404
|
+
const share = store.share({
|
|
405
|
+
ownerUserId: 'person-alice',
|
|
406
|
+
ownerWorkspaceId: 1,
|
|
407
|
+
ownerLobeName: 'Auth Service',
|
|
408
|
+
sharedWithUserId: 'person-bob',
|
|
409
|
+
});
|
|
410
|
+
store.accept(share.id);
|
|
411
|
+
const updated = store.getShare(share.id);
|
|
412
|
+
expect(updated!.accepted).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('lists incoming shares for a user', () => {
|
|
416
|
+
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
417
|
+
store.share({ ownerUserId: 'person-charlie', ownerWorkspaceId: 2, ownerLobeName: 'WS2', sharedWithUserId: 'person-bob' });
|
|
418
|
+
const incoming = store.listIncoming('person-bob');
|
|
419
|
+
expect(incoming).toHaveLength(2);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('lists outgoing shares for a user', () => {
|
|
423
|
+
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
424
|
+
const outgoing = store.listOutgoing('person-alice');
|
|
425
|
+
expect(outgoing).toHaveLength(1);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('revokes a share', () => {
|
|
429
|
+
const share = store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
430
|
+
store.revoke(share.id);
|
|
431
|
+
expect(store.getShare(share.id)).toBeNull();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('prevents duplicate shares', () => {
|
|
435
|
+
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
436
|
+
store.share({ ownerUserId: 'person-alice', ownerWorkspaceId: 1, ownerLobeName: 'WS1', sharedWithUserId: 'person-bob' });
|
|
437
|
+
const incoming = store.listIncoming('person-bob');
|
|
438
|
+
expect(incoming).toHaveLength(1);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- [ ] **Step 2: Implement share store**
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// src/lib/cortex/lobes/shares.ts
|
|
447
|
+
import type Database from 'better-sqlite3';
|
|
448
|
+
|
|
449
|
+
export interface LobeShare {
|
|
450
|
+
id: string;
|
|
451
|
+
ownerUserId: string;
|
|
452
|
+
ownerWorkspaceId: number;
|
|
453
|
+
ownerLobeName: string;
|
|
454
|
+
sharedWithUserId: string;
|
|
455
|
+
accepted: boolean;
|
|
456
|
+
created: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
interface ShareInput {
|
|
460
|
+
ownerUserId: string;
|
|
461
|
+
ownerWorkspaceId: number;
|
|
462
|
+
ownerLobeName: string;
|
|
463
|
+
sharedWithUserId: string;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export class LobeShareStore {
|
|
467
|
+
private db: InstanceType<typeof Database>;
|
|
468
|
+
|
|
469
|
+
constructor(db: InstanceType<typeof Database>) {
|
|
470
|
+
this.db = db;
|
|
471
|
+
this.db.exec(`
|
|
472
|
+
CREATE TABLE IF NOT EXISTS lobe_shares (
|
|
473
|
+
id TEXT PRIMARY KEY,
|
|
474
|
+
owner_user_id TEXT NOT NULL,
|
|
475
|
+
owner_workspace_id INTEGER NOT NULL,
|
|
476
|
+
owner_lobe_name TEXT NOT NULL,
|
|
477
|
+
shared_with_user_id TEXT NOT NULL,
|
|
478
|
+
accepted INTEGER DEFAULT 0,
|
|
479
|
+
created TEXT NOT NULL,
|
|
480
|
+
UNIQUE(owner_user_id, owner_workspace_id, shared_with_user_id)
|
|
481
|
+
);
|
|
482
|
+
CREATE INDEX IF NOT EXISTS idx_lobe_shares_recipient ON lobe_shares(shared_with_user_id);
|
|
483
|
+
CREATE INDEX IF NOT EXISTS idx_lobe_shares_owner ON lobe_shares(owner_user_id);
|
|
484
|
+
`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
share(input: ShareInput): LobeShare {
|
|
488
|
+
const id = crypto.randomUUID();
|
|
489
|
+
const now = new Date().toISOString();
|
|
490
|
+
|
|
491
|
+
this.db.prepare(`
|
|
492
|
+
INSERT INTO lobe_shares (id, owner_user_id, owner_workspace_id, owner_lobe_name, shared_with_user_id, created)
|
|
493
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
494
|
+
ON CONFLICT(owner_user_id, owner_workspace_id, shared_with_user_id) DO NOTHING
|
|
495
|
+
`).run(id, input.ownerUserId, input.ownerWorkspaceId, input.ownerLobeName, input.sharedWithUserId, now);
|
|
496
|
+
|
|
497
|
+
// Return existing if duplicate
|
|
498
|
+
const existing = this.db.prepare(
|
|
499
|
+
'SELECT * FROM lobe_shares WHERE owner_user_id = ? AND owner_workspace_id = ? AND shared_with_user_id = ?'
|
|
500
|
+
).get(input.ownerUserId, input.ownerWorkspaceId, input.sharedWithUserId) as any;
|
|
501
|
+
|
|
502
|
+
return this.rowToShare(existing);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
accept(id: string): void {
|
|
506
|
+
this.db.prepare('UPDATE lobe_shares SET accepted = 1 WHERE id = ?').run(id);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
revoke(id: string): void {
|
|
510
|
+
this.db.prepare('DELETE FROM lobe_shares WHERE id = ?').run(id);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
getShare(id: string): LobeShare | null {
|
|
514
|
+
const row = this.db.prepare('SELECT * FROM lobe_shares WHERE id = ?').get(id) as any;
|
|
515
|
+
return row ? this.rowToShare(row) : null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
listIncoming(userId: string): LobeShare[] {
|
|
519
|
+
const rows = this.db.prepare(
|
|
520
|
+
'SELECT * FROM lobe_shares WHERE shared_with_user_id = ? ORDER BY created DESC'
|
|
521
|
+
).all(userId) as any[];
|
|
522
|
+
return rows.map(r => this.rowToShare(r));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
listOutgoing(userId: string): LobeShare[] {
|
|
526
|
+
const rows = this.db.prepare(
|
|
527
|
+
'SELECT * FROM lobe_shares WHERE owner_user_id = ? ORDER BY created DESC'
|
|
528
|
+
).all(userId) as any[];
|
|
529
|
+
return rows.map(r => this.rowToShare(r));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
listAcceptedForUser(userId: string): LobeShare[] {
|
|
533
|
+
const rows = this.db.prepare(
|
|
534
|
+
'SELECT * FROM lobe_shares WHERE shared_with_user_id = ? AND accepted = 1'
|
|
535
|
+
).all(userId) as any[];
|
|
536
|
+
return rows.map(r => this.rowToShare(r));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private rowToShare(row: any): LobeShare {
|
|
540
|
+
return {
|
|
541
|
+
id: row.id,
|
|
542
|
+
ownerUserId: row.owner_user_id,
|
|
543
|
+
ownerWorkspaceId: row.owner_workspace_id,
|
|
544
|
+
ownerLobeName: row.owner_lobe_name,
|
|
545
|
+
sharedWithUserId: row.shared_with_user_id,
|
|
546
|
+
accepted: row.accepted === 1,
|
|
547
|
+
created: row.created,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
- [ ] **Step 3: Run tests, commit**
|
|
554
|
+
|
|
555
|
+
Run: `npx vitest run tests/lib/cortex/lobes/shares.test.ts`
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
git commit -m "feat(cortex): add cross-user lobe sharing with handshake"
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
### Task 5: Integrate lobe resolver with Context Assembly Engine
|
|
564
|
+
|
|
565
|
+
**Files:**
|
|
566
|
+
- Modify: `src/lib/cortex/retrieval/context-engine.ts`
|
|
567
|
+
|
|
568
|
+
- [ ] **Step 1: Read context-engine.ts**
|
|
569
|
+
|
|
570
|
+
Read the file. Find `computeSourceWeights()` — the private method that returns hardcoded layer definitions.
|
|
571
|
+
|
|
572
|
+
- [ ] **Step 2: Add optional lobe-aware scope computation**
|
|
573
|
+
|
|
574
|
+
Add to `ContextEngineDeps`:
|
|
575
|
+
```typescript
|
|
576
|
+
resolvedLobes?: ResolvedLobe[]; // pre-computed accessible lobes for this workspace
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Import:
|
|
580
|
+
```typescript
|
|
581
|
+
import type { ResolvedLobe } from '../lobes/resolver';
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Modify `computeSourceWeights()` to use resolved lobes when available:
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
private computeSourceWeights(
|
|
588
|
+
intent: IntentResult,
|
|
589
|
+
workspaceId: number | null,
|
|
590
|
+
): SourceConfig[] {
|
|
591
|
+
// If lobes are provided, use them instead of hardcoded layers
|
|
592
|
+
if (this.deps.resolvedLobes && this.deps.resolvedLobes.length > 0) {
|
|
593
|
+
return this.deps.resolvedLobes.map(lobe => {
|
|
594
|
+
const graphProximity = lobe.baseWeight; // lobes already have base weights
|
|
595
|
+
|
|
596
|
+
const weight = computeScopeWeight({
|
|
597
|
+
graphProximity,
|
|
598
|
+
scopeLevel: lobe.type === 'personal' ? 'personal'
|
|
599
|
+
: lobe.type === 'team' || lobe.type === 'department' ? 'team'
|
|
600
|
+
: lobe.type === 'organization' ? 'organization'
|
|
601
|
+
: 'team',
|
|
602
|
+
intentBiases: intent.biases,
|
|
603
|
+
authorityFactor: 1.0,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
layerKey: lobe.layerKey,
|
|
608
|
+
weight,
|
|
609
|
+
limit: Math.max(3, Math.round(weight * 10)),
|
|
610
|
+
};
|
|
611
|
+
}).sort((a, b) => b.weight - a.weight);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Fallback: hardcoded layers (backward compat when no lobe config)
|
|
615
|
+
const layerDefs = [
|
|
616
|
+
// ... existing code unchanged ...
|
|
617
|
+
];
|
|
618
|
+
// ... rest of existing method unchanged ...
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
- [ ] **Step 3: Run existing context-engine tests to verify no regressions**
|
|
623
|
+
|
|
624
|
+
Run: `npx vitest run tests/lib/cortex/retrieval/context-engine.test.ts`
|
|
625
|
+
|
|
626
|
+
- [ ] **Step 4: Commit**
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
git add src/lib/cortex/retrieval/context-engine.ts
|
|
630
|
+
git commit -m "feat(cortex): integrate lobe resolver into context assembly engine"
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Chunk 3: API Routes and UI
|
|
636
|
+
|
|
637
|
+
### Task 6: Lobe API endpoints
|
|
638
|
+
|
|
639
|
+
**Files:**
|
|
640
|
+
- Create: `src/app/api/cortex/lobes/route.ts`
|
|
641
|
+
- Create: `src/app/api/cortex/lobes/[id]/route.ts`
|
|
642
|
+
- Create: `src/app/api/cortex/lobes/share/route.ts`
|
|
643
|
+
|
|
644
|
+
- [ ] **Step 1: Create main lobes endpoint**
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
// src/app/api/cortex/lobes/route.ts
|
|
648
|
+
import { NextResponse } from 'next/server';
|
|
649
|
+
import type { NextRequest } from 'next/server';
|
|
650
|
+
import { getAuthUser, withUser } from '@/lib/auth';
|
|
651
|
+
import { getDb } from '@/lib/db';
|
|
652
|
+
import { parseLobeConfig } from '@/lib/cortex/lobes/config';
|
|
653
|
+
|
|
654
|
+
export async function GET(request: NextRequest) {
|
|
655
|
+
const user = getAuthUser(request);
|
|
656
|
+
return withUser(user, async () => {
|
|
657
|
+
const db = getDb();
|
|
658
|
+
const workspaces = db.prepare(
|
|
659
|
+
'SELECT id, name, color, lobe_config FROM workspaces ORDER BY name'
|
|
660
|
+
).all() as any[];
|
|
661
|
+
|
|
662
|
+
const lobes = workspaces.map(ws => ({
|
|
663
|
+
workspaceId: ws.id,
|
|
664
|
+
name: ws.name,
|
|
665
|
+
color: ws.color,
|
|
666
|
+
config: parseLobeConfig(ws.lobe_config),
|
|
667
|
+
}));
|
|
668
|
+
|
|
669
|
+
return NextResponse.json({ lobes });
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
- [ ] **Step 2: Create single workspace lobe config endpoint**
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
// src/app/api/cortex/lobes/[id]/route.ts
|
|
678
|
+
import { NextResponse } from 'next/server';
|
|
679
|
+
import type { NextRequest } from 'next/server';
|
|
680
|
+
import { getAuthUser, withUser } from '@/lib/auth';
|
|
681
|
+
import { getDb } from '@/lib/db';
|
|
682
|
+
import { parseLobeConfig, serializeLobeConfig } from '@/lib/cortex/lobes/config';
|
|
683
|
+
import type { LobeConfig } from '@/lib/cortex/lobes/config';
|
|
684
|
+
|
|
685
|
+
export async function GET(
|
|
686
|
+
request: NextRequest,
|
|
687
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
688
|
+
) {
|
|
689
|
+
const { id } = await params;
|
|
690
|
+
const user = getAuthUser(request);
|
|
691
|
+
return withUser(user, async () => {
|
|
692
|
+
const db = getDb();
|
|
693
|
+
const ws = db.prepare('SELECT id, name, lobe_config FROM workspaces WHERE id = ?').get(Number(id)) as any;
|
|
694
|
+
if (!ws) return NextResponse.json({ error: 'Workspace not found' }, { status: 404 });
|
|
695
|
+
return NextResponse.json({ workspaceId: ws.id, name: ws.name, config: parseLobeConfig(ws.lobe_config) });
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export async function PUT(
|
|
700
|
+
request: NextRequest,
|
|
701
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
702
|
+
) {
|
|
703
|
+
const { id } = await params;
|
|
704
|
+
const user = getAuthUser(request);
|
|
705
|
+
return withUser(user, async () => {
|
|
706
|
+
const db = getDb();
|
|
707
|
+
const body = await request.json() as Partial<LobeConfig>;
|
|
708
|
+
|
|
709
|
+
const ws = db.prepare('SELECT lobe_config FROM workspaces WHERE id = ?').get(Number(id)) as any;
|
|
710
|
+
if (!ws) return NextResponse.json({ error: 'Workspace not found' }, { status: 404 });
|
|
711
|
+
|
|
712
|
+
const current = parseLobeConfig(ws.lobe_config);
|
|
713
|
+
const updated: LobeConfig = {
|
|
714
|
+
isPrivate: body.isPrivate ?? current.isPrivate,
|
|
715
|
+
excludedFrom: body.excludedFrom ?? current.excludedFrom,
|
|
716
|
+
subscriptions: body.subscriptions ?? current.subscriptions,
|
|
717
|
+
tags: body.tags ?? current.tags,
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
db.prepare('UPDATE workspaces SET lobe_config = ? WHERE id = ?').run(serializeLobeConfig(updated), Number(id));
|
|
721
|
+
return NextResponse.json({ config: updated });
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
- [ ] **Step 3: Create share endpoint**
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
// src/app/api/cortex/lobes/share/route.ts
|
|
730
|
+
import { NextResponse } from 'next/server';
|
|
731
|
+
import type { NextRequest } from 'next/server';
|
|
732
|
+
import { getAuthUser, withUser } from '@/lib/auth';
|
|
733
|
+
import { getCortex, isCortexAvailable } from '@/lib/cortex';
|
|
734
|
+
import { LobeShareStore } from '@/lib/cortex/lobes/shares';
|
|
735
|
+
import { slugify } from '@/lib/cortex/graph/types';
|
|
736
|
+
|
|
737
|
+
export async function GET(request: NextRequest) {
|
|
738
|
+
const user = getAuthUser(request);
|
|
739
|
+
return withUser(user, async () => {
|
|
740
|
+
if (!isCortexAvailable()) return NextResponse.json({ incoming: [], outgoing: [] });
|
|
741
|
+
const cortex = await getCortex();
|
|
742
|
+
if (!cortex) return NextResponse.json({ incoming: [], outgoing: [] });
|
|
743
|
+
|
|
744
|
+
const shareStore = new LobeShareStore(cortex.graph['db']);
|
|
745
|
+
const userId = `person-${slugify(user)}`;
|
|
746
|
+
|
|
747
|
+
return NextResponse.json({
|
|
748
|
+
incoming: shareStore.listIncoming(userId),
|
|
749
|
+
outgoing: shareStore.listOutgoing(userId),
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export async function POST(request: NextRequest) {
|
|
755
|
+
const user = getAuthUser(request);
|
|
756
|
+
return withUser(user, async () => {
|
|
757
|
+
if (!isCortexAvailable()) return NextResponse.json({ error: 'Cortex unavailable' }, { status: 403 });
|
|
758
|
+
const cortex = await getCortex();
|
|
759
|
+
if (!cortex) return NextResponse.json({ error: 'Cortex unavailable' }, { status: 500 });
|
|
760
|
+
|
|
761
|
+
const body = await request.json();
|
|
762
|
+
const { action, shareId, workspaceId, lobeName, sharedWithUserId } = body;
|
|
763
|
+
const shareStore = new LobeShareStore(cortex.graph['db']);
|
|
764
|
+
const userId = `person-${slugify(user)}`;
|
|
765
|
+
|
|
766
|
+
if (action === 'share') {
|
|
767
|
+
const share = shareStore.share({
|
|
768
|
+
ownerUserId: userId,
|
|
769
|
+
ownerWorkspaceId: workspaceId,
|
|
770
|
+
ownerLobeName: lobeName,
|
|
771
|
+
sharedWithUserId,
|
|
772
|
+
});
|
|
773
|
+
return NextResponse.json({ share }, { status: 201 });
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (action === 'accept') {
|
|
777
|
+
shareStore.accept(shareId);
|
|
778
|
+
return NextResponse.json({ accepted: true });
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (action === 'revoke' || action === 'decline') {
|
|
782
|
+
shareStore.revoke(shareId);
|
|
783
|
+
return NextResponse.json({ revoked: true });
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
- [ ] **Step 4: Commit**
|
|
792
|
+
|
|
793
|
+
```bash
|
|
794
|
+
git add src/app/api/cortex/lobes/
|
|
795
|
+
git commit -m "feat(cortex): add lobe API endpoints for config and sharing"
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
### Task 7: Lobe settings UI component
|
|
801
|
+
|
|
802
|
+
**Files:**
|
|
803
|
+
- Create: `src/components/cortex/lobe-settings.tsx`
|
|
804
|
+
|
|
805
|
+
- [ ] **Step 1: Create the component**
|
|
806
|
+
|
|
807
|
+
A settings panel showing the workspace's knowledge sources with toggles, tags, and subscription management.
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
'use client';
|
|
811
|
+
|
|
812
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
813
|
+
import { Shield, ShieldOff, Tag, Plus, X, Users } from 'lucide-react';
|
|
814
|
+
import { api } from '@/lib/api';
|
|
815
|
+
import type { LobeConfig, LobeSubscription } from '@/lib/cortex/lobes/config';
|
|
816
|
+
|
|
817
|
+
interface LobeSettingsProps {
|
|
818
|
+
workspaceId: number;
|
|
819
|
+
workspaceName: string;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export function LobeSettings({ workspaceId, workspaceName }: LobeSettingsProps) {
|
|
823
|
+
const [config, setConfig] = useState<LobeConfig | null>(null);
|
|
824
|
+
const [allLobes, setAllLobes] = useState<any[]>([]);
|
|
825
|
+
const [newTag, setNewTag] = useState('');
|
|
826
|
+
const [saving, setSaving] = useState(false);
|
|
827
|
+
|
|
828
|
+
const fetchConfig = useCallback(async () => {
|
|
829
|
+
const res = await fetch(api(`/api/cortex/lobes/${workspaceId}`));
|
|
830
|
+
if (res.ok) {
|
|
831
|
+
const data = await res.json();
|
|
832
|
+
setConfig(data.config);
|
|
833
|
+
}
|
|
834
|
+
}, [workspaceId]);
|
|
835
|
+
|
|
836
|
+
const fetchAllLobes = useCallback(async () => {
|
|
837
|
+
const res = await fetch(api('/api/cortex/lobes'));
|
|
838
|
+
if (res.ok) {
|
|
839
|
+
const data = await res.json();
|
|
840
|
+
setAllLobes(data.lobes || []);
|
|
841
|
+
}
|
|
842
|
+
}, []);
|
|
843
|
+
|
|
844
|
+
useEffect(() => { fetchConfig(); fetchAllLobes(); }, [fetchConfig, fetchAllLobes]);
|
|
845
|
+
|
|
846
|
+
const save = async (updates: Partial<LobeConfig>) => {
|
|
847
|
+
setSaving(true);
|
|
848
|
+
await fetch(api(`/api/cortex/lobes/${workspaceId}`), {
|
|
849
|
+
method: 'PUT',
|
|
850
|
+
headers: { 'Content-Type': 'application/json' },
|
|
851
|
+
body: JSON.stringify(updates),
|
|
852
|
+
});
|
|
853
|
+
await fetchConfig();
|
|
854
|
+
setSaving(false);
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
if (!config) return <div className="text-gray-500 text-sm p-4">Loading...</div>;
|
|
858
|
+
|
|
859
|
+
const activeSourceCount = allLobes.filter(l =>
|
|
860
|
+
l.workspaceId !== workspaceId && !l.config.isPrivate && !l.config.excludedFrom?.includes(workspaceId)
|
|
861
|
+
).length + 2; // +2 for personal + team
|
|
862
|
+
|
|
863
|
+
return (
|
|
864
|
+
<div className="space-y-6">
|
|
865
|
+
<div>
|
|
866
|
+
<h3 className="text-sm font-medium text-gray-200 mb-1">Knowledge Lobes</h3>
|
|
867
|
+
<p className="text-xs text-gray-500">
|
|
868
|
+
This workspace draws from {activeSourceCount} lobes.
|
|
869
|
+
{config.isPrivate && ' This lobe is private — other workspaces cannot access its knowledge.'}
|
|
870
|
+
</p>
|
|
871
|
+
</div>
|
|
872
|
+
|
|
873
|
+
{/* Privacy toggle */}
|
|
874
|
+
<div className="flex items-center justify-between py-3 border-t border-white/5">
|
|
875
|
+
<div className="flex items-center gap-2">
|
|
876
|
+
{config.isPrivate ? <ShieldOff className="w-4 h-4 text-red-400" /> : <Shield className="w-4 h-4 text-green-400" />}
|
|
877
|
+
<div>
|
|
878
|
+
<div className="text-sm text-gray-200">Private lobe</div>
|
|
879
|
+
<div className="text-[10px] text-gray-500">Other workspaces cannot access this knowledge</div>
|
|
880
|
+
</div>
|
|
881
|
+
</div>
|
|
882
|
+
<button
|
|
883
|
+
onClick={() => save({ isPrivate: !config.isPrivate })}
|
|
884
|
+
disabled={saving}
|
|
885
|
+
className={`px-3 py-1 text-xs rounded ${config.isPrivate ? 'bg-red-500/20 text-red-400' : 'bg-white/5 text-gray-400'}`}
|
|
886
|
+
>
|
|
887
|
+
{config.isPrivate ? 'Private' : 'Open'}
|
|
888
|
+
</button>
|
|
889
|
+
</div>
|
|
890
|
+
|
|
891
|
+
{/* Tags */}
|
|
892
|
+
<div className="border-t border-white/5 pt-3">
|
|
893
|
+
<div className="flex items-center gap-2 mb-2">
|
|
894
|
+
<Tag className="w-3.5 h-3.5 text-gray-500" />
|
|
895
|
+
<span className="text-xs text-gray-400">Tags</span>
|
|
896
|
+
</div>
|
|
897
|
+
<div className="flex flex-wrap gap-1.5 mb-2">
|
|
898
|
+
{config.tags.map(tag => (
|
|
899
|
+
<span key={tag} className="flex items-center gap-1 text-[11px] bg-purple-500/10 text-purple-400 px-2 py-0.5 rounded">
|
|
900
|
+
{tag}
|
|
901
|
+
<button onClick={() => save({ tags: config.tags.filter(t => t !== tag) })} className="hover:text-white">
|
|
902
|
+
<X className="w-2.5 h-2.5" />
|
|
903
|
+
</button>
|
|
904
|
+
</span>
|
|
905
|
+
))}
|
|
906
|
+
</div>
|
|
907
|
+
<div className="flex gap-1.5">
|
|
908
|
+
<input
|
|
909
|
+
value={newTag}
|
|
910
|
+
onChange={e => setNewTag(e.target.value)}
|
|
911
|
+
onKeyDown={e => {
|
|
912
|
+
if (e.key === 'Enter' && newTag.trim()) {
|
|
913
|
+
save({ tags: [...config.tags, newTag.trim()] });
|
|
914
|
+
setNewTag('');
|
|
915
|
+
}
|
|
916
|
+
}}
|
|
917
|
+
placeholder="Add tag..."
|
|
918
|
+
className="flex-1 px-2 py-1 text-xs bg-white/5 border border-white/10 rounded text-gray-300 focus:outline-none focus:border-purple-500/50"
|
|
919
|
+
/>
|
|
920
|
+
</div>
|
|
921
|
+
</div>
|
|
922
|
+
|
|
923
|
+
{/* Subscriptions */}
|
|
924
|
+
<div className="border-t border-white/5 pt-3">
|
|
925
|
+
<div className="flex items-center gap-2 mb-2">
|
|
926
|
+
<Plus className="w-3.5 h-3.5 text-gray-500" />
|
|
927
|
+
<span className="text-xs text-gray-400">Additional sources</span>
|
|
928
|
+
</div>
|
|
929
|
+
{config.subscriptions.length === 0 ? (
|
|
930
|
+
<p className="text-[11px] text-gray-600">No additional subscriptions. Using defaults.</p>
|
|
931
|
+
) : (
|
|
932
|
+
<div className="space-y-1">
|
|
933
|
+
{config.subscriptions.map((sub, i) => (
|
|
934
|
+
<div key={i} className="flex items-center justify-between text-xs bg-white/[0.02] rounded px-2 py-1.5">
|
|
935
|
+
<span className="text-gray-300">{sub.label} <span className="text-gray-600">({sub.type})</span></span>
|
|
936
|
+
<button
|
|
937
|
+
onClick={() => save({ subscriptions: config.subscriptions.filter((_, j) => j !== i) })}
|
|
938
|
+
className="text-gray-600 hover:text-red-400"
|
|
939
|
+
>
|
|
940
|
+
<X className="w-3 h-3" />
|
|
941
|
+
</button>
|
|
942
|
+
</div>
|
|
943
|
+
))}
|
|
944
|
+
</div>
|
|
945
|
+
)}
|
|
946
|
+
</div>
|
|
947
|
+
|
|
948
|
+
{/* Exclusions */}
|
|
949
|
+
<div className="border-t border-white/5 pt-3">
|
|
950
|
+
<div className="flex items-center gap-2 mb-2">
|
|
951
|
+
<Users className="w-3.5 h-3.5 text-gray-500" />
|
|
952
|
+
<span className="text-xs text-gray-400">Excluded workspaces</span>
|
|
953
|
+
</div>
|
|
954
|
+
{config.excludedFrom.length === 0 ? (
|
|
955
|
+
<p className="text-[11px] text-gray-600">No exclusions. All workspaces can access this lobe.</p>
|
|
956
|
+
) : (
|
|
957
|
+
<div className="space-y-1">
|
|
958
|
+
{config.excludedFrom.map(wsId => {
|
|
959
|
+
const ws = allLobes.find(l => l.workspaceId === wsId);
|
|
960
|
+
return (
|
|
961
|
+
<div key={wsId} className="flex items-center justify-between text-xs bg-white/[0.02] rounded px-2 py-1.5">
|
|
962
|
+
<span className="text-gray-300">{ws?.name || `Workspace ${wsId}`}</span>
|
|
963
|
+
<button
|
|
964
|
+
onClick={() => save({ excludedFrom: config.excludedFrom.filter(id => id !== wsId) })}
|
|
965
|
+
className="text-gray-600 hover:text-red-400"
|
|
966
|
+
>
|
|
967
|
+
<X className="w-3 h-3" />
|
|
968
|
+
</button>
|
|
969
|
+
</div>
|
|
970
|
+
);
|
|
971
|
+
})}
|
|
972
|
+
</div>
|
|
973
|
+
)}
|
|
974
|
+
</div>
|
|
975
|
+
</div>
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
- [ ] **Step 2: Commit**
|
|
981
|
+
|
|
982
|
+
```bash
|
|
983
|
+
git add src/components/cortex/lobe-settings.tsx
|
|
984
|
+
git commit -m "feat(cortex): add lobe settings UI component"
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
---
|
|
988
|
+
|
|
989
|
+
### Task 8: Barrel export and wiring
|
|
990
|
+
|
|
991
|
+
**Files:**
|
|
992
|
+
- Create: `src/lib/cortex/lobes/index.ts`
|
|
993
|
+
- Modify: `src/app/(desktop)/cortex/page.tsx` — add Lobes section to Settings tab
|
|
994
|
+
|
|
995
|
+
- [ ] **Step 1: Create barrel export**
|
|
996
|
+
|
|
997
|
+
```typescript
|
|
998
|
+
// src/lib/cortex/lobes/index.ts
|
|
999
|
+
export { parseLobeConfig, serializeLobeConfig, DEFAULT_LOBE_CONFIG } from './config';
|
|
1000
|
+
export type { LobeConfig, LobeSubscription } from './config';
|
|
1001
|
+
export { resolveLobes } from './resolver';
|
|
1002
|
+
export type { ResolvedLobe } from './resolver';
|
|
1003
|
+
export { LobeShareStore } from './shares';
|
|
1004
|
+
export type { LobeShare } from './shares';
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
- [ ] **Step 2: Add LobeSettings to the Cortex page Settings tab**
|
|
1008
|
+
|
|
1009
|
+
Read `src/app/(desktop)/cortex/page.tsx`. Find the Settings tab rendering. Currently it shows `<CortexSettings />`. Add `<LobeSettings>` below it, but only when a workspace is active. The workspace ID needs to come from somewhere — check how the terminal page gets `activeWorkspace`:
|
|
1010
|
+
|
|
1011
|
+
Read `src/app/(desktop)/terminal/page.tsx` to see how `activeWorkspace` is loaded. The pattern is likely a fetch to `/api/workspaces` with `is_active=1`.
|
|
1012
|
+
|
|
1013
|
+
For the Cortex page, add a simple workspace selector or use the active workspace. The simplest approach: fetch the active workspace and pass its ID to LobeSettings.
|
|
1014
|
+
|
|
1015
|
+
```typescript
|
|
1016
|
+
// In CortexPage, add state:
|
|
1017
|
+
const [activeWorkspace, setActiveWorkspace] = useState<any>(null);
|
|
1018
|
+
|
|
1019
|
+
// Fetch active workspace:
|
|
1020
|
+
useEffect(() => {
|
|
1021
|
+
fetch(api('/api/workspaces'))
|
|
1022
|
+
.then(r => r.json())
|
|
1023
|
+
.then(data => {
|
|
1024
|
+
const active = (data.workspaces || []).find((w: any) => w.isActive);
|
|
1025
|
+
setActiveWorkspace(active);
|
|
1026
|
+
})
|
|
1027
|
+
.catch(() => {});
|
|
1028
|
+
}, []);
|
|
1029
|
+
|
|
1030
|
+
// In Settings tab rendering, add LobeSettings:
|
|
1031
|
+
{tab === 'settings' && (
|
|
1032
|
+
<div className="p-6 max-w-2xl space-y-8">
|
|
1033
|
+
<CortexSettings />
|
|
1034
|
+
{activeWorkspace && (
|
|
1035
|
+
<LobeSettings
|
|
1036
|
+
workspaceId={activeWorkspace.id}
|
|
1037
|
+
workspaceName={activeWorkspace.name}
|
|
1038
|
+
/>
|
|
1039
|
+
)}
|
|
1040
|
+
</div>
|
|
1041
|
+
)}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
Import: `import { LobeSettings } from '@/components/cortex/lobe-settings';`
|
|
1045
|
+
|
|
1046
|
+
- [ ] **Step 3: Run full test suite**
|
|
1047
|
+
|
|
1048
|
+
Run: `npx vitest run tests/lib/cortex/`
|
|
1049
|
+
|
|
1050
|
+
- [ ] **Step 4: Commit**
|
|
1051
|
+
|
|
1052
|
+
```bash
|
|
1053
|
+
git add src/lib/cortex/lobes/index.ts src/app/(desktop)/cortex/page.tsx
|
|
1054
|
+
git commit -m "feat(cortex): wire lobe settings into Cortex page"
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
## Summary
|
|
1060
|
+
|
|
1061
|
+
| Task | Component | Tests | Status |
|
|
1062
|
+
|------|-----------|-------|--------|
|
|
1063
|
+
| 1 | Lobe config types | — | |
|
|
1064
|
+
| 2 | DB migration (lobe_config column) | — | |
|
|
1065
|
+
| 3 | Lobe resolver | 8 | |
|
|
1066
|
+
| 4 | Cross-user sharing | 6 | |
|
|
1067
|
+
| 5 | Context Engine integration | regression | |
|
|
1068
|
+
| 6 | API endpoints (3 files) | — | |
|
|
1069
|
+
| 7 | Lobe settings UI component | — | |
|
|
1070
|
+
| 8 | Barrel export + page wiring | regression | |
|
|
1071
|
+
|
|
1072
|
+
**Total: 8 tasks, ~14 new tests, 3 chunks**
|
|
1073
|
+
|
|
1074
|
+
**Key design decisions:**
|
|
1075
|
+
- Lobe config stored as JSON column on workspaces table (simple, always loaded with workspace)
|
|
1076
|
+
- Resolver computes accessible lobes at query time from workspace list + config
|
|
1077
|
+
- Cross-user shares use a separate SQLite table in the entity graph DB with two-step handshake
|
|
1078
|
+
- Context Engine uses resolved lobes when provided, falls back to hardcoded layers for backward compat
|
|
1079
|
+
- Privacy is safe-by-default: private workspaces are excluded, cross-user is closed by default
|
|
1080
|
+
- Base weights: own=1.0, personal=0.9, sibling workspaces=0.6, team=0.5, subscribed=0.4
|