AgentDocumentView.tsx4.7 KBView on GitHub 'use client';
import { useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import type { AgentTab } from '@/modules/agents/types';
import { useTRPC } from '@/providers/query-provider';
import { Skeleton } from '@/components/ui/skeleton';
import { AgentView } from './AgentView';
/**
* A subagent DOCUMENT, opened as the agent it is.
*
* An agent is a document — that is the whole design (apps/server/docs/wiki/agent-document-type.md).
* The consequence nobody had drawn until now is that opening that document should open the
* AGENT, not a markdown editor pointed at it. Five surfaces used to render a subagent doc as
* generic prose: the Brain knowledge explorer, the file browser's open-file view, the home
* artifact panel, the playground playbook editor, and the CRM-updater panel. Exactly one of
* them mounted the structured header, and all five rendered the YAML frontmatter as body
* text whenever `HideFrontmatterExtension` bailed — which it does the moment the block is
* not a clean `horizontalRule … horizontalRule` run, because it keys on node types rather
* than on content.
*
* So the fix is not a better hider. It is that a document whose `documentType` is `agent`
* has ONE rendering, and it is the workspace: Settings carries every frontmatter key as a
* field, Playbook carries the instruction body with the block hidden, and the raw YAML has
* nowhere left to appear.
*
* Keyed on `documentType`, not on the path. The path regex was duplicated in two files and
* absent from the other three, and it deliberately excluded `crm-updater.md` — which is why
* that one doc had neither a header nor hiding on any surface. `documentType: 'agent'` is
* stamped on every subagent-path write by `resolveEffectiveDocumentType`, so it is the fact
* that is actually true of all of them.
*/
export function AgentDocumentView({
documentId,
flush,
className,
targetUserId,
}: {
documentId: string;
/** Drop the workspace's own horizontal inset — the host surface already provides one. */
flush?: boolean;
className?: string;
/**
* The document's real owner, when it differs from the signed-in caller — an org
* admin/owner viewing a teammate's agent. Passed as a PROP, deliberately not read
* ambiently via `useTargetUserId()`: this component is mounted from general
* surfaces (the home artifact panel, the file browser) that carry no admin
* picker, so an ambient read here would apply whichever teammate happens to be
* selected on a DIFFERENT screen instead of the document actually on screen.
* `CompanyExplorer` derives this from the document's own `userId`
* (`documents.getDoc`), which is what makes it correct regardless of picker state.
*/
targetUserId?: string;
}) {
const trpc = useTRPC();
/**
* documentId → agentId, off the list every agent surface already holds.
*
* `agent.list` is the authorization boundary too: it enumerates the AOPs of
* `targetUserId` (or the caller, when absent — org-admin gated, see
* `agent.ts`'s `resolveActorUserId`), so a subagent document belonging to
* someone the caller cannot administer resolves to nothing here rather than
* rendering a workspace over an agent they cannot reach.
*/
const { data: agents, isLoading } = useQuery(
trpc.agent.list.queryOptions({ targetUserId }),
);
const agentId = useMemo(
() => agents?.find((a) => a.documentId === documentId)?.agentId,
[agents, documentId],
);
/**
* Config, and controlled.
*
* Opening the document is opening its configuration — that is what a subagent doc IS —
* and the tab is owned here rather than by the URL because this view is mounted INSIDE
* another surface's address (`?documentId=…`, the artifact panel, a file browser). Two
* writers of `?tab=` is the bug the workspace's own note warns about.
*/
const [tab, setTab] = useState<AgentTab>('config');
if (isLoading) {
return (
<div className={className}>
<Skeleton className="h-6 w-48" />
<Skeleton className="mt-3 h-40 w-full" />
</div>
);
}
// Not resolvable — a stale id, another user's agent, or a doc on a subagent path that
// never got an `agent_id`. Say so rather than falling back to a prose editor, which is
// what would put the raw frontmatter back on screen.
if (!agentId) {
return (
<div className={className}>
<p className="text-muted-foreground text-sm">
This is an agent document, but the agent it belongs to could not be loaded.
</p>
</div>
);
}
return (
<AgentView
agentId={agentId}
targetUserId={targetUserId}
chrome="embedded"
flush={flush ?? false}
tab={tab}
onTabChange={setTab}
className={className}
/>
);
}