@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
709 lines (708 loc) • 21.1 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { promises as fs } from "fs";
import * as path from "path";
import { pathToFileURL } from "url";
import { logger } from "../monitoring/logger.js";
const DEFAULT_LOAD_TIMEOUT = 3e4;
const ALLOWED_PERMISSION_PATTERNS = [
/^network:[a-z0-9.-]+$/,
/^storage:(local|session)$/,
/^frames:(read|write)$/,
/^events:(emit|listen)$/
];
const BLOCKED_DOMAINS = [
"localhost",
"127.0.0.1",
"0.0.0.0",
"*.local",
"internal.*"
];
class ExtensionLoader {
loadedExtensions = /* @__PURE__ */ new Map();
extensionInstances = /* @__PURE__ */ new Map();
eventHandlers = /* @__PURE__ */ new Map();
grantedPermissions = /* @__PURE__ */ new Map();
/**
* Load an extension from various sources
*/
async loadExtension(options) {
const { source, timeout = DEFAULT_LOAD_TIMEOUT } = options;
try {
const { type, uri } = this.parseSource(source);
logger.info("Loading extension", { source, type });
const loadPromise = this.loadFromSource(type, uri, options);
const result = await this.withTimeout(loadPromise, timeout);
if (!result.success || !result.extension) {
return result;
}
const validation = this.validateExtension(result.extension);
if (!validation.valid) {
return {
success: false,
error: `Extension validation failed: ${validation.errors.join(", ")}`,
warnings: validation.warnings
};
}
if (!options.skipPermissionCheck) {
const permissionCheck = await this.verifyPermissions(
result.extension,
options.permissions
);
if (!permissionCheck.success) {
return permissionCheck;
}
}
const initResult = await this.initializeExtension(
result.extension,
options
);
if (!initResult.success) {
return initResult;
}
const extensionId = this.generateExtensionId(result.extension);
this.registerExtension(
extensionId,
result.extension,
source,
type,
options
);
logger.info("Extension loaded successfully", {
extensionId,
name: result.extension.name,
version: result.extension.version
});
return {
success: true,
extension: result.extension,
extensionId,
warnings: validation.warnings
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error("Failed to load extension", { source, error: message });
return {
success: false,
error: `Failed to load extension: ${message}`
};
}
}
/**
* Unload an extension
*/
async unloadExtension(extensionId) {
const extension = this.extensionInstances.get(extensionId);
const state = this.loadedExtensions.get(extensionId);
if (!extension || !state) {
logger.warn("Extension not found for unload", { extensionId });
return false;
}
try {
await extension.destroy();
this.extensionInstances.delete(extensionId);
this.loadedExtensions.delete(extensionId);
this.grantedPermissions.delete(extensionId);
logger.info("Extension unloaded", { extensionId });
return true;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error("Failed to unload extension", {
extensionId,
error: message
});
return false;
}
}
/**
* Get all loaded extensions
*/
getLoadedExtensions() {
return Array.from(this.loadedExtensions.values());
}
/**
* Get extension by ID
*/
getExtension(extensionId) {
return this.extensionInstances.get(extensionId);
}
/**
* Get extension state by ID
*/
getExtensionState(extensionId) {
return this.loadedExtensions.get(extensionId);
}
/**
* Parse source string to determine type and URI
*/
parseSource(source) {
if (source.startsWith("https://") || source.startsWith("http://")) {
return { type: "url", uri: source };
}
if (source.startsWith("file://")) {
return { type: "file", uri: source.slice(7) };
}
if (source.startsWith("npm:")) {
return { type: "npm", uri: source.slice(4) };
}
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
return { type: "file", uri: source };
}
return { type: "npm", uri: source };
}
/**
* Load extension from source based on type
*/
async loadFromSource(type, uri, _options) {
switch (type) {
case "url":
return this.loadFromUrl(uri);
case "file":
return this.loadFromFile(uri);
case "npm":
return this.loadFromNpm(uri);
default:
return {
success: false,
error: `Unknown source type: ${type}`
};
}
}
/**
* Load extension from URL
*/
async loadFromUrl(url) {
try {
const parsedUrl = new URL(url);
if (this.isBlockedDomain(parsedUrl.hostname)) {
return {
success: false,
error: `Blocked domain: ${parsedUrl.hostname}`
};
}
if (parsedUrl.protocol !== "https:" && process.env["NODE_ENV"] === "production") {
return {
success: false,
error: "Only HTTPS URLs are allowed in production"
};
}
const response = await fetch(url);
if (!response.ok) {
return {
success: false,
error: `HTTP ${response.status}: ${response.statusText}`
};
}
const code = await response.text();
const manifestUrl = url.replace(/\.js$/, ".manifest.json");
let manifest;
try {
const manifestResponse = await fetch(manifestUrl);
if (manifestResponse.ok) {
manifest = await manifestResponse.json();
}
} catch {
}
const extension = await this.evaluateExtensionCode(code, url);
if (manifest) {
extension.manifest = manifest;
}
return { success: true, extension };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Failed to load from URL: ${message}`
};
}
}
/**
* Load extension from local file
*/
async loadFromFile(filePath) {
try {
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
try {
await fs.access(absolutePath);
} catch {
return {
success: false,
error: `File not found: ${absolutePath}`
};
}
const manifestPath = absolutePath.replace(/\.js$/, ".manifest.json");
let manifest;
try {
const manifestContent = await fs.readFile(manifestPath, "utf-8");
manifest = JSON.parse(manifestContent);
} catch {
try {
const packagePath = path.join(
path.dirname(absolutePath),
"package.json"
);
const packageContent = await fs.readFile(packagePath, "utf-8");
const pkg = JSON.parse(packageContent);
if (pkg.stackmemory) {
manifest = pkg.stackmemory;
}
} catch {
}
}
const fileUrl = pathToFileURL(absolutePath).href;
const module = await import(fileUrl);
const extension = module.default || module;
if (!this.isExtensionLike(extension)) {
return {
success: false,
error: "Module does not export a valid extension"
};
}
if (manifest) {
extension.manifest = manifest;
}
return { success: true, extension };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Failed to load from file: ${message}`
};
}
}
/**
* Load extension from NPM package
*/
async loadFromNpm(packageName) {
try {
const { name, _version } = this.parseNpmPackage(packageName);
let module;
try {
module = await import(name);
} catch {
try {
const nodeModulesPath = path.join(
process.cwd(),
"node_modules",
name
);
const packageJsonPath = path.join(nodeModulesPath, "package.json");
const packageJson = JSON.parse(
await fs.readFile(packageJsonPath, "utf-8")
);
const entryPoint = packageJson.main || "index.js";
const entryPath = path.join(nodeModulesPath, entryPoint);
module = await import(pathToFileURL(entryPath).href);
} catch {
return {
success: false,
error: `Package not found: ${name}. Try running: npm install ${name}`
};
}
}
const extension = module.default || module;
if (!this.isExtensionLike(extension)) {
return {
success: false,
error: "Package does not export a valid extension"
};
}
return { success: true, extension };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Failed to load from NPM: ${message}`
};
}
}
/**
* Evaluate extension code (for URL sources)
*/
async evaluateExtensionCode(code, _sourceUrl) {
const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
try {
const module = await import(dataUrl);
const extension = module.default || module;
if (!this.isExtensionLike(extension)) {
throw new Error("Code does not export a valid extension");
}
return extension;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to evaluate extension code: ${message}`);
}
}
/**
* Check if object looks like an extension
*/
isExtensionLike(obj) {
if (!obj || typeof obj !== "object") return false;
const ext = obj;
return typeof ext["name"] === "string" && typeof ext["version"] === "string" && typeof ext["init"] === "function" && typeof ext["destroy"] === "function";
}
/**
* Validate extension structure and metadata
*/
validateExtension(extension) {
const errors = [];
const warnings = [];
if (!extension.name || typeof extension.name !== "string") {
errors.push("Extension must have a valid name");
}
if (!extension.version || typeof extension.version !== "string") {
errors.push("Extension must have a valid version");
}
if (extension.version && !/^\d+\.\d+\.\d+/.test(extension.version)) {
warnings.push("Version should follow semver format (x.y.z)");
}
if (typeof extension.init !== "function") {
errors.push("Extension must have an init method");
}
if (typeof extension.destroy !== "function") {
errors.push("Extension must have a destroy method");
}
if (extension.tools) {
if (!Array.isArray(extension.tools)) {
errors.push("Extension tools must be an array");
} else {
extension.tools.forEach((tool, index) => {
if (!tool.name) {
errors.push(`Tool at index ${index} must have a name`);
}
if (!tool.execute || typeof tool.execute !== "function") {
errors.push(
`Tool "${tool.name || index}" must have an execute function`
);
}
});
}
}
if (extension.manifest) {
const manifestValidation = this.validateManifest(extension.manifest);
errors.push(...manifestValidation.errors);
warnings.push(...manifestValidation.warnings);
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
/**
* Validate extension manifest
*/
validateManifest(manifest) {
const errors = [];
const warnings = [];
if (!manifest.name) {
errors.push("Manifest must have a name");
}
if (!manifest.version) {
errors.push("Manifest must have a version");
}
if (!manifest.permissions || !Array.isArray(manifest.permissions)) {
errors.push("Manifest must have a permissions array");
} else {
manifest.permissions.forEach((perm) => {
if (!this.isValidPermission(perm)) {
errors.push(`Invalid permission: ${perm}`);
}
});
}
return { valid: errors.length === 0, errors, warnings };
}
/**
* Check if permission string is valid
*/
isValidPermission(permission) {
return ALLOWED_PERMISSION_PATTERNS.some(
(pattern) => pattern.test(permission)
);
}
/**
* Verify extension permissions
*/
async verifyPermissions(extension, overridePermissions) {
const requestedPermissions = overridePermissions || extension.manifest?.permissions || [];
for (const perm of requestedPermissions) {
if (perm.startsWith("network:")) {
const domain = perm.slice(8);
if (this.isBlockedDomain(domain)) {
return {
success: false,
error: `Permission denied: network access to ${domain} is blocked`
};
}
}
}
logger.debug("Permission verification passed", {
extension: extension.name,
permissions: requestedPermissions
});
return { success: true, extension };
}
/**
* Initialize extension with context
*/
async initializeExtension(extension, options) {
try {
const extensionId = this.generateExtensionId(extension);
const permissions = options.permissions || extension.manifest?.permissions || [];
const context = this.createExtensionContext(extensionId, permissions);
this.grantedPermissions.set(extensionId, new Set(permissions));
await extension.init(context);
return { success: true, extension };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Extension initialization failed: ${message}`
};
}
}
/**
* Create extension context with permission-gated access
*/
createExtensionContext(extensionId, permissions) {
const permSet = new Set(permissions);
const context = {
extensionId,
permissions,
// State management (always available, scoped to extension)
state: {
get: async (_key) => {
return void 0;
},
set: async (_key, _value) => {
},
delete: async (_key) => {
}
},
// Sandboxed fetch (always available)
fetch: this.createSandboxedFetch(extensionId, permSet),
// Event system
emit: (event, data) => {
if (!permSet.has("events:emit")) {
logger.warn("Extension lacks events:emit permission", {
extensionId,
event
});
return;
}
this.emitEvent(event, data);
},
on: (event, handler) => {
if (!permSet.has("events:listen")) {
logger.warn("Extension lacks events:listen permission", {
extensionId,
event
});
return () => {
};
}
return this.addEventListener(event, handler);
},
off: (event, handler) => {
this.removeEventListener(event, handler);
}
};
if (permSet.has("frames:read") || permSet.has("frames:write")) {
context.frames = {
get: async (_frameId) => {
return void 0;
},
list: async () => {
return [];
}
};
if (permSet.has("frames:write")) {
context.frames.create = async (_options) => {
throw new Error("Frame creation not implemented");
};
context.frames.update = async (_frameId, _data) => {
throw new Error("Frame update not implemented");
};
}
}
return context;
}
/**
* Create sandboxed fetch that respects network permissions
*/
createSandboxedFetch(extensionId, permissions) {
return async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
const parsedUrl = new URL(url);
const domain = parsedUrl.hostname;
const hasPermission = Array.from(permissions).some((perm) => {
if (!perm.startsWith("network:")) return false;
const allowedDomain = perm.slice(8);
if (allowedDomain.startsWith("*.")) {
const baseDomain = allowedDomain.slice(2);
return domain.endsWith(baseDomain);
}
return domain === allowedDomain;
});
if (!hasPermission) {
throw new Error(`Network access denied for domain: ${domain}`);
}
if (this.isBlockedDomain(domain)) {
throw new Error(`Network access blocked for domain: ${domain}`);
}
return fetch(input, init);
};
}
/**
* Check if domain is blocked
*/
isBlockedDomain(domain) {
return BLOCKED_DOMAINS.some((blocked) => {
if (blocked.startsWith("*.")) {
return domain.endsWith(blocked.slice(1));
}
if (blocked.endsWith(".*")) {
return domain.startsWith(blocked.slice(0, -1));
}
return domain === blocked;
});
}
/**
* Register loaded extension
*/
registerExtension(extensionId, extension, source, sourceType, options) {
const state = {
id: extensionId,
name: extension.name,
version: extension.version,
source,
sourceType,
permissions: options.permissions || extension.manifest?.permissions || [],
loadedAt: Date.now(),
status: "active"
};
this.loadedExtensions.set(extensionId, state);
this.extensionInstances.set(extensionId, extension);
}
/**
* Generate unique extension ID
*/
generateExtensionId(extension) {
return `${extension.name}@${extension.version}`;
}
/**
* Parse NPM package string
*/
parseNpmPackage(packageName) {
const atIndex = packageName.lastIndexOf("@");
if (atIndex > 0) {
return {
name: packageName.slice(0, atIndex),
version: packageName.slice(atIndex + 1)
};
}
return { name: packageName };
}
/**
* Wrap promise with timeout
*/
async withTimeout(promise, timeoutMs) {
return Promise.race([
promise,
new Promise(
(_, reject) => setTimeout(
() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)),
timeoutMs
)
)
]);
}
/**
* Emit event to all listeners
*/
emitEvent(event, data) {
const handlers = this.eventHandlers.get(event);
if (!handlers) return;
handlers.forEach((handler) => {
try {
const result = handler(data);
if (result instanceof Promise) {
result.catch((error) => {
logger.error("Event handler error", { event, error });
});
}
} catch (error) {
logger.error("Event handler error", { event, error });
}
});
}
/**
* Add event listener
*/
addEventListener(event, handler) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
}
this.eventHandlers.get(event).add(handler);
return () => this.removeEventListener(event, handler);
}
/**
* Remove event listener
*/
removeEventListener(event, handler) {
const handlers = this.eventHandlers.get(event);
if (handlers) {
handlers.delete(handler);
}
}
/**
* Disable an extension without unloading
*/
async disableExtension(extensionId) {
const state = this.loadedExtensions.get(extensionId);
if (!state) return false;
state.status = "disabled";
return true;
}
/**
* Enable a disabled extension
*/
async enableExtension(extensionId) {
const state = this.loadedExtensions.get(extensionId);
if (!state || state.status !== "disabled") return false;
state.status = "active";
return true;
}
/**
* Unload all extensions
*/
async unloadAll() {
const extensionIds = Array.from(this.loadedExtensions.keys());
for (const extensionId of extensionIds) {
await this.unloadExtension(extensionId);
}
}
}
let loaderInstance;
function getExtensionLoader() {
if (!loaderInstance) {
loaderInstance = new ExtensionLoader();
}
return loaderInstance;
}
async function loadExtension(source, options) {
const loader = getExtensionLoader();
return loader.loadExtension({ source, ...options });
}
async function unloadExtension(extensionId) {
const loader = getExtensionLoader();
return loader.unloadExtension(extensionId);
}
export {
ExtensionLoader,
getExtensionLoader,
loadExtension,
unloadExtension
};