Skip to content

Commit 3024958

Browse files
Merge pull request #1095 from objectstack-ai/claude/fix-sidebar-agent-tool-display
Addressing PR comments
2 parents f7be000 + 4d5a3e5 commit 3024958

File tree

13 files changed

+399
-50
lines changed

13 files changed

+399
-50
lines changed

apps/studio/mocks/node-polyfills.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const promises = {
2727
stat: async () => ({ isDirectory: () => false, isFile: () => false }),
2828
mkdir: async () => {},
2929
rm: async () => {},
30+
access: async () => { throw new Error('ENOENT: no such file or directory (polyfill)'); },
3031
};
3132

3233
// os polyfills
@@ -81,8 +82,8 @@ realpathSync.native = () => '';
8182
export const constants = {};
8283
export const lstat = async () => ({ isDirectory: () => false, isFile: () => false, isSymbolicLink: () => false });
8384
export const stat = async () => ({ isDirectory: () => false, isFile: () => false, isSymbolicLink: () => false });
84-
export const access = () => {};
85-
export const accessSync = () => {};
85+
export const access = async () => { throw new Error('ENOENT: no such file or directory (polyfill)'); };
86+
export const accessSync = () => { throw new Error('ENOENT: no such file or directory (polyfill)'); };
8687
export const mkdir = () => {};
8788
export const mkdirSync = () => {};
8889
export const rmdir = () => {};

apps/studio/src/components/CodeExporter.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
55
import { Button } from '@/components/ui/button';
66
import { Badge } from '@/components/ui/badge';
77
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
8-
import { Code2, Copy, Check, Database, Layout, Workflow, Bot, AppWindow } from 'lucide-react';
8+
import { Code2, Copy, Check, Database, Layout, Workflow, Bot, AppWindow, Wrench } from 'lucide-react';
99

1010
// ─── Types ──────────────────────────────────────────────────────────
1111

12-
type ExportType = 'object' | 'view' | 'flow' | 'agent' | 'app';
12+
type ExportType = 'object' | 'view' | 'flow' | 'agent' | 'tool' | 'app';
1313

1414
export interface CodeExporterProps {
1515
type: ExportType;
@@ -24,6 +24,7 @@ const TYPE_LABELS: Record<ExportType, { label: string; icon: React.ElementType }
2424
view: { label: 'View', icon: Layout },
2525
flow: { label: 'Flow', icon: Workflow },
2626
agent: { label: 'Agent', icon: Bot },
27+
tool: { label: 'Tool', icon: Wrench },
2728
app: { label: 'App', icon: AppWindow },
2829
};
2930

@@ -135,11 +136,26 @@ function generateAppCode(def: Record<string, unknown>, name?: string): string {
135136
return lines.join('\n');
136137
}
137138

139+
function generateToolCode(def: Record<string, unknown>, name?: string): string {
140+
const toolName = name || (def.name as string) || 'my_tool';
141+
const lines = [
142+
"import { defineStack } from '@objectstack/spec';",
143+
'',
144+
'export default defineStack({',
145+
' tools: {',
146+
` ${toolName}: ${formatValue(def, 2)},`,
147+
' },',
148+
'});',
149+
];
150+
return lines.join('\n');
151+
}
152+
138153
const CODE_GENERATORS: Record<ExportType, (def: Record<string, unknown>, name?: string) => string> = {
139154
object: generateObjectCode,
140155
view: generateViewCode,
141156
flow: generateFlowCode,
142157
agent: generateAgentCode,
158+
tool: generateToolCode,
143159
app: generateAppCode,
144160
};
145161

