import { pool } from '../../../db/pool.js'; const TABLE = { page: 'pages', ref: 'refs', task: 'tasks', conversation: 'conversations' }; export const readTool = { name: 'read', description: 'Read a single entity (page, ref, task, conversation) by id for grounding.', input_schema: { type: 'object', properties: { kind: { type: 'string', enum: ['page', 'ref', 'task', 'conversation'] }, id: { type: 'string', description: 'uuid of the entity' } }, required: ['kind', 'id'] }, async handler({ kind, id }, ctx = {}) { const table = TABLE[kind]; if (!table) return { error: `unknown kind "${kind}"` }; const { rows: [row] } = await pool.query(`SELECT * FROM ${table} WHERE id=$1`, [id]); if (!row) return { error: `${kind} ${id} not found` }; // Space scoping. When a Space is bound (external scoped agents — and Dross, // which operates within one Space), an entity that carries space_id must // match it. Kinds without a space_id column (conversations) can't be proven // in-scope, so they're denied to spaceScoped callers (external agents) only. // Owner/Dross with no bound Space (ctx.space_id == null) → unrestricted. if (ctx.space_id != null) { if (row.space_id !== undefined) { if (row.space_id !== ctx.space_id) return { error: `${kind} ${id} not found` }; } else if (ctx.spaceScoped) { return { error: `${kind} ${id} not found` }; } } return row; } };