UNPKG

@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

212 lines (209 loc) 6.39 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { logger } from "../../../core/monitoring/logger.js"; class TeamHandlers { constructor(deps) { this.deps = deps; } /** * Get context from other agents working on the same project. * Returns recent frames and shared anchors from other sessions. */ async handleTeamContextGet(args) { try { const limit = args.limit ?? 10; const types = args.types; const since = args.since; const projectId = this.getProjectId(); const runId = this.getRunId(); const frames = await this.deps.dbAdapter.getRecentFramesExcludingRun( projectId, runId, { limit, types, since } ); const sharedAnchors = await this.deps.dbAdapter.getSharedAnchors( projectId, { limit, since } ); if (frames.length === 0 && sharedAnchors.length === 0) { return { content: [ { type: "text", text: "No context from other agents found in this project." } ] }; } const frameSummaries = frames.map((f) => ({ frame_id: f.frame_id, run_id: f.run_id, name: f.name, type: f.type, state: f.state, created_at: f.created_at, digest_text: f.digest_text || null, anchors: f.anchors.map((a) => ({ type: a.type, text: a.text, priority: a.priority })) })); const sharedSummaries = sharedAnchors.map((a) => ({ type: a.type, text: a.text, priority: a.priority, frame_name: a.frame_name, run_id: a.run_id, shared_by: a.metadata?.sharedBy })); const text = `Team Context (${frames.length} frames, ${sharedAnchors.length} shared anchors): ` + frameSummaries.map( (f) => `[${f.run_id?.slice(0, 8)}] ${f.name} (${f.type}, ${f.state})` + (f.digest_text ? ` ${f.digest_text}` : "") + (f.anchors.length > 0 ? "\n Anchors: " + f.anchors.map((a) => `${a.type}: ${a.text}`).join("; ") : "") ).join("\n") + (sharedSummaries.length > 0 ? "\n\nShared Anchors:\n" + sharedSummaries.map((a) => ` [${a.type}] ${a.text} (priority: ${a.priority})`).join("\n") : ""); return { content: [{ type: "text", text }], metadata: { frames: frameSummaries, sharedAnchors: sharedSummaries } }; } catch (error) { logger.error( "Error getting team context", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Share a piece of context with other agents. * Creates a high-priority anchor tagged with shared=true. */ async handleTeamContextShare(args) { try { const { content, type = "FACT", priority = 8 } = args; if (!content) { throw new Error("content is required"); } const validTypes = [ "FACT", "DECISION", "CONSTRAINT", "INTERFACE_CONTRACT", "TODO", "RISK" ]; if (!validTypes.includes(type)) { throw new Error( `Invalid type. Must be one of: ${validTypes.join(", ")}` ); } let frameId = this.deps.frameManager.getCurrentFrameId(); if (!frameId) { frameId = this.deps.frameManager.createFrame({ type: "tool_scope", name: "team_context_share" }); } const runId = this.getRunId(); this.deps.frameManager.addAnchor(type, content, priority, { shared: true, sharedBy: runId, sharedAt: Date.now() }); logger.info("Shared team context", { type, priority }); return { content: [ { type: "text", text: `Shared ${type}: ${content}` } ] }; } catch (error) { logger.error( "Error sharing team context", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Search across all agents' context in the project. * Uses FTS5 search without run_id filtering. */ async handleTeamSearch(args) { try { const { query, limit = 20, include_events = false } = args; if (!query) { throw new Error("query is required"); } const projectId = this.getProjectId(); const results = await this.deps.dbAdapter.search({ query, projectId, limit }); if (results.length === 0) { return { content: [ { type: "text", text: `No results found for "${query}" across team sessions.` } ] }; } const frameResults = results.map((f) => ({ frame_id: f.frame_id, run_id: f.run_id, name: f.name, type: f.type, state: f.state, score: f.score, digest_text: f.digest_text || null, created_at: f.created_at, events: [] })); if (include_events) { for (const fr of frameResults) { const events = await this.deps.dbAdapter.getFrameEvents(fr.frame_id, { limit: 5 }); fr.events = events.map((e) => ({ event_type: e.event_type, payload: e.payload, ts: e.ts })); } } const text = `Team Search: ${results.length} results for "${query}": ` + frameResults.map( (f) => `[${f.run_id?.slice(0, 8)}] ${f.name} (score: ${f.score?.toFixed(3)})` + (f.digest_text ? ` ${f.digest_text}` : "") ).join("\n"); return { content: [{ type: "text", text }], metadata: { results: frameResults, total: results.length } }; } catch (error) { logger.error( "Error in team search", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** Extract projectId from the dbAdapter (protected field on DatabaseAdapter) */ getProjectId() { return this.deps.dbAdapter.projectId; } /** Extract current runId from frameManager (private field) */ getRunId() { return this.deps.frameManager.currentRunId; } } export { TeamHandlers };