apps/studio/src/components/MetadataInspector.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Button } from "@/components/ui/button";
1010
import { ScrollArea } from "@/components/ui/scroll-area";
1111
import {
1212
Search, Copy, Check, ChevronRight, ChevronDown,
13-
Zap, BarChart3, FileText, Workflow, Bot, Globe, BookOpen, Shield,
13+
Zap, BarChart3, FileText, Workflow, Bot, Globe, BookOpen, Shield, Wrench,
1414
type LucideIcon,
1515
} from 'lucide-react';
1616
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
@@ -34,8 +34,12 @@ const TYPE_ICONS: Record<string, LucideIcon> = {
3434
dashboards: BarChart3,
3535
reports: FileText,
3636
flows: Workflow,
37+
agent: Bot,
3738
agents: Bot,
39+
tool: Wrench,
40+
tools: Wrench,
3841
apis: Globe,
42+
ragPipeline: BookOpen,
3943
ragPipelines: BookOpen,
4044
profiles: Shield,
4145
sharingRules: Shield,
@@ -46,8 +50,12 @@ const TYPE_LABELS: Record<string, string> = {
4650
dashboards: 'Dashboard',
4751
reports: 'Report',
4852
flows: 'Flow',
53+
agent: 'Agent',
4954
agents: 'Agent',
55+
tool: 'Tool',
56+
tools: 'Tool',
5057
apis: 'API',
58+
ragPipeline: 'RAG Pipeline',
5159
ragPipelines: 'RAG Pipeline',
5260
profiles: 'Profile',
5361
sharingRules: 'Sharing Rule',

apps/studio/src/components/app-sidebar.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
UserCog,
3333
ChevronRight,
3434
Settings,
35+
Wrench,
3536
type LucideIcon,
3637
} from "lucide-react"
3738
import { useState, useEffect, useCallback, useMemo } from "react"
@@ -94,7 +95,11 @@ const META_TYPE_HINTS: Record<string, { label: string; icon: LucideIcon }> = {
9495
profiles: { label: 'Profiles', icon: Shield },
9596
sharingRules: { label: 'Sharing Rules', icon: Shield },
9697
policies: { label: 'Policies', icon: Shield },
98+
agent: { label: 'Agents', icon: Bot },
9799
agents: { label: 'Agents', icon: Bot },
100+
tool: { label: 'Tools', icon: Wrench },
101+
tools: { label: 'Tools', icon: Wrench },
102+
ragPipeline: { label: 'RAG Pipelines', icon: BookOpen },
98103
ragPipelines: { label: 'RAG Pipelines', icon: BookOpen },
99104
apis: { label: 'APIs', icon: Globe },
100105
connectors: { label: 'Connectors', icon: Link2 },
@@ -123,7 +128,7 @@ const PROTOCOL_GROUPS: ProtocolGroup[] = [
123128
{ key: 'ui', label: 'UI', icon: AppWindow, types: ['app', 'apps', 'actions', 'views', 'pages', 'dashboards', 'reports', 'themes'] },
124129
{ key: 'automation', label: 'Automation', icon: Workflow, types: ['flows', 'workflows', 'approvals', 'webhooks'] },
125130
{ key: 'security', label: 'Security', icon: Shield, types: ['roles', 'permissions', 'profiles', 'sharingRules', 'policies'] },
126-
{ key: 'ai', label: 'AI', icon: Bot, types: ['agents', 'ragPipelines'] },
131+
{ key: 'ai', label: 'AI', icon: Bot, types: ['agent', 'agents', 'tool', 'tools', 'ragPipeline', 'ragPipelines'] },
127132
{ key: 'api', label: 'API', icon: Globe, types: ['apis', 'connectors'] },
128133
];
129134

@@ -211,12 +216,32 @@ export function AppSidebar({
211216
} else if (Array.isArray(typesResult)) {
212217
types = typesResult as any;
213218
}
214-
setMetaTypes(types);
219+
220+
// Normalize types: prefer singular form (agent, tool) over plural (agents, tools)
221+
// when both exist in PROTOCOL_GROUPS, since the singular REST endpoint merges
222+
// SchemaRegistry items with MetadataService runtime items.
223+
const groupSingulars = new Set(PROTOCOL_GROUPS.flatMap(g => g.types).filter(t => !t.endsWith('s')));
224+
const normalized = types.map(t => {
225+
if (t.endsWith('s') && groupSingulars.has(t.slice(0, -1))) {
226+
return t.slice(0, -1); // agents → agent, tools → tool
227+
}
228+
return t;
229+
});
230+
// Also add group types that aren't covered at all by the server types
231+
const groupTypes = PROTOCOL_GROUPS.flatMap(g => g.types);
232+
const coveredSet = new Set(normalized);
233+
const extraTypes = groupTypes.filter(t => {
234+
if (coveredSet.has(t)) return false;
235+
const variant = t.endsWith('s') ? t.slice(0, -1) : t + 's';
236+
return !coveredSet.has(variant);
237+
});
238+
const allTypes = Array.from(new Set([...normalized, ...extraTypes]));
239+
setMetaTypes(allTypes);
215240

216241
const packageId = selectedPackage?.manifest?.id;
217242

218243
const entries = await Promise.all(
219-
types
244+
allTypes
220245
.filter(t => !HIDDEN_TYPES.has(t))
221246
.map(async (type) => {
222247
try {

apps/studio/src/lib/create-broker-shim.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,22 @@ export function createBrokerShim(kernel: any): BrokerShim {
178178
}
179179

180180
if (service === 'metadata') {
181+
// Get MetadataService for runtime-registered metadata (agents, tools, etc.)
182+
const metadataService = kernel.context?.getService('metadata');
183+
181184
if (method === 'types') {
182-
return { types: SchemaRegistry.getRegisteredTypes() };
185+
// Combine types from both SchemaRegistry (static) and MetadataService (runtime)
186+
const schemaTypes = SchemaRegistry.getRegisteredTypes();
187+
188+
// MetadataService exposes types through getRegisteredTypes() method
189+
let runtimeTypes: string[] = [];
190+
if (metadataService && typeof metadataService.getRegisteredTypes === 'function') {
191+
runtimeTypes = await metadataService.getRegisteredTypes();
192+
}
193+
194+
// Merge and deduplicate
195+
const allTypes = Array.from(new Set([...schemaTypes, ...runtimeTypes]));
196+
return { types: allTypes };
183197
}
184198
if (method === 'objects') {
185199
const packageId = params.packageId;
@@ -206,9 +220,33 @@ export function createBrokerShim(kernel: any): BrokerShim {
206220
}
207221
return def || null;
208222
}
209-
// Generic metadata type: metadata.<type> → SchemaRegistry.listItems(type, packageId?)
223+
// Generic metadata type: metadata.<type> → check both SchemaRegistry and MetadataService
210224
const packageId = params.packageId;
211-
const items = SchemaRegistry.listItems(method, packageId);
225+
226+
// Try SchemaRegistry first (static metadata from packages)
227+
let items = SchemaRegistry.listItems(method, packageId);
228+
229+
// Also check MetadataService for runtime-registered metadata (agents, tools, etc.)
230+
if (metadataService && typeof metadataService.list === 'function') {
231+
try {
232+
const runtimeItems = await metadataService.list(method);
233+
if (runtimeItems && runtimeItems.length > 0) {
234+
// Merge items, avoiding duplicates by name
235+
const itemMap = new Map();
236+
items.forEach((item: any) => itemMap.set(item.name, item));
237+
runtimeItems.forEach((item: any) => {
238+
if (item && typeof item === 'object' && 'name' in item) {
239+
itemMap.set(item.name, item);
240+
}
241+
});
242+
items = Array.from(itemMap.values());
243+
}
244+
} catch (err) {
245+
// MetadataService.list might fail for unknown types, that's OK
246+
console.debug(`[BrokerShim] MetadataService.list('${method}') failed:`, err);
247+
}
248+
}
249+
212250
if (items && items.length > 0) {
213251
return { type: method, items };
214252
}

apps/studio/src/mocks/simulateBrowser.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ export async function simulateBrowser() {
142142
}
143143
}),
144144

145+
// Metadata - Get all types (base route returns types)
146+
http.get('http://localhost:3000/api/v1/meta', async () => {
147+
console.log('[VirtualNetwork] GET /meta (types)');
148+
try {
149+
const result = await (kernel as any).broker.call('metadata.types', {});
150+
return HttpResponse.json({ success: true, data: result });
151+
} catch (err: any) {
152+
return HttpResponse.json({ error: err.message }, { status: 500 });
153+
}
154+
}),
155+
145156
// Metadata - Objects List (Singular & Plural support)
146157
http.get('http://localhost:3000/api/v1/meta/object', async () => {
147158
console.log('[VirtualNetwork] GET /meta/object');
@@ -194,6 +205,22 @@ export async function simulateBrowser() {
194205
} catch (err: any) {
195206
return HttpResponse.json({ error: err.message }, { status: 500 });
196207
}
208+
}),
209+
210+
// Metadata - Generic type list (for agents, tools, etc.)
211+
// This must come AFTER specific routes like /meta/object to avoid conflicts
212+
http.get('http://localhost:3000/api/v1/meta/:type', async ({ params }) => {
213+
// Skip if it's a specific route we already handled
214+
if (params.type === 'object' || params.type === 'objects') {
215+
return;
216+
}
217+
console.log(`[VirtualNetwork] GET /meta/${params.type}`);
218+
try {
219+
const result = await (kernel as any).broker.call(`metadata.${params.type}`, {});
220+
return HttpResponse.json({ success: true, data: result });
221+
} catch (err: any) {
222+
return HttpResponse.json({ error: err.message }, { status: 500 });
223+
}
197224
})
198225
];
199226

apps/studio/src/plugins/built-in/ai-plugin.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { defineStudioPlugin } from '@objectstack/spec/studio';
1111
import type { StudioPlugin } from '../types';
12-
import { Bot, BookOpen } from 'lucide-react';
12+
import { Bot, BookOpen, Wrench } from 'lucide-react';
1313

1414
export const aiProtocolPlugin: StudioPlugin = {
1515
manifest: defineStudioPlugin({
@@ -23,19 +23,21 @@ export const aiProtocolPlugin: StudioPlugin = {
2323
key: 'ai',
2424
label: 'AI',
2525
icon: 'bot',
26-
metadataTypes: ['agents', 'ragPipelines'],
26+
metadataTypes: ['agent', 'tool', 'ragPipeline'],
2727
order: 50,
2828
},
2929
],
3030
metadataIcons: [
31-
{ metadataType: 'agents', label: 'Agents', icon: 'bot' },
32-
{ metadataType: 'ragPipelines', label: 'RAG Pipelines', icon: 'book-open' },
31+
{ metadataType: 'agent', label: 'Agents', icon: 'bot' },
32+
{ metadataType: 'tool', label: 'Tools', icon: 'wrench' },
33+
{ metadataType: 'ragPipeline', label: 'RAG Pipelines', icon: 'book-open' },
3334
],
3435
},
3536
}),
3637

3738
activate(api) {
38-
api.registerMetadataIcon('agents', Bot, 'Agents');
39-
api.registerMetadataIcon('ragPipelines', BookOpen, 'RAG Pipelines');
39+
api.registerMetadataIcon('agent', Bot, 'Agents');
40+
api.registerMetadataIcon('tool', Wrench, 'Tools');
41+
api.registerMetadataIcon('ragPipeline', BookOpen, 'RAG Pipelines');
4042
},
4143
};

apps/studio/src/plugins/built-in/default-plugin.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const METADATA_TO_EXPORT_TYPE: Record<string, CodeExporterProps['type']> = {
3030
flows: 'flow',
3131
agent: 'agent',
3232
agents: 'agent',
33+
tool: 'tool',
34+
tools: 'tool',
3335
app: 'app',
3436
apps: 'app',
3537
};

0 commit comments

Comments
 (0)