workspace-visibility.ts3.0 KBView on GitHub
/**
 * Which rows the `/brain` workspace draws — the ONE rule its tree, its search box and its
 * graph all share, so a file one of them hides cannot walk back in through another.
 */

/**
 * `{user|organisation}/agent-…` — an agent's own namespace at the root of a workspace.
 *
 * Two shapes live there and both are machine-managed: `agent-{agentId}`, the folder an
 * agent writes its output, config and memory into, and `agent-instructions`, the
 * per-playbook instruction documents the routing surface owns. Neither is a file anyone
 * keeps by hand, and there are as many of the first as the user has agents — twenty rows
 * reading "Agent 4008f724 71dc 4d3f 8f52 Afe58ae927b9" sat above the first folder the user
 * actually made. Each has a surface of its own (`/agents/:agentId`, `/brain/playbooks/routing`)
 * where it is editable in context, which is where it belongs.
 *
 * Matched on the two shapes EXACTLY, not on an `agent-` prefix. A folder a person names
 * "Agent Notes" slugifies to `agent-notes` (services/file-system/paths.ts) and lands at
 * `user/agent-notes` — a prefix test hides it from the tree, the search AND the graph at
 * once, which is a user's own folder gone from every way there is to look for it. An agent
 * id is a UUID, and `instructions` is the one other name the namespace answers to.
 *
 * ── Why a UUID and not something looser ──
 *
 * `aop_agents.id` is `text`, so nothing in the SCHEMA forces a UUID, and a looser pattern was
 * argued for on that basis. Checked against production before choosing: 2215 agents and 414
 * distinct `agent-*` root segments, none of them anything but a UUID or `agent-instructions`.
 * There is also no second signal to lean on — a namespace root is a plain `folder` row, the
 * same document type a person's own folder has — so this is a choice between two regexes, and
 * their failure modes are not comparable:
 *
 *   too loose → a folder someone MADE disappears from every view. Reads as data loss.
 *   too strict → a hypothetical non-UUID agent folder shows up as one extra row. Clutter.
 *
 * So it is deliberately strict. If agent ids ever stop being UUIDs this is the line that has to
 * change with them, and `workspaceVisibility.test.ts` is where that shows up.
 *
 * Conversation-scoped agent folders (`conversation/{id}/agent-{id}`) need no mention here —
 * `/brain` shows nothing under `conversation/` at all.
 */
const AGENT_NAMESPACE =
  /^(?:user|organisation)\/agent-(?:instructions|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\/|$)/i;

export function isAgentWorkspacePath(path: string | null | undefined): boolean {
  return AGENT_NAMESPACE.test(path ?? '');
}

/** Whether a document belongs in `/brain` at all — the tree, the search and the graph. */
export function isBrainWorkspaceDoc(doc: { path: string | null | undefined }): boolean {
  // Conversation-scoped docs are not workspace files; `/brain` never shows them.
  if ((doc.path ?? '').startsWith('conversation/')) return false;
  if (isAgentWorkspacePath(doc.path)) return false;
  return true;
}