Skip to content

Commit 350f79a

Browse files
committed
web: require versioned agent references
1 parent 297d1ce commit 350f79a

File tree

2 files changed

+21
-42
lines changed

2 files changed

+21
-42
lines changed

web/src/lib/__tests__/agent-tree.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ describe('buildAgentTree', () => {
1313
return async (
1414
publisher: string,
1515
agentId: string,
16-
version: string | null
16+
version: string,
1717
): Promise<AgentLookupResult | null> => {
18-
const key = version ? `${publisher}/${agentId}@${version}` : `${publisher}/${agentId}`
18+
const key = `${publisher}/${agentId}@${version}`
1919
return agents[key] ?? null
2020
}
2121
}

web/src/lib/agent-tree.ts

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
interface ParsedAgentId {
22
publisher: string
33
agentId: string
4-
version: string | null
4+
version: string
55
}
66

77
export interface AgentTreeNode {
@@ -12,7 +12,7 @@ export interface AgentTreeNode {
1212
/** Publisher ID */
1313
publisher: string
1414
/** Version string */
15-
version: string | null
15+
version: string
1616
/** Human-readable display name */
1717
displayName: string
1818
/** Description of when/why to spawn this agent */
@@ -48,50 +48,30 @@ function sanitizeIdForMermaid(id: string): string {
4848
}
4949

5050
/** Parse agent ID string (e.g. "publisher/agentId@version") */
51-
function parseAgentId(
52-
agentIdString: string,
53-
defaultPublisher?: string,
54-
): ParsedAgentId {
55-
// Fully qualified: publisher/agentId@version
51+
function parseAgentId(agentIdString: string): ParsedAgentId {
5652
const fqMatch = agentIdString.match(/^([^/]+)\/(.+)@(.+)$/)
57-
if (fqMatch) {
58-
return {
59-
publisher: fqMatch[1]!,
60-
agentId: fqMatch[2]!,
61-
version: fqMatch[3]!,
62-
}
63-
}
64-
65-
// With publisher but no version: publisher/agentId
66-
const publisherMatch = agentIdString.match(/^([^/]+)\/(.+)$/)
67-
if (publisherMatch) {
68-
return {
69-
publisher: publisherMatch[1]!,
70-
agentId: publisherMatch[2]!,
71-
version: null,
72-
}
53+
if (!fqMatch) {
54+
throw new Error(
55+
`Invalid agent reference '${agentIdString}'. Expected 'publisher/agentId@version'.`,
56+
)
7357
}
7458

75-
// Simple ID: agentId
7659
return {
77-
publisher: defaultPublisher ?? 'unknown',
78-
agentId: agentIdString,
79-
version: null,
60+
publisher: fqMatch[1]!,
61+
agentId: fqMatch[2]!,
62+
version: fqMatch[3]!,
8063
}
8164
}
8265

8366
function formatAgentId(parsed: ParsedAgentId): string {
84-
if (parsed.version) {
85-
return `${parsed.publisher}/${parsed.agentId}@${parsed.version}`
86-
}
87-
return `${parsed.publisher}/${parsed.agentId}`
67+
return `${parsed.publisher}/${parsed.agentId}@${parsed.version}`
8868
}
8969

9070
interface BuildTreeContext {
9171
lookupAgent: (
9272
publisher: string,
9373
agentId: string,
94-
version: string | null,
74+
version: string,
9575
) => Promise<AgentLookupResult | null>
9676
visitedIds: Set<string>
9777
currentDepth: number
@@ -100,10 +80,9 @@ interface BuildTreeContext {
10080

10181
async function buildTreeNodeRecursive(
10282
agentIdString: string,
103-
defaultPublisher: string,
10483
ctx: BuildTreeContext,
10584
): Promise<AgentTreeNode> {
106-
const parsed = parseAgentId(agentIdString, defaultPublisher)
85+
const parsed = parseAgentId(agentIdString)
10786
const fullId = formatAgentId(parsed)
10887

10988
// Check for cycles
@@ -146,7 +125,7 @@ async function buildTreeNodeRecursive(
146125
// Recursively build children if we haven't hit max depth
147126
if (agentData && ctx.currentDepth < ctx.maxDepth) {
148127
const childPromises = agentData.spawnableAgents.map((childId) =>
149-
buildTreeNodeRecursive(childId, parsed.publisher, {
128+
buildTreeNodeRecursive(childId, {
150129
...ctx,
151130
currentDepth: ctx.currentDepth + 1,
152131
visitedIds: new Set(ctx.visitedIds), // Clone for each branch
@@ -168,7 +147,7 @@ export async function buildAgentTree(params: {
168147
lookupAgent: (
169148
publisher: string,
170149
agentId: string,
171-
version: string | null,
150+
version: string,
172151
) => Promise<AgentLookupResult | null>
173152
maxDepth?: number
174153
}): Promise<AgentTreeData> {
@@ -188,7 +167,7 @@ export async function buildAgentTree(params: {
188167

189168
// Build children
190169
const childPromises = rootSpawnableAgents.map((childId) =>
191-
buildTreeNodeRecursive(childId, rootPublisher, {
170+
buildTreeNodeRecursive(childId, {
192171
lookupAgent,
193172
visitedIds: new Set(visitedIds),
194173
currentDepth: 1,
@@ -256,15 +235,15 @@ export function generateMermaidDiagram(tree: AgentTreeData): string {
256235

257236
function getNodeLabel(node: AgentTreeNode): string {
258237
const name = node.displayName
259-
const version = node.version ? `v${node.version}` : ''
238+
const version = `v${node.version}`
260239
// Escape special characters for Mermaid to prevent XSS
261240
const escapedName = name
262241
.replace(/"/g, "'")
263242
.replace(/</g, '&lt;')
264243
.replace(/>/g, '&gt;')
265244
.replace(/&(?!lt;|gt;|amp;)/g, '&amp;')
266245
const escapedVersion = version.replace(/</g, '&lt;').replace(/>/g, '&gt;')
267-
return escapedVersion ? `"${escapedName}<br/>${escapedVersion}"` : `"${escapedName}"`
246+
return `"${escapedName}<br/>${escapedVersion}"`
268247
}
269248

270249
function traverse(node: AgentTreeNode, parentSanitizedId: string | null) {
@@ -315,7 +294,7 @@ export interface NodeData {
315294
fullId: string
316295
agentId: string
317296
publisher: string
318-
version: string | null
297+
version: string
319298
displayName: string
320299
spawnerPrompt: string | null
321300
isAvailable: boolean

0 commit comments

Comments
 (0)