@objectstack/service-ai 4.0.4 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/index.cjs +1176 -135
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1225 -430
  4. package/dist/index.d.ts +1225 -430
  5. package/dist/index.js +1160 -128
  6. package/dist/index.js.map +1 -1
  7. package/package.json +35 -8
  8. package/.turbo/turbo-build.log +0 -22
  9. package/CHANGELOG.md +0 -61
  10. package/src/__tests__/ai-service.test.ts +0 -981
  11. package/src/__tests__/auth-and-toolcalling.test.ts +0 -677
  12. package/src/__tests__/chatbot-features.test.ts +0 -1116
  13. package/src/__tests__/metadata-tools.test.ts +0 -970
  14. package/src/__tests__/objectql-conversation-service.test.ts +0 -382
  15. package/src/__tests__/tool-routes.test.ts +0 -191
  16. package/src/__tests__/vercel-stream-encoder.test.ts +0 -310
  17. package/src/adapters/index.ts +0 -6
  18. package/src/adapters/memory-adapter.ts +0 -72
  19. package/src/adapters/types.ts +0 -3
  20. package/src/adapters/vercel-adapter.ts +0 -148
  21. package/src/agent-runtime.ts +0 -154
  22. package/src/agents/data-chat-agent.ts +0 -79
  23. package/src/agents/index.ts +0 -4
  24. package/src/agents/metadata-assistant-agent.ts +0 -87
  25. package/src/ai-service.ts +0 -364
  26. package/src/conversation/in-memory-conversation-service.ts +0 -103
  27. package/src/conversation/index.ts +0 -4
  28. package/src/conversation/objectql-conversation-service.ts +0 -301
  29. package/src/index.ts +0 -60
  30. package/src/objects/ai-conversation.object.ts +0 -86
  31. package/src/objects/ai-message.object.ts +0 -86
  32. package/src/objects/index.ts +0 -10
  33. package/src/plugin.ts +0 -391
  34. package/src/routes/agent-routes.ts +0 -190
  35. package/src/routes/ai-routes.ts +0 -439
  36. package/src/routes/index.ts +0 -5
  37. package/src/routes/message-utils.ts +0 -90
  38. package/src/routes/tool-routes.ts +0 -142
  39. package/src/stream/index.ts +0 -3
  40. package/src/stream/vercel-stream-encoder.ts +0 -153
  41. package/src/tools/add-field.tool.ts +0 -70
  42. package/src/tools/create-object.tool.ts +0 -66
  43. package/src/tools/data-tools.ts +0 -293
  44. package/src/tools/delete-field.tool.ts +0 -38
  45. package/src/tools/describe-object.tool.ts +0 -31
  46. package/src/tools/index.ts +0 -18
  47. package/src/tools/list-objects.tool.ts +0 -34
  48. package/src/tools/metadata-tools.ts +0 -430
  49. package/src/tools/modify-field.tool.ts +0 -44
  50. package/src/tools/tool-registry.ts +0 -132
  51. package/tsconfig.json +0 -17
  52. package/vitest.config.ts +0 -23
package/dist/index.cjs CHANGED
@@ -256,6 +256,10 @@ var init_create_object_tool = __esm({
256
256
  type: "string",
257
257
  description: "Human-readable display name (e.g. Project Task)"
258
258
  },
259
+ packageId: {
260
+ type: "string",
261
+ description: "Package ID that will own this object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
262
+ },
259
263
  fields: {
260
264
  type: "array",
261
265
  description: "Initial fields to create with the object",
@@ -305,6 +309,10 @@ var init_add_field_tool = __esm({
305
309
  parameters: {
306
310
  type: "object",
307
311
  properties: {
312
+ packageId: {
313
+ type: "string",
314
+ description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
315
+ },
308
316
  objectName: {
309
317
  type: "string",
310
318
  description: "Target object machine name (snake_case)"
@@ -371,6 +379,10 @@ var init_modify_field_tool = __esm({
371
379
  parameters: {
372
380
  type: "object",
373
381
  properties: {
382
+ packageId: {
383
+ type: "string",
384
+ description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
385
+ },
374
386
  objectName: {
375
387
  type: "string",
376
388
  description: "Target object machine name (snake_case)"
@@ -416,6 +428,10 @@ var init_delete_field_tool = __esm({
416
428
  parameters: {
417
429
  type: "object",
418
430
  properties: {
431
+ packageId: {
432
+ type: "string",
433
+ description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
434
+ },
419
435
  objectName: {
420
436
  type: "string",
421
437
  description: "Target object machine name (snake_case)"
@@ -504,12 +520,55 @@ __export(metadata_tools_exports, {
504
520
  function isSnakeCase(value) {
505
521
  return SNAKE_CASE_RE.test(value);
506
522
  }
523
+ async function getActivePackageId(ctx) {
524
+ if (!ctx.conversationService?.getMetadata || !ctx.conversationId) {
525
+ return null;
526
+ }
527
+ const metadata = await ctx.conversationService.getMetadata(ctx.conversationId);
528
+ return metadata?.activePackageId ?? null;
529
+ }
530
+ async function resolvePackageId(ctx, explicitPackageId) {
531
+ let packageId = null;
532
+ if (explicitPackageId) {
533
+ packageId = explicitPackageId;
534
+ } else {
535
+ packageId = await getActivePackageId(ctx);
536
+ }
537
+ if (!packageId) {
538
+ return {
539
+ packageId: null,
540
+ warning: "No package specified. Metadata will be created without package association. Consider using set_active_package or providing packageId parameter."
541
+ };
542
+ }
543
+ if (ctx.packageRegistry) {
544
+ const exists = await ctx.packageRegistry.exists(packageId);
545
+ if (!exists) {
546
+ return {
547
+ packageId: null,
548
+ error: `Package "${packageId}" not found. Use list_packages to see available packages or create_package to create a new one.`
549
+ };
550
+ }
551
+ const pkg = await ctx.packageRegistry.get(packageId);
552
+ if (pkg?.manifest.source === "filesystem") {
553
+ return {
554
+ packageId: null,
555
+ error: `Package "${packageId}" is read-only (loaded from code). Only database packages can be modified. Use create_package to create a new database package.`
556
+ };
557
+ }
558
+ }
559
+ return { packageId };
560
+ }
507
561
  function createCreateObjectHandler(ctx) {
508
562
  return async (args) => {
509
- const { name, label, fields, enableFeatures } = args;
563
+ const { name, label, packageId: explicitPackageId, fields, enableFeatures } = args;
510
564
  if (!name || !label) {
511
565
  return JSON.stringify({ error: 'Both "name" and "label" are required' });
512
566
  }
567
+ const resolved = await resolvePackageId(ctx, explicitPackageId);
568
+ if (resolved.error) {
569
+ return JSON.stringify({ error: resolved.error });
570
+ }
571
+ const packageId = resolved.packageId;
513
572
  if (!isSnakeCase(name)) {
514
573
  return JSON.stringify({ error: `Invalid object name "${name}". Must be snake_case.` });
515
574
  }
@@ -541,6 +600,7 @@ function createCreateObjectHandler(ctx) {
541
600
  const objectDef = {
542
601
  name,
543
602
  label,
603
+ ...packageId ? { packageId } : {},
544
604
  ...Object.keys(fieldMap).length > 0 ? { fields: fieldMap } : {},
545
605
  ...enableFeatures ? { enable: enableFeatures } : {}
546
606
  };
@@ -548,16 +608,21 @@ function createCreateObjectHandler(ctx) {
548
608
  return JSON.stringify({
549
609
  name,
550
610
  label,
611
+ ...packageId ? { packageId } : {},
551
612
  fieldCount: Object.keys(fieldMap).length
552
613
  });
553
614
  };
554
615
  }
555
616
  function createAddFieldHandler(ctx) {
556
617
  return async (args) => {
557
- const { objectName, name, label, type, required, defaultValue, options, reference } = args;
618
+ const { objectName, name, label, type, required, defaultValue, options, reference, packageId: explicitPackageId } = args;
558
619
  if (!objectName || !name || !type) {
559
620
  return JSON.stringify({ error: '"objectName", "name", and "type" are required' });
560
621
  }
622
+ const resolved = await resolvePackageId(ctx, explicitPackageId);
623
+ if (resolved.error) {
624
+ return JSON.stringify({ error: resolved.error });
625
+ }
561
626
  if (!isSnakeCase(objectName)) {
562
627
  return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
563
628
  }
@@ -598,16 +663,21 @@ function createAddFieldHandler(ctx) {
598
663
  return JSON.stringify({
599
664
  objectName,
600
665
  fieldName: name,
601
- fieldType: type
666
+ fieldType: type,
667
+ packageId: resolved.packageId
602
668
  });
603
669
  };
604
670
  }
605
671
  function createModifyFieldHandler(ctx) {
606
672
  return async (args) => {
607
- const { objectName, fieldName, changes } = args;
673
+ const { objectName, fieldName, changes, packageId: explicitPackageId } = args;
608
674
  if (!objectName || !fieldName || !changes) {
609
675
  return JSON.stringify({ error: '"objectName", "fieldName", and "changes" are required' });
610
676
  }
677
+ const resolved = await resolvePackageId(ctx, explicitPackageId);
678
+ if (resolved.error) {
679
+ return JSON.stringify({ error: resolved.error });
680
+ }
611
681
  if (!isSnakeCase(objectName)) {
612
682
  return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
613
683
  }
@@ -632,16 +702,21 @@ function createModifyFieldHandler(ctx) {
632
702
  return JSON.stringify({
633
703
  objectName,
634
704
  fieldName,
635
- updatedProperties: Object.keys(changes)
705
+ updatedProperties: Object.keys(changes),
706
+ packageId: resolved.packageId
636
707
  });
637
708
  };
638
709
  }
639
710
  function createDeleteFieldHandler(ctx) {
640
711
  return async (args) => {
641
- const { objectName, fieldName } = args;
712
+ const { objectName, fieldName, packageId: explicitPackageId } = args;
642
713
  if (!objectName || !fieldName) {
643
714
  return JSON.stringify({ error: '"objectName" and "fieldName" are required' });
644
715
  }
716
+ const resolved = await resolvePackageId(ctx, explicitPackageId);
717
+ if (resolved.error) {
718
+ return JSON.stringify({ error: resolved.error });
719
+ }
645
720
  if (!isSnakeCase(objectName)) {
646
721
  return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
647
722
  }
@@ -664,7 +739,8 @@ function createDeleteFieldHandler(ctx) {
664
739
  return JSON.stringify({
665
740
  objectName,
666
741
  fieldName,
667
- success: true
742
+ success: true,
743
+ packageId: resolved.packageId
668
744
  });
669
745
  };
670
746
  }
@@ -781,21 +857,30 @@ __export(index_exports, {
781
857
  METADATA_TOOL_DEFINITIONS: () => METADATA_TOOL_DEFINITIONS,
782
858
  MemoryLLMAdapter: () => MemoryLLMAdapter,
783
859
  ObjectQLConversationService: () => ObjectQLConversationService,
860
+ PACKAGE_TOOL_DEFINITIONS: () => PACKAGE_TOOL_DEFINITIONS,
861
+ SkillRegistry: () => SkillRegistry,
784
862
  ToolRegistry: () => ToolRegistry,
785
863
  VercelLLMAdapter: () => VercelLLMAdapter,
786
864
  addFieldTool: () => addFieldTool,
787
865
  buildAIRoutes: () => buildAIRoutes,
788
866
  buildAgentRoutes: () => buildAgentRoutes,
867
+ buildAssistantRoutes: () => buildAssistantRoutes,
789
868
  buildToolRoutes: () => buildToolRoutes,
790
869
  createObjectTool: () => createObjectTool,
870
+ createPackageTool: () => createPackageTool,
791
871
  deleteFieldTool: () => deleteFieldTool,
792
872
  describeObjectTool: () => describeObjectTool,
793
873
  encodeStreamPart: () => encodeStreamPart,
794
874
  encodeVercelDataStream: () => encodeVercelDataStream,
875
+ getActivePackageTool: () => getActivePackageTool,
876
+ getPackageTool: () => getPackageTool,
795
877
  listObjectsTool: () => listObjectsTool,
878
+ listPackagesTool: () => listPackagesTool,
796
879
  modifyFieldTool: () => modifyFieldTool,
797
880
  registerDataTools: () => registerDataTools,
798
- registerMetadataTools: () => registerMetadataTools
881
+ registerMetadataTools: () => registerMetadataTools,
882
+ registerPackageTools: () => registerPackageTools,
883
+ setActivePackageTool: () => setActivePackageTool
799
884
  });
800
885
  module.exports = __toCommonJS(index_exports);
801
886
 
@@ -1310,25 +1395,41 @@ async function* encodeVercelDataStream(events) {
1310
1395
  yield sse({ type: "text-start", id: "0" });
1311
1396
  let textOpen = true;
1312
1397
  let finishReason = "stop";
1313
- for await (const part of events) {
1314
- if (part.type === "finish") {
1315
- finishReason = part.finishReason ?? "stop";
1316
- }
1317
- if (part.type === "finish-step" || part.type === "finish") {
1318
- if (textOpen) {
1319
- yield sse({ type: "text-end", id: "0" });
1320
- textOpen = false;
1398
+ let errorMessage;
1399
+ try {
1400
+ for await (const part of events) {
1401
+ if (part.type === "error") {
1402
+ const errPart = part;
1403
+ const raw = errPart.error;
1404
+ errorMessage = raw && typeof raw === "object" && "message" in raw ? String(raw.message) : typeof raw === "string" ? raw : "Unknown provider error";
1405
+ finishReason = "error";
1406
+ break;
1407
+ }
1408
+ if (part.type === "finish") {
1409
+ finishReason = part.finishReason ?? "stop";
1410
+ }
1411
+ if (part.type === "finish-step" || part.type === "finish") {
1412
+ if (textOpen) {
1413
+ yield sse({ type: "text-end", id: "0" });
1414
+ textOpen = false;
1415
+ }
1416
+ continue;
1417
+ }
1418
+ const frame = encodeStreamPart(part);
1419
+ if (frame) {
1420
+ yield frame;
1321
1421
  }
1322
- continue;
1323
- }
1324
- const frame = encodeStreamPart(part);
1325
- if (frame) {
1326
- yield frame;
1327
1422
  }
1423
+ } catch (err) {
1424
+ errorMessage = err instanceof Error ? err.message : String(err);
1425
+ finishReason = "error";
1328
1426
  }
1329
1427
  if (textOpen) {
1330
1428
  yield sse({ type: "text-end", id: "0" });
1331
1429
  }
1430
+ if (errorMessage) {
1431
+ yield sse({ type: "error", errorText: errorMessage });
1432
+ }
1332
1433
  yield sse({ type: "finish-step" });
1333
1434
  yield sse({ type: "finish", finishReason });
1334
1435
  yield "data: [DONE]\n\n";
@@ -1732,10 +1833,12 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
1732
1833
  return { status: 403, body: { error: `Agent "${agentName}" is not active` } };
1733
1834
  }
1734
1835
  try {
1735
- const systemMessages = agentRuntime.buildSystemMessages(agent, chatContext);
1836
+ const activeSkills = await agentRuntime.resolveActiveSkills(agent, chatContext);
1837
+ const systemMessages = agentRuntime.buildSystemMessages(agent, chatContext, activeSkills);
1736
1838
  const agentOptions = agentRuntime.buildRequestOptions(
1737
1839
  agent,
1738
- aiService.toolRegistry.getAll()
1840
+ aiService.toolRegistry.getAll(),
1841
+ activeSkills
1739
1842
  );
1740
1843
  const safeOverrides = {};
1741
1844
  if (extraOptions) {
@@ -1789,6 +1892,217 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
1789
1892
  ];
1790
1893
  }
1791
1894
 
1895
+ // src/routes/assistant-routes.ts
1896
+ var ALLOWED_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
1897
+ function validateAssistantMessage(raw) {
1898
+ if (typeof raw !== "object" || raw === null) {
1899
+ return "each message must be an object";
1900
+ }
1901
+ const msg = raw;
1902
+ if (typeof msg.role !== "string" || !ALLOWED_ROLES.has(msg.role)) {
1903
+ return `message.role must be one of ${[...ALLOWED_ROLES].map((r) => `"${r}"`).join(", ")} for assistant chat`;
1904
+ }
1905
+ const allowEmpty = msg.role === "assistant";
1906
+ return validateMessageContent(msg, { allowEmptyContent: allowEmpty });
1907
+ }
1908
+ function parseContext(raw) {
1909
+ if (typeof raw !== "object" || raw === null) return {};
1910
+ const obj = raw;
1911
+ const ctx = {};
1912
+ for (const key of Object.keys(obj)) {
1913
+ ctx[key] = obj[key];
1914
+ }
1915
+ return ctx;
1916
+ }
1917
+ function buildAssistantRoutes(aiService, agentRuntime, skillRegistry, logger) {
1918
+ return [
1919
+ // ── Resolve current assistant + skill set ──────────────────
1920
+ {
1921
+ method: "GET",
1922
+ path: "/api/v1/ai/assistant",
1923
+ description: "Resolve the default AI assistant and active skills for a given context",
1924
+ auth: true,
1925
+ permissions: ["ai:chat"],
1926
+ handler: async (req) => {
1927
+ try {
1928
+ const context = parseContextFromQuery(req.query);
1929
+ const explicitAgentName = typeof req.query?.agent === "string" ? req.query.agent : void 0;
1930
+ const agent = explicitAgentName ? await agentRuntime.loadAgent(explicitAgentName) : await agentRuntime.resolveDefaultAgent(context);
1931
+ if (!agent) {
1932
+ return {
1933
+ status: 200,
1934
+ body: { agent: null, skills: [] }
1935
+ };
1936
+ }
1937
+ const skills = await agentRuntime.resolveActiveSkills(agent, context);
1938
+ return {
1939
+ status: 200,
1940
+ body: {
1941
+ agent: {
1942
+ name: agent.name,
1943
+ label: agent.label,
1944
+ role: agent.role,
1945
+ avatar: agent.avatar,
1946
+ instructions: agent.instructions
1947
+ },
1948
+ skills: skills.map((s) => skillRegistry.toSummary(s)),
1949
+ context
1950
+ }
1951
+ };
1952
+ } catch (err) {
1953
+ logger.error("[AI Route] /assistant error", err instanceof Error ? err : void 0);
1954
+ return { status: 500, body: { error: "Internal AI service error" } };
1955
+ }
1956
+ }
1957
+ },
1958
+ // ── List active skills (slash-command palette) ─────────────
1959
+ {
1960
+ method: "GET",
1961
+ path: "/api/v1/ai/assistant/skills",
1962
+ description: "List active AI skills for a given context (used by slash-command palettes)",
1963
+ auth: true,
1964
+ permissions: ["ai:chat"],
1965
+ handler: async (req) => {
1966
+ try {
1967
+ const context = parseContextFromQuery(req.query);
1968
+ const agentName = typeof req.query?.agent === "string" ? req.query.agent : void 0;
1969
+ let restrictTo;
1970
+ if (agentName) {
1971
+ const agent = await agentRuntime.loadAgent(agentName);
1972
+ if (agent?.skills) restrictTo = agent.skills;
1973
+ }
1974
+ const skills = await skillRegistry.listActiveSkills(context, restrictTo);
1975
+ return {
1976
+ status: 200,
1977
+ body: { skills: skills.map((s) => skillRegistry.toSummary(s)) }
1978
+ };
1979
+ } catch (err) {
1980
+ logger.error("[AI Route] /assistant/skills error", err instanceof Error ? err : void 0);
1981
+ return { status: 500, body: { error: "Internal AI service error" } };
1982
+ }
1983
+ }
1984
+ },
1985
+ // ── Ambient chat (the "single entry" Claude-Code pattern) ──
1986
+ {
1987
+ method: "POST",
1988
+ path: "/api/v1/ai/assistant/chat",
1989
+ description: "Ambient AI chat \u2014 auto-resolves agent and skills from context (supports Vercel Data Stream Protocol)",
1990
+ auth: true,
1991
+ permissions: ["ai:chat"],
1992
+ handler: async (req) => {
1993
+ const body = req.body ?? {};
1994
+ const {
1995
+ messages: rawMessages,
1996
+ context: rawContext,
1997
+ options: extraOptions,
1998
+ agent: explicitAgentName,
1999
+ skill: explicitSkillName
2000
+ } = body;
2001
+ if (!Array.isArray(rawMessages) || rawMessages.length === 0) {
2002
+ return { status: 400, body: { error: "messages array is required" } };
2003
+ }
2004
+ for (const msg of rawMessages) {
2005
+ const err = validateAssistantMessage(msg);
2006
+ if (err) return { status: 400, body: { error: err } };
2007
+ }
2008
+ const context = parseContext(rawContext);
2009
+ const agent = explicitAgentName ? await agentRuntime.loadAgent(explicitAgentName) : await agentRuntime.resolveDefaultAgent(context);
2010
+ if (!agent) {
2011
+ return {
2012
+ status: 404,
2013
+ body: { error: "No active assistant available \u2014 register at least one agent or set defaultAgent on the app metadata" }
2014
+ };
2015
+ }
2016
+ if (agent.active === false) {
2017
+ return { status: 403, body: { error: `Agent "${agent.name}" is not active` } };
2018
+ }
2019
+ try {
2020
+ let activeSkills = await agentRuntime.resolveActiveSkills(agent, context);
2021
+ if (explicitSkillName) {
2022
+ activeSkills = activeSkills.filter((s) => s.name === explicitSkillName);
2023
+ if (activeSkills.length === 0) {
2024
+ const direct = await skillRegistry.loadSkill(explicitSkillName);
2025
+ if (direct && direct.active !== false) activeSkills = [direct];
2026
+ }
2027
+ }
2028
+ const systemMessages = agentRuntime.buildSystemMessages(agent, context, activeSkills);
2029
+ const agentOptions = agentRuntime.buildRequestOptions(
2030
+ agent,
2031
+ aiService.toolRegistry.getAll(),
2032
+ activeSkills
2033
+ );
2034
+ const safeOverrides = {};
2035
+ if (extraOptions) {
2036
+ const ALLOWED_KEYS = /* @__PURE__ */ new Set(["temperature", "maxTokens", "stop"]);
2037
+ for (const key of Object.keys(extraOptions)) {
2038
+ if (ALLOWED_KEYS.has(key)) safeOverrides[key] = extraOptions[key];
2039
+ }
2040
+ }
2041
+ const mergedOptions = { ...agentOptions, ...safeOverrides };
2042
+ const fullMessages = [
2043
+ ...systemMessages,
2044
+ ...rawMessages.map((m) => normalizeMessage(m))
2045
+ ];
2046
+ const chatWithToolsOptions = {
2047
+ ...mergedOptions,
2048
+ maxIterations: agent.planning?.maxIterations
2049
+ };
2050
+ const wantStream = body.stream !== false;
2051
+ if (wantStream) {
2052
+ if (!aiService.streamChatWithTools) {
2053
+ return { status: 501, body: { error: "Streaming is not supported by the configured AI service" } };
2054
+ }
2055
+ const events = aiService.streamChatWithTools(fullMessages, chatWithToolsOptions);
2056
+ return {
2057
+ status: 200,
2058
+ stream: true,
2059
+ vercelDataStream: true,
2060
+ contentType: "text/event-stream",
2061
+ headers: {
2062
+ "Content-Type": "text/event-stream",
2063
+ "Cache-Control": "no-cache",
2064
+ "Connection": "keep-alive",
2065
+ "x-vercel-ai-ui-message-stream": "v1",
2066
+ "x-objectstack-agent": agent.name,
2067
+ "x-objectstack-skills": activeSkills.map((s) => s.name).join(",")
2068
+ },
2069
+ events: encodeVercelDataStream(events)
2070
+ };
2071
+ }
2072
+ const result = await aiService.chatWithTools(fullMessages, chatWithToolsOptions);
2073
+ return {
2074
+ status: 200,
2075
+ body: {
2076
+ ...result ?? {},
2077
+ _agent: agent.name,
2078
+ _skills: activeSkills.map((s) => s.name)
2079
+ }
2080
+ };
2081
+ } catch (err) {
2082
+ logger.error(
2083
+ "[AI Route] /assistant/chat error",
2084
+ err instanceof Error ? err : void 0
2085
+ );
2086
+ const upstreamMsg = err instanceof Error ? err.message : String(err);
2087
+ return {
2088
+ status: 500,
2089
+ body: { error: "Internal AI service error", detail: upstreamMsg }
2090
+ };
2091
+ }
2092
+ }
2093
+ }
2094
+ ];
2095
+ }
2096
+ function parseContextFromQuery(query) {
2097
+ if (!query) return {};
2098
+ const ctx = {};
2099
+ for (const [key, value] of Object.entries(query)) {
2100
+ if (value == null) continue;
2101
+ ctx[key] = value;
2102
+ }
2103
+ return ctx;
2104
+ }
2105
+
1792
2106
  // src/routes/tool-routes.ts
1793
2107
  function extractOutputValue(output) {
1794
2108
  if (!output) return "";
@@ -2104,8 +2418,7 @@ var ObjectQLConversationService = class {
2104
2418
  // src/objects/ai-conversation.object.ts
2105
2419
  var import_data = require("@objectstack/spec/data");
2106
2420
  var AiConversationObject = import_data.ObjectSchema.create({
2107
- namespace: "ai",
2108
- name: "conversations",
2421
+ name: "ai_conversations",
2109
2422
  label: "AI Conversation",
2110
2423
  pluralLabel: "AI Conversations",
2111
2424
  icon: "message-square",
@@ -2123,16 +2436,14 @@ var AiConversationObject = import_data.ObjectSchema.create({
2123
2436
  maxLength: 500,
2124
2437
  description: "Conversation title or summary"
2125
2438
  }),
2126
- agent_id: import_data.Field.text({
2127
- label: "Agent ID",
2439
+ agent_id: import_data.Field.lookup("sys_agent", {
2440
+ label: "Agent",
2128
2441
  required: false,
2129
- maxLength: 255,
2130
- description: "Associated AI agent identifier"
2442
+ description: "Associated AI agent"
2131
2443
  }),
2132
- user_id: import_data.Field.text({
2133
- label: "User ID",
2444
+ user_id: import_data.Field.lookup("sys_user", {
2445
+ label: "User",
2134
2446
  required: false,
2135
- maxLength: 255,
2136
2447
  description: "User who owns the conversation"
2137
2448
  }),
2138
2449
  metadata: import_data.Field.textarea({
@@ -2171,8 +2482,7 @@ var AiConversationObject = import_data.ObjectSchema.create({
2171
2482
  // src/objects/ai-message.object.ts
2172
2483
  var import_data2 = require("@objectstack/spec/data");
2173
2484
  var AiMessageObject = import_data2.ObjectSchema.create({
2174
- namespace: "ai",
2175
- name: "messages",
2485
+ name: "ai_messages",
2176
2486
  label: "AI Message",
2177
2487
  pluralLabel: "AI Messages",
2178
2488
  icon: "message-circle",
@@ -2184,8 +2494,8 @@ var AiMessageObject = import_data2.ObjectSchema.create({
2184
2494
  required: true,
2185
2495
  readonly: true
2186
2496
  }),
2187
- conversation_id: import_data2.Field.text({
2188
- label: "Conversation ID",
2497
+ conversation_id: import_data2.Field.lookup("ai_conversations", {
2498
+ label: "Conversation",
2189
2499
  required: true,
2190
2500
  description: "Foreign key to ai_conversations"
2191
2501
  }),
@@ -2243,8 +2553,9 @@ init_metadata_tools();
2243
2553
  // src/agent-runtime.ts
2244
2554
  var import_ai7 = require("@objectstack/spec/ai");
2245
2555
  var AgentRuntime = class {
2246
- constructor(metadataService) {
2556
+ constructor(metadataService, skillRegistry) {
2247
2557
  this.metadataService = metadataService;
2558
+ this.skillRegistry = skillRegistry;
2248
2559
  }
2249
2560
  // ── Public API ────────────────────────────────────────────────
2250
2561
  /**
@@ -2288,12 +2599,21 @@ var AgentRuntime = class {
2288
2599
  /**
2289
2600
  * Build the system message(s) that should be prepended to the
2290
2601
  * conversation when chatting with the given agent.
2602
+ *
2603
+ * The composed prompt has up to three sections:
2604
+ * 1. The agent's base `instructions` (its persona / prime directives).
2605
+ * 2. UI context hints from {@link AgentChatContext} (current object,
2606
+ * record, view) so the agent can tailor responses without extra
2607
+ * tool calls.
2608
+ * 3. An "Active Skills" block describing the capabilities currently
2609
+ * available — only populated when `activeSkills` is provided.
2291
2610
  */
2292
- buildSystemMessages(agent, context) {
2611
+ buildSystemMessages(agent, context, activeSkills) {
2293
2612
  const parts = [];
2294
2613
  parts.push(agent.instructions);
2295
2614
  if (context) {
2296
2615
  const ctx = [];
2616
+ if (context.appName) ctx.push(`Current app: ${context.appName}`);
2297
2617
  if (context.objectName) ctx.push(`Current object: ${context.objectName}`);
2298
2618
  if (context.recordId) ctx.push(`Selected record ID: ${context.recordId}`);
2299
2619
  if (context.viewName) ctx.push(`Current view: ${context.viewName}`);
@@ -2301,6 +2621,10 @@ var AgentRuntime = class {
2301
2621
  parts.push("\n--- Current Context ---\n" + ctx.join("\n"));
2302
2622
  }
2303
2623
  }
2624
+ if (activeSkills && activeSkills.length > 0 && this.skillRegistry) {
2625
+ const block = this.skillRegistry.composeInstructionsBlock(activeSkills);
2626
+ if (block) parts.push(block);
2627
+ }
2304
2628
  return [{ role: "system", content: parts.join("\n") }];
2305
2629
  }
2306
2630
  /**
@@ -2308,37 +2632,272 @@ var AgentRuntime = class {
2308
2632
  *
2309
2633
  * Tool references declared in `agent.tools` are resolved by name against
2310
2634
  * `availableTools` (i.e. the full set of ToolRegistry definitions).
2311
- * Any unresolved references (tools the agent declares but that are not
2312
- * registered) are silently skipped this is intentional so that agents
2313
- * can be defined before all tools are available.
2635
+ * Tools belonging to `activeSkills` are also resolved and merged into
2636
+ * the final tool list (deduplicated by name).
2637
+ *
2638
+ * Any unresolved references (tools the agent or skill declares but
2639
+ * that are not registered) are silently skipped — this is intentional
2640
+ * so that agents/skills can be defined before all tools are available.
2314
2641
  *
2315
2642
  * @param agent - The agent definition to derive options from
2316
2643
  * @param availableTools - All tool definitions currently registered in the ToolRegistry
2644
+ * @param activeSkills - Skills resolved from agent.skills[] + context filtering
2317
2645
  * @returns Request options with model config and resolved tool definitions
2318
2646
  */
2319
- buildRequestOptions(agent, availableTools) {
2647
+ buildRequestOptions(agent, availableTools, activeSkills) {
2320
2648
  const options = {};
2321
2649
  if (agent.model) {
2322
2650
  options.model = agent.model.model;
2323
2651
  options.temperature = agent.model.temperature;
2324
2652
  options.maxTokens = agent.model.maxTokens;
2325
2653
  }
2654
+ const toolMap = new Map(availableTools.map((t) => [t.name, t]));
2655
+ const seen = /* @__PURE__ */ new Set();
2656
+ const resolved = [];
2326
2657
  if (agent.tools && agent.tools.length > 0) {
2327
- const toolMap = new Map(availableTools.map((t) => [t.name, t]));
2328
- const resolved = [];
2329
2658
  for (const ref of agent.tools) {
2659
+ if (seen.has(ref.name)) continue;
2330
2660
  const def = toolMap.get(ref.name);
2331
2661
  if (def) {
2332
2662
  resolved.push(def);
2663
+ seen.add(ref.name);
2333
2664
  }
2334
2665
  }
2335
- if (resolved.length > 0) {
2336
- options.tools = resolved;
2337
- options.toolChoice = "auto";
2666
+ }
2667
+ if (activeSkills && activeSkills.length > 0 && this.skillRegistry) {
2668
+ const skillTools = this.skillRegistry.flattenToTools(activeSkills, availableTools);
2669
+ for (const def of skillTools) {
2670
+ if (seen.has(def.name)) continue;
2671
+ resolved.push(def);
2672
+ seen.add(def.name);
2338
2673
  }
2339
2674
  }
2675
+ if (resolved.length > 0) {
2676
+ options.tools = resolved;
2677
+ options.toolChoice = "auto";
2678
+ }
2340
2679
  return options;
2341
2680
  }
2681
+ // ── Skill resolution helpers ─────────────────────────────────
2682
+ /**
2683
+ * Resolve the set of skills active for a given agent in a given
2684
+ * context. Combines:
2685
+ *
2686
+ * 1. The agent's declared `skills[]` whitelist (if any).
2687
+ * 2. Filtering by `triggerConditions` against the runtime context.
2688
+ *
2689
+ * When the agent declares no skills, returns the empty list (i.e.
2690
+ * the agent only uses its inline `tools[]`).
2691
+ *
2692
+ * Returns an empty array if no SkillRegistry was provided to the
2693
+ * runtime (legacy mode).
2694
+ */
2695
+ async resolveActiveSkills(agent, context) {
2696
+ if (!this.skillRegistry) return [];
2697
+ if (!agent.skills || agent.skills.length === 0) return [];
2698
+ return this.skillRegistry.listActiveSkills(context ?? {}, agent.skills);
2699
+ }
2700
+ /**
2701
+ * Pick a default agent for the given context, used by the ambient
2702
+ * chat endpoint when the client doesn't specify an `agentName`.
2703
+ *
2704
+ * Resolution order:
2705
+ * 1. The `defaultAgent` of the app named by `context.appName`.
2706
+ * 2. The first active agent in the registry (deterministic fallback).
2707
+ * 3. `undefined` if no agents are registered.
2708
+ */
2709
+ async resolveDefaultAgent(context) {
2710
+ if (context?.appName) {
2711
+ const rawApp = await this.metadataService.get("app", context.appName).catch(() => void 0);
2712
+ const defaultAgentName = rawApp?.defaultAgent;
2713
+ if (defaultAgentName) {
2714
+ const agent = await this.loadAgent(defaultAgentName);
2715
+ if (agent && agent.active !== false) return agent;
2716
+ }
2717
+ }
2718
+ const summaries = await this.listAgents();
2719
+ if (summaries.length === 0) return void 0;
2720
+ return this.loadAgent(summaries[0].name);
2721
+ }
2722
+ };
2723
+
2724
+ // src/skill-registry.ts
2725
+ var import_ai8 = require("@objectstack/spec/ai");
2726
+ var SkillRegistry = class {
2727
+ constructor(metadataService) {
2728
+ this.metadataService = metadataService;
2729
+ }
2730
+ // ── Loading ────────────────────────────────────────────────────
2731
+ /**
2732
+ * Load and validate a single skill definition by name.
2733
+ *
2734
+ * Returns `undefined` when the skill is missing or fails Zod
2735
+ * validation (so callers don't accidentally feed malformed metadata
2736
+ * to the LLM).
2737
+ */
2738
+ async loadSkill(skillName) {
2739
+ const raw = await this.metadataService.get("skill", skillName);
2740
+ if (!raw) return void 0;
2741
+ const result = import_ai8.SkillSchema.safeParse(raw);
2742
+ if (!result.success) return void 0;
2743
+ return result.data;
2744
+ }
2745
+ /**
2746
+ * Load all skill definitions, dropping any that fail validation
2747
+ * or are explicitly inactive.
2748
+ */
2749
+ async listSkills() {
2750
+ const raw = await this.metadataService.list("skill");
2751
+ const skills = [];
2752
+ for (const item of raw) {
2753
+ const result = import_ai8.SkillSchema.safeParse(item);
2754
+ if (result.success && result.data.active !== false) {
2755
+ skills.push(result.data);
2756
+ }
2757
+ }
2758
+ return skills;
2759
+ }
2760
+ /**
2761
+ * Load only the skills referenced by `skillNames`, preserving
2762
+ * declaration order. Missing or invalid skill names are silently
2763
+ * dropped (logged at the route layer if needed) so an Agent can be
2764
+ * defined before all its skills are persisted.
2765
+ */
2766
+ async loadSkills(skillNames) {
2767
+ const skills = [];
2768
+ for (const name of skillNames) {
2769
+ const skill = await this.loadSkill(name);
2770
+ if (skill && skill.active !== false) {
2771
+ skills.push(skill);
2772
+ }
2773
+ }
2774
+ return skills;
2775
+ }
2776
+ // ── Context filtering ──────────────────────────────────────────
2777
+ /**
2778
+ * Return skills whose `triggerConditions` are satisfied by the
2779
+ * given context. Skills without any conditions are always considered
2780
+ * active and returned in their declaration order.
2781
+ *
2782
+ * If `restrictTo` is provided, the result is intersected with that
2783
+ * allow-list (typically the agent's `skills[]` field) so an agent
2784
+ * never sees skills outside its declared scope.
2785
+ */
2786
+ async listActiveSkills(context = {}, restrictTo) {
2787
+ const allowList = restrictTo ? new Set(restrictTo) : void 0;
2788
+ const all = await this.listSkills();
2789
+ return all.filter((skill) => {
2790
+ if (allowList && !allowList.has(skill.name)) return false;
2791
+ return this.matchesContext(skill, context);
2792
+ });
2793
+ }
2794
+ /**
2795
+ * Evaluate a skill's `triggerConditions` against the given context.
2796
+ *
2797
+ * Semantics:
2798
+ * - No conditions defined → always matches.
2799
+ * - All conditions must pass (logical AND).
2800
+ * - Operators: `eq`, `neq`, `in`, `not_in`, `contains`.
2801
+ * - `contains` does substring matching for strings and `Array.includes`
2802
+ * for arrays.
2803
+ * - Missing context fields fail unless the operator is `neq` /
2804
+ * `not_in` (treating "absent" as "not equal to anything").
2805
+ */
2806
+ matchesContext(skill, context) {
2807
+ const conditions = skill.triggerConditions;
2808
+ if (!conditions || conditions.length === 0) return true;
2809
+ return conditions.every((cond) => this.evaluateCondition(cond, context));
2810
+ }
2811
+ evaluateCondition(cond, context) {
2812
+ const fieldValue = context[cond.field];
2813
+ const expected = cond.value;
2814
+ switch (cond.operator) {
2815
+ case "eq":
2816
+ return fieldValue === expected;
2817
+ case "neq":
2818
+ return fieldValue !== expected;
2819
+ case "in": {
2820
+ const list = Array.isArray(expected) ? expected : [expected];
2821
+ return list.includes(fieldValue);
2822
+ }
2823
+ case "not_in": {
2824
+ const list = Array.isArray(expected) ? expected : [expected];
2825
+ return !list.includes(fieldValue);
2826
+ }
2827
+ case "contains": {
2828
+ if (typeof fieldValue === "string" && typeof expected === "string") {
2829
+ return fieldValue.includes(expected);
2830
+ }
2831
+ if (Array.isArray(fieldValue)) {
2832
+ return Array.isArray(expected) ? expected.every((v) => fieldValue.includes(v)) : fieldValue.includes(expected);
2833
+ }
2834
+ return false;
2835
+ }
2836
+ default:
2837
+ return false;
2838
+ }
2839
+ }
2840
+ // ── Tool resolution ───────────────────────────────────────────
2841
+ /**
2842
+ * Flatten a list of skills to a deduplicated array of concrete tool
2843
+ * definitions, preserving the order skills declared their tools.
2844
+ *
2845
+ * Tools that are declared by a skill but missing from the available
2846
+ * tool registry are silently dropped — this is intentional so a skill
2847
+ * can be authored before all its underlying tools are registered.
2848
+ */
2849
+ flattenToTools(skills, availableTools) {
2850
+ const toolMap = new Map(availableTools.map((t) => [t.name, t]));
2851
+ const seen = /* @__PURE__ */ new Set();
2852
+ const resolved = [];
2853
+ for (const skill of skills) {
2854
+ for (const toolName of skill.tools) {
2855
+ if (seen.has(toolName)) continue;
2856
+ const def = toolMap.get(toolName);
2857
+ if (def) {
2858
+ resolved.push(def);
2859
+ seen.add(toolName);
2860
+ }
2861
+ }
2862
+ }
2863
+ return resolved;
2864
+ }
2865
+ // ── System-prompt composition ─────────────────────────────────
2866
+ /**
2867
+ * Build the "Active Skills" block to append to an agent's system
2868
+ * prompt. The block lists each skill's label + instructions so the
2869
+ * LLM knows which capabilities are available and how to invoke them.
2870
+ *
2871
+ * Returns an empty string when there are no skills, so the caller
2872
+ * can safely concatenate without producing dangling whitespace.
2873
+ */
2874
+ composeInstructionsBlock(skills) {
2875
+ if (skills.length === 0) return "";
2876
+ const lines = ["", "--- Active Skills ---"];
2877
+ for (const skill of skills) {
2878
+ lines.push(`
2879
+ ### ${skill.label} (${skill.name})`);
2880
+ if (skill.description) lines.push(skill.description);
2881
+ if (skill.instructions) lines.push(skill.instructions);
2882
+ if (skill.tools.length > 0) {
2883
+ lines.push(`Tools: ${skill.tools.join(", ")}`);
2884
+ }
2885
+ }
2886
+ return lines.join("\n");
2887
+ }
2888
+ /**
2889
+ * Project a skill to a wire-friendly summary suitable for the
2890
+ * `/api/v1/ai/skills` endpoint and slash-command palettes.
2891
+ */
2892
+ toSummary(skill) {
2893
+ return {
2894
+ name: skill.name,
2895
+ label: skill.label,
2896
+ description: skill.description,
2897
+ triggerPhrases: skill.triggerPhrases,
2898
+ toolCount: skill.tools.length
2899
+ };
2900
+ }
2342
2901
  };
2343
2902
 
2344
2903
  // src/agents/data-chat-agent.ts
@@ -2348,34 +2907,15 @@ var DATA_CHAT_AGENT = {
2348
2907
  role: "Business Data Analyst",
2349
2908
  instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
2350
2909
 
2351
- Capabilities:
2352
- - List available data objects (tables) and their schemas
2353
- - Query records with filters, sorting, and pagination
2354
- - Look up individual records by ID
2355
- - Perform aggregations and statistical analysis (count, sum, avg, min, max)
2356
-
2357
- Guidelines:
2358
- 1. Always use the describe_object tool first to understand a table's structure before querying it.
2359
- 2. Respect the user's current context \u2014 if they are viewing a specific object or record, use that as the default scope.
2360
- 3. When presenting data, format it in a clear and readable way using markdown tables or bullet lists.
2361
- 4. For large result sets, summarize the data and mention the total count.
2362
- 5. When performing aggregations, explain the results in plain language.
2363
- 6. If a query returns no results, suggest possible reasons and alternative queries.
2364
- 7. Never expose internal IDs unless the user explicitly asks for them.
2365
- 8. Always answer in the same language the user is using.`,
2910
+ Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
2366
2911
  model: {
2367
2912
  provider: "openai",
2368
2913
  model: "gpt-4",
2369
2914
  temperature: 0.3,
2370
2915
  maxTokens: 4096
2371
2916
  },
2372
- tools: [
2373
- { type: "query", name: "list_objects", description: "List all available data objects" },
2374
- { type: "query", name: "describe_object", description: "Get schema/fields of a data object" },
2375
- { type: "query", name: "query_records", description: "Query records with filters and pagination" },
2376
- { type: "query", name: "get_record", description: "Get a single record by ID" },
2377
- { type: "query", name: "aggregate_data", description: "Aggregate/statistics on data" }
2378
- ],
2917
+ // Capability bundle lives on the skill; the agent only references it.
2918
+ skills: ["data_explorer"],
2379
2919
  active: true,
2380
2920
  visibility: "global",
2381
2921
  guardrails: {
@@ -2403,39 +2943,15 @@ var METADATA_ASSISTANT_AGENT = {
2403
2943
  role: "Schema Architect",
2404
2944
  instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
2405
2945
 
2406
- Capabilities:
2407
- - Create new data objects (tables) with fields
2408
- - Add fields (columns) to existing objects
2409
- - Modify field properties (label, type, required, default value)
2410
- - Delete fields from objects
2411
- - List all registered metadata objects and their schemas
2412
- - Describe the full schema of a specific object
2413
-
2414
- Guidelines:
2415
- 1. Before creating a new object, use list_objects to check if a similar one already exists.
2416
- 2. Before modifying or deleting fields, use describe_object to understand the current schema.
2417
- 3. Always use snake_case for object names and field names (e.g. project_task, due_date).
2418
- 4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
2419
- 5. When creating objects, propose a reasonable set of initial fields based on the entity type.
2420
- 6. Explain what changes you are about to make before executing them.
2421
- 7. After making changes, confirm the result by describing the updated schema.
2422
- 8. For destructive operations (deleting fields), always warn the user about potential data loss.
2423
- 9. Always answer in the same language the user is using.
2424
- 10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
2946
+ Always answer in the same language the user is using. If the user's request is ambiguous, ask clarifying questions before proceeding. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
2425
2947
  model: {
2426
2948
  provider: "openai",
2427
2949
  model: "gpt-4",
2428
2950
  temperature: 0.2,
2429
2951
  maxTokens: 4096
2430
2952
  },
2431
- tools: [
2432
- { type: "action", name: "create_object", description: "Create a new data object (table)" },
2433
- { type: "action", name: "add_field", description: "Add a field to an existing object" },
2434
- { type: "action", name: "modify_field", description: "Modify an existing field definition" },
2435
- { type: "action", name: "delete_field", description: "Delete a field from an object" },
2436
- { type: "query", name: "list_objects", description: "List all data objects" },
2437
- { type: "query", name: "describe_object", description: "Describe an object schema" }
2438
- ],
2953
+ // Capability bundle lives on the skill; the agent only references it.
2954
+ skills: ["metadata_authoring"],
2439
2955
  active: true,
2440
2956
  visibility: "global",
2441
2957
  guardrails: {
@@ -2456,8 +2972,100 @@ Guidelines:
2456
2972
  }
2457
2973
  };
2458
2974
 
2975
+ // src/skills/data-explorer-skill.ts
2976
+ var DATA_EXPLORER_SKILL = {
2977
+ name: "data_explorer",
2978
+ label: "Data Explorer",
2979
+ description: "Read-only Q&A over the user's business data \u2014 schema discovery, filtered queries, lookups, and aggregations.",
2980
+ instructions: `You can explore the user's business data through these tools.
2981
+
2982
+ Capabilities:
2983
+ - List available data objects (tables) and their schemas
2984
+ - Query records with filters, sorting, and pagination
2985
+ - Look up individual records by ID
2986
+ - Perform aggregations and statistical analysis (count, sum, avg, min, max)
2987
+
2988
+ Guidelines:
2989
+ 1. Always use the describe_object tool first to understand a table's structure before querying it.
2990
+ 2. Respect the user's current context \u2014 if they are viewing a specific object or record, use that as the default scope.
2991
+ 3. When presenting data, format it in a clear and readable way using markdown tables or bullet lists.
2992
+ 4. For large result sets, summarize the data and mention the total count.
2993
+ 5. When performing aggregations, explain the results in plain language.
2994
+ 6. If a query returns no results, suggest possible reasons and alternative queries.
2995
+ 7. Never expose internal IDs unless the user explicitly asks for them.
2996
+ 8. Always answer in the same language the user is using.`,
2997
+ tools: [
2998
+ "list_objects",
2999
+ "describe_object",
3000
+ "query_records",
3001
+ "get_record",
3002
+ "aggregate_data"
3003
+ ],
3004
+ triggerPhrases: [
3005
+ "show me",
3006
+ "list",
3007
+ "how many",
3008
+ "count",
3009
+ "find records",
3010
+ "query",
3011
+ "aggregate",
3012
+ "sum",
3013
+ "average"
3014
+ ],
3015
+ active: true
3016
+ };
3017
+
3018
+ // src/skills/metadata-authoring-skill.ts
3019
+ var METADATA_AUTHORING_SKILL = {
3020
+ name: "metadata_authoring",
3021
+ label: "Metadata Authoring",
3022
+ description: "Create and modify ObjectStack metadata \u2014 objects, fields, schema changes through natural language.",
3023
+ instructions: `You are an expert metadata architect. When the user asks you to design or change a data model, use these tools.
3024
+
3025
+ Capabilities:
3026
+ - Create new data objects (tables) with fields
3027
+ - Add fields (columns) to existing objects
3028
+ - Modify field properties (label, type, required, default value)
3029
+ - Delete fields from objects
3030
+ - List all registered metadata objects and their schemas
3031
+ - Describe the full schema of a specific object
3032
+
3033
+ Guidelines:
3034
+ 1. Before creating a new object, use list_objects to check if a similar one already exists.
3035
+ 2. Before modifying or deleting fields, use describe_object to understand the current schema.
3036
+ 3. Always use snake_case for object names and field names (e.g. project_task, due_date).
3037
+ 4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
3038
+ 5. When creating objects, propose a reasonable set of initial fields based on the entity type.
3039
+ 6. Explain what changes you are about to make before executing them.
3040
+ 7. After making changes, confirm the result by describing the updated schema.
3041
+ 8. For destructive operations (deleting fields), always warn the user about potential data loss.
3042
+ 9. Always answer in the same language the user is using.
3043
+ 10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
3044
+ tools: [
3045
+ "create_object",
3046
+ "add_field",
3047
+ "modify_field",
3048
+ "delete_field",
3049
+ "list_objects",
3050
+ "describe_object"
3051
+ ],
3052
+ triggerPhrases: [
3053
+ "create object",
3054
+ "create table",
3055
+ "add field",
3056
+ "add column",
3057
+ "modify field",
3058
+ "change field",
3059
+ "delete field",
3060
+ "drop field",
3061
+ "design schema",
3062
+ "new entity"
3063
+ ],
3064
+ active: true
3065
+ };
3066
+
2459
3067
  // src/adapters/vercel-adapter.ts
2460
- var import_ai8 = require("ai");
3068
+ var import_ai9 = require("ai");
2461
3069
  function buildVercelOptions(options) {
2462
3070
  if (!options) return {};
2463
3071
  const opts = {};
@@ -2467,9 +3075,9 @@ function buildVercelOptions(options) {
2467
3075
  if (options.tools?.length) {
2468
3076
  const tools = {};
2469
3077
  for (const t of options.tools) {
2470
- tools[t.name] = (0, import_ai8.tool)({
3078
+ tools[t.name] = (0, import_ai9.tool)({
2471
3079
  description: t.description,
2472
- inputSchema: (0, import_ai8.jsonSchema)(t.parameters)
3080
+ inputSchema: (0, import_ai9.jsonSchema)(t.parameters)
2473
3081
  });
2474
3082
  }
2475
3083
  opts.tools = tools;
@@ -2485,7 +3093,7 @@ var VercelLLMAdapter = class {
2485
3093
  this.model = config.model;
2486
3094
  }
2487
3095
  async chat(messages, options) {
2488
- const result = await (0, import_ai8.generateText)({
3096
+ const result = await (0, import_ai9.generateText)({
2489
3097
  model: this.model,
2490
3098
  messages,
2491
3099
  ...buildVercelOptions(options)
@@ -2502,7 +3110,7 @@ var VercelLLMAdapter = class {
2502
3110
  };
2503
3111
  }
2504
3112
  async complete(prompt, options) {
2505
- const result = await (0, import_ai8.generateText)({
3113
+ const result = await (0, import_ai9.generateText)({
2506
3114
  model: this.model,
2507
3115
  prompt,
2508
3116
  ...buildVercelOptions(options)
@@ -2518,13 +3126,20 @@ var VercelLLMAdapter = class {
2518
3126
  };
2519
3127
  }
2520
3128
  async *streamChat(messages, options) {
2521
- const result = (0, import_ai8.streamText)({
3129
+ const result = (0, import_ai9.streamText)({
2522
3130
  model: this.model,
2523
3131
  messages,
2524
3132
  ...buildVercelOptions(options)
2525
3133
  });
2526
- for await (const part of result.fullStream) {
2527
- yield part;
3134
+ try {
3135
+ for await (const part of result.fullStream) {
3136
+ yield part;
3137
+ }
3138
+ } catch (err) {
3139
+ yield {
3140
+ type: "error",
3141
+ error: err instanceof Error ? err : new Error(String(err))
3142
+ };
2528
3143
  }
2529
3144
  }
2530
3145
  async embed(_input) {
@@ -2606,11 +3221,14 @@ var AIServicePlugin = class {
2606
3221
  /* webpackIgnore: true */
2607
3222
  pkg
2608
3223
  );
2609
- const createModel = mod[factory] ?? mod.default;
2610
- if (typeof createModel === "function") {
3224
+ const provider = mod[factory] ?? mod.default;
3225
+ if (typeof provider === "function") {
2611
3226
  const modelId = process.env.AI_MODEL ?? defaultModel;
2612
- const adapter = new VercelLLMAdapter({ model: createModel(modelId) });
2613
- return { adapter, description: `${displayName} (model: ${modelId})` };
3227
+ const useChatApi = factory === "openai" && typeof provider.chat === "function";
3228
+ const model = useChatApi ? provider.chat(modelId) : provider(modelId);
3229
+ const adapter = new VercelLLMAdapter({ model });
3230
+ const apiSuffix = useChatApi ? " [chat-completions]" : "";
3231
+ return { adapter, description: `${displayName} (model: ${modelId})${apiSuffix}` };
2614
3232
  }
2615
3233
  } catch (err) {
2616
3234
  ctx.logger.warn(
@@ -2671,6 +3289,7 @@ var AIServicePlugin = class {
2671
3289
  name: "AI Service",
2672
3290
  version: "1.0.0",
2673
3291
  type: "plugin",
3292
+ scope: "project",
2674
3293
  namespace: "ai",
2675
3294
  objects: [AiConversationObject, AiMessageObject]
2676
3295
  });
@@ -2679,25 +3298,12 @@ var AIServicePlugin = class {
2679
3298
  ctx.logger.debug("[AI] Before chat", { messages });
2680
3299
  });
2681
3300
  }
2682
- try {
2683
- const setupNav = ctx.getService("setupNav");
2684
- if (setupNav) {
2685
- setupNav.contribute({
2686
- areaId: "area_ai",
2687
- items: [
2688
- { id: "nav_ai_conversations", type: "object", label: "Conversations", objectName: "conversations", icon: "message-square", order: 10 },
2689
- { id: "nav_ai_messages", type: "object", label: "Messages", objectName: "messages", icon: "messages-square", order: 20 }
2690
- ]
2691
- });
2692
- ctx.logger.info("[AI] Navigation items contributed to Setup App");
2693
- }
2694
- } catch {
2695
- }
2696
3301
  ctx.logger.info("[AI] Service initialized");
2697
3302
  }
2698
3303
  async start(ctx) {
2699
3304
  if (!this.service) return;
2700
3305
  let metadataService;
3306
+ const withTimeout = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
2701
3307
  try {
2702
3308
  metadataService = ctx.getService("metadata");
2703
3309
  console.log("[AI Plugin] Retrieved metadata service:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
@@ -2705,6 +3311,13 @@ var AIServicePlugin = class {
2705
3311
  console.log("[AI] Metadata service not available:", e.message);
2706
3312
  ctx.logger.debug("[AI] Metadata service not available");
2707
3313
  }
3314
+ if (metadataService && typeof metadataService.exists === "function") {
3315
+ const probeResult = await withTimeout(metadataService.exists("tool", "__probe__"), 3e3);
3316
+ if (probeResult === null) {
3317
+ ctx.logger.warn("[AI] Metadata service unreachable (timed out) \u2014 AI tools/agents will work but Studio visibility unavailable");
3318
+ metadataService = void 0;
3319
+ }
3320
+ }
2708
3321
  try {
2709
3322
  const dataEngine = ctx.getService("data");
2710
3323
  if (dataEngine) {
@@ -2713,18 +3326,31 @@ var AIServicePlugin = class {
2713
3326
  if (metadataService) {
2714
3327
  const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
2715
3328
  for (const toolDef of DATA_TOOL_DEFINITIONS2) {
2716
- const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
3329
+ const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
3330
+ if (toolExists === null) {
3331
+ ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
3332
+ break;
3333
+ }
2717
3334
  if (!toolExists) {
2718
- await metadataService.register("tool", toolDef.name, toolDef);
3335
+ try {
3336
+ await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
3337
+ } catch (err) {
3338
+ ctx.logger.warn(
3339
+ "[AI] Failed to persist tool metadata (non-fatal)",
3340
+ err instanceof Error ? { tool: toolDef.name, error: err.message } : { tool: toolDef.name }
3341
+ );
3342
+ }
2719
3343
  }
2720
3344
  }
2721
3345
  ctx.logger.info(`[AI] ${DATA_TOOL_DEFINITIONS2.length} data tools registered as metadata`);
2722
3346
  }
2723
3347
  if (metadataService) {
2724
3348
  try {
2725
- const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", DATA_CHAT_AGENT.name) : false;
2726
- if (!agentExists) {
2727
- await metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT);
3349
+ const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
3350
+ if (agentExists === null) {
3351
+ ctx.logger.warn("[AI] Metadata service timed out checking data_chat agent, skipping");
3352
+ } else if (!agentExists) {
3353
+ await withTimeout(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
2728
3354
  console.log("[AI] Registered data_chat agent to metadataService");
2729
3355
  ctx.logger.info("[AI] data_chat agent registered");
2730
3356
  } else {
@@ -2734,6 +3360,19 @@ var AIServicePlugin = class {
2734
3360
  } catch (err) {
2735
3361
  ctx.logger.warn("[AI] Failed to register data_chat agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
2736
3362
  }
3363
+ try {
3364
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
3365
+ if (skillExists === null) {
3366
+ ctx.logger.warn("[AI] Metadata service timed out checking data_explorer skill, skipping");
3367
+ } else if (!skillExists) {
3368
+ await withTimeout(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
3369
+ ctx.logger.info("[AI] data_explorer skill registered");
3370
+ } else {
3371
+ ctx.logger.debug("[AI] data_explorer skill already exists, skipping auto-registration");
3372
+ }
3373
+ } catch (err) {
3374
+ ctx.logger.warn("[AI] Failed to register data_explorer skill", err instanceof Error ? { error: err.message } : { error: String(err) });
3375
+ }
2737
3376
  }
2738
3377
  }
2739
3378
  } catch {
@@ -2745,16 +3384,29 @@ var AIServicePlugin = class {
2745
3384
  ctx.logger.info("[AI] Built-in metadata tools registered");
2746
3385
  const { METADATA_TOOL_DEFINITIONS: METADATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_metadata_tools(), metadata_tools_exports));
2747
3386
  for (const toolDef of METADATA_TOOL_DEFINITIONS2) {
2748
- const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
3387
+ const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
3388
+ if (toolExists === null) {
3389
+ ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
3390
+ break;
3391
+ }
2749
3392
  if (!toolExists) {
2750
- await metadataService.register("tool", toolDef.name, toolDef);
3393
+ try {
3394
+ await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
3395
+ } catch (err) {
3396
+ ctx.logger.warn(
3397
+ "[AI] Failed to persist tool metadata (non-fatal)",
3398
+ err instanceof Error ? { tool: toolDef.name, error: err.message } : { tool: toolDef.name }
3399
+ );
3400
+ }
2751
3401
  }
2752
3402
  }
2753
3403
  ctx.logger.info(`[AI] ${METADATA_TOOL_DEFINITIONS2.length} metadata tools registered as metadata`);
2754
3404
  try {
2755
- const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name) : false;
2756
- if (!agentExists) {
2757
- await metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT);
3405
+ const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
3406
+ if (agentExists === null) {
3407
+ ctx.logger.warn("[AI] Metadata service timed out checking metadata_assistant agent, skipping");
3408
+ } else if (!agentExists) {
3409
+ await withTimeout(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
2758
3410
  console.log("[AI] Registered metadata_assistant agent to metadataService");
2759
3411
  ctx.logger.info("[AI] metadata_assistant agent registered");
2760
3412
  } else {
@@ -2764,22 +3416,71 @@ var AIServicePlugin = class {
2764
3416
  } catch (err) {
2765
3417
  ctx.logger.warn("[AI] Failed to register metadata_assistant agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
2766
3418
  }
3419
+ try {
3420
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
3421
+ if (skillExists === null) {
3422
+ ctx.logger.warn("[AI] Metadata service timed out checking metadata_authoring skill, skipping");
3423
+ } else if (!skillExists) {
3424
+ await withTimeout(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
3425
+ ctx.logger.info("[AI] metadata_authoring skill registered");
3426
+ } else {
3427
+ ctx.logger.debug("[AI] metadata_authoring skill already exists, skipping auto-registration");
3428
+ }
3429
+ } catch (err) {
3430
+ ctx.logger.warn("[AI] Failed to register metadata_authoring skill", err instanceof Error ? { error: err.message } : { error: String(err) });
3431
+ }
2767
3432
  } catch (err) {
2768
3433
  ctx.logger.debug("[AI] Failed to register metadata tools", err instanceof Error ? err : void 0);
2769
3434
  }
2770
3435
  }
2771
3436
  await ctx.trigger("ai:ready", this.service);
3437
+ if (metadataService) {
3438
+ try {
3439
+ const objectql = ctx.getService("objectql");
3440
+ const registry = objectql?.registry;
3441
+ if (registry && typeof registry.listItems === "function") {
3442
+ const stackAgents = registry.listItems("agent");
3443
+ let bridged = 0;
3444
+ for (const entry of stackAgents) {
3445
+ const agent = entry?.content ?? entry;
3446
+ const agentName = agent?.name;
3447
+ if (!agentName || typeof agentName !== "string") continue;
3448
+ const exists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", agentName)) : false;
3449
+ if (exists === true) continue;
3450
+ try {
3451
+ await withTimeout(metadataService.register("agent", agentName, agent));
3452
+ bridged++;
3453
+ } catch (err) {
3454
+ ctx.logger.warn(
3455
+ "[AI] Failed to bridge stack agent into metadata service (non-fatal)",
3456
+ err instanceof Error ? { agent: agentName, error: err.message } : { agent: agentName }
3457
+ );
3458
+ }
3459
+ }
3460
+ if (bridged > 0) {
3461
+ ctx.logger.info(`[AI] Bridged ${bridged} stack-defined agent(s) from ObjectQL registry`);
3462
+ console.log(`[AI] Bridged ${bridged} stack-defined agent(s) from ObjectQL registry`);
3463
+ }
3464
+ }
3465
+ } catch (err) {
3466
+ ctx.logger.debug("[AI] ObjectQL registry not available, skipping agent bridge", err instanceof Error ? err : void 0);
3467
+ }
3468
+ }
2772
3469
  const routes = buildAIRoutes(this.service, this.service.conversationService, ctx.logger);
2773
3470
  const toolRoutes = buildToolRoutes(this.service, ctx.logger);
2774
3471
  routes.push(...toolRoutes);
2775
3472
  ctx.logger.info(`[AI] Tool routes registered (${toolRoutes.length} routes)`);
2776
3473
  if (metadataService) {
2777
- const agentRuntime = new AgentRuntime(metadataService);
3474
+ const skillRegistry = new SkillRegistry(metadataService);
3475
+ const agentRuntime = new AgentRuntime(metadataService, skillRegistry);
2778
3476
  const agentRoutes = buildAgentRoutes(this.service, agentRuntime, ctx.logger);
2779
3477
  routes.push(...agentRoutes);
2780
3478
  ctx.logger.info(`[AI] Agent routes registered (${agentRoutes.length} routes)`);
3479
+ const assistantRoutes = buildAssistantRoutes(this.service, agentRuntime, skillRegistry, ctx.logger);
3480
+ routes.push(...assistantRoutes);
3481
+ ctx.logger.info(`[AI] Assistant (ambient) routes registered (${assistantRoutes.length} routes)`);
2781
3482
  } else {
2782
- ctx.logger.debug("[AI] Metadata service not available, skipping agent routes");
3483
+ ctx.logger.debug("[AI] Metadata service not available, skipping agent and assistant routes");
2783
3484
  }
2784
3485
  await ctx.trigger("ai:routes", routes);
2785
3486
  const kernel = ctx.getKernel();
@@ -2799,6 +3500,337 @@ var AIServicePlugin = class {
2799
3500
  init_data_tools();
2800
3501
  init_metadata_tools();
2801
3502
  init_metadata_tools();
3503
+
3504
+ // src/tools/list-packages.tool.ts
3505
+ var import_ai10 = require("@objectstack/spec/ai");
3506
+ var listPackagesTool = (0, import_ai10.defineTool)({
3507
+ name: "list_packages",
3508
+ label: "List Packages",
3509
+ description: "Lists all installed packages in the system. Use this to see what packages are available before creating or modifying metadata. Packages are the containers that hold metadata.",
3510
+ category: "utility",
3511
+ builtIn: true,
3512
+ parameters: {
3513
+ type: "object",
3514
+ properties: {
3515
+ status: {
3516
+ type: "string",
3517
+ description: "Filter by package status",
3518
+ enum: ["installed", "disabled", "installing", "upgrading", "uninstalling", "error"]
3519
+ },
3520
+ enabled: {
3521
+ type: "boolean",
3522
+ description: "Filter by enabled state (true = only enabled, false = only disabled)"
3523
+ }
3524
+ },
3525
+ additionalProperties: false
3526
+ }
3527
+ });
3528
+
3529
+ // src/tools/get-package.tool.ts
3530
+ var import_ai11 = require("@objectstack/spec/ai");
3531
+ var getPackageTool = (0, import_ai11.defineTool)({
3532
+ name: "get_package",
3533
+ label: "Get Package",
3534
+ description: "Gets detailed information about a specific installed package, including its manifest, metadata, and installation status.",
3535
+ category: "utility",
3536
+ builtIn: true,
3537
+ parameters: {
3538
+ type: "object",
3539
+ properties: {
3540
+ packageId: {
3541
+ type: "string",
3542
+ description: "Package identifier (reverse domain notation, e.g., com.acme.crm)"
3543
+ }
3544
+ },
3545
+ required: ["packageId"],
3546
+ additionalProperties: false
3547
+ }
3548
+ });
3549
+
3550
+ // src/tools/create-package.tool.ts
3551
+ var import_ai12 = require("@objectstack/spec/ai");
3552
+ var createPackageTool = (0, import_ai12.defineTool)({
3553
+ name: "create_package",
3554
+ label: "Create Package",
3555
+ description: "Creates a new package (metadata container) with the specified manifest. All metadata in ObjectStack should belong to a package. Use this when starting new development or when the user wants to organize their metadata into a new module.",
3556
+ category: "utility",
3557
+ builtIn: true,
3558
+ parameters: {
3559
+ type: "object",
3560
+ properties: {
3561
+ id: {
3562
+ type: "string",
3563
+ description: "Package identifier in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)"
3564
+ },
3565
+ name: {
3566
+ type: "string",
3567
+ description: 'Human-readable package name (e.g., "CRM Application", "Sales Module")'
3568
+ },
3569
+ version: {
3570
+ type: "string",
3571
+ description: 'Semantic version (e.g., "1.0.0")',
3572
+ default: "1.0.0"
3573
+ },
3574
+ description: {
3575
+ type: "string",
3576
+ description: "Brief description of what this package provides"
3577
+ },
3578
+ namespace: {
3579
+ type: "string",
3580
+ description: "Namespace prefix for metadata (snake_case, e.g., crm, sales). If not provided, derived from package ID."
3581
+ },
3582
+ type: {
3583
+ type: "string",
3584
+ description: "Package type",
3585
+ enum: ["application", "plugin", "library", "template"],
3586
+ default: "application"
3587
+ }
3588
+ },
3589
+ required: ["id", "name"],
3590
+ additionalProperties: false
3591
+ }
3592
+ });
3593
+
3594
+ // src/tools/get-active-package.tool.ts
3595
+ var import_ai13 = require("@objectstack/spec/ai");
3596
+ var getActivePackageTool = (0, import_ai13.defineTool)({
3597
+ name: "get_active_package",
3598
+ label: "Get Active Package",
3599
+ description: "Gets the currently active package in this conversation. The active package determines where new metadata will be created. Returns null if no package is set.",
3600
+ category: "utility",
3601
+ builtIn: true,
3602
+ parameters: {
3603
+ type: "object",
3604
+ properties: {},
3605
+ additionalProperties: false
3606
+ }
3607
+ });
3608
+
3609
+ // src/tools/set-active-package.tool.ts
3610
+ var import_ai14 = require("@objectstack/spec/ai");
3611
+ var setActivePackageTool = (0, import_ai14.defineTool)({
3612
+ name: "set_active_package",
3613
+ label: "Set Active Package",
3614
+ description: "Sets the active package for this conversation. All subsequent metadata creation operations (objects, views, flows, etc.) will be associated with this package unless explicitly overridden.",
3615
+ category: "utility",
3616
+ builtIn: true,
3617
+ parameters: {
3618
+ type: "object",
3619
+ properties: {
3620
+ packageId: {
3621
+ type: "string",
3622
+ description: "Package identifier to set as active (e.g., com.acme.crm)"
3623
+ }
3624
+ },
3625
+ required: ["packageId"],
3626
+ additionalProperties: false
3627
+ }
3628
+ });
3629
+
3630
+ // src/tools/package-tools.ts
3631
+ var PACKAGE_TOOL_DEFINITIONS = [
3632
+ listPackagesTool,
3633
+ getPackageTool,
3634
+ createPackageTool,
3635
+ getActivePackageTool,
3636
+ setActivePackageTool
3637
+ ];
3638
+ var REVERSE_DOMAIN_RE = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/;
3639
+ var SNAKE_CASE_RE2 = /^[a-z_][a-z0-9_]*$/;
3640
+ var SEMVER_RE = /^\d+\.\d+\.\d+(-[a-z0-9]+(\.[a-z0-9]+)*)?$/;
3641
+ function isReverseDomain(value) {
3642
+ return REVERSE_DOMAIN_RE.test(value);
3643
+ }
3644
+ function isSnakeCase2(value) {
3645
+ return SNAKE_CASE_RE2.test(value);
3646
+ }
3647
+ function isSemVer(value) {
3648
+ return SEMVER_RE.test(value);
3649
+ }
3650
+ function deriveNamespace(packageId) {
3651
+ const parts = packageId.split(".");
3652
+ return parts[parts.length - 1];
3653
+ }
3654
+ function createListPackagesHandler(ctx) {
3655
+ return async (args) => {
3656
+ const { status, enabled } = args ?? {};
3657
+ const filter = {};
3658
+ if (status) filter.status = status;
3659
+ if (enabled !== void 0) filter.enabled = enabled;
3660
+ const packages = await ctx.packageRegistry.list(filter);
3661
+ const result = packages.map((pkg) => ({
3662
+ id: pkg.manifest.id,
3663
+ name: pkg.manifest.name,
3664
+ version: pkg.manifest.version,
3665
+ type: pkg.manifest.type,
3666
+ status: pkg.status,
3667
+ enabled: pkg.enabled,
3668
+ installedAt: pkg.installedAt,
3669
+ description: pkg.manifest.description
3670
+ }));
3671
+ return JSON.stringify({
3672
+ packages: result,
3673
+ total: result.length
3674
+ });
3675
+ };
3676
+ }
3677
+ function createGetPackageHandler(ctx) {
3678
+ return async (args) => {
3679
+ const { packageId } = args;
3680
+ if (!packageId) {
3681
+ return JSON.stringify({ error: "packageId is required" });
3682
+ }
3683
+ const pkg = await ctx.packageRegistry.get(packageId);
3684
+ if (!pkg) {
3685
+ return JSON.stringify({ error: `Package "${packageId}" not found` });
3686
+ }
3687
+ return JSON.stringify({
3688
+ id: pkg.manifest.id,
3689
+ name: pkg.manifest.name,
3690
+ version: pkg.manifest.version,
3691
+ type: pkg.manifest.type,
3692
+ status: pkg.status,
3693
+ enabled: pkg.enabled,
3694
+ installedAt: pkg.installedAt,
3695
+ updatedAt: pkg.updatedAt,
3696
+ description: pkg.manifest.description,
3697
+ namespace: pkg.manifest.namespace,
3698
+ dependencies: pkg.manifest.dependencies,
3699
+ registeredNamespaces: pkg.registeredNamespaces
3700
+ });
3701
+ };
3702
+ }
3703
+ function createCreatePackageHandler(ctx) {
3704
+ return async (args) => {
3705
+ const { id, name, version = "1.0.0", description, namespace, type = "application" } = args;
3706
+ if (!id || !name) {
3707
+ return JSON.stringify({ error: 'Both "id" and "name" are required' });
3708
+ }
3709
+ if (!isReverseDomain(id)) {
3710
+ return JSON.stringify({
3711
+ error: `Invalid package ID "${id}". Must be in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)`
3712
+ });
3713
+ }
3714
+ if (!isSemVer(version)) {
3715
+ return JSON.stringify({
3716
+ error: `Invalid version "${version}". Must be semantic version (e.g., 1.0.0, 2.1.3-beta)`
3717
+ });
3718
+ }
3719
+ const exists = await ctx.packageRegistry.exists(id);
3720
+ if (exists) {
3721
+ return JSON.stringify({ error: `Package "${id}" already exists` });
3722
+ }
3723
+ const derivedNamespace = namespace || deriveNamespace(id);
3724
+ if (!isSnakeCase2(derivedNamespace)) {
3725
+ return JSON.stringify({
3726
+ error: `Invalid namespace "${derivedNamespace}". Must be snake_case (e.g., crm, sales_module)`
3727
+ });
3728
+ }
3729
+ const manifest = {
3730
+ id,
3731
+ name,
3732
+ version,
3733
+ type,
3734
+ namespace: derivedNamespace,
3735
+ ...description ? { description } : {}
3736
+ };
3737
+ const installedPackage = await ctx.packageRegistry.install(manifest);
3738
+ if (ctx.conversationService && ctx.conversationId) {
3739
+ try {
3740
+ await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
3741
+ activePackageId: id
3742
+ });
3743
+ } catch (err) {
3744
+ console.warn("Failed to set active package in conversation:", err);
3745
+ }
3746
+ }
3747
+ return JSON.stringify({
3748
+ packageId: installedPackage.manifest.id,
3749
+ name: installedPackage.manifest.name,
3750
+ version: installedPackage.manifest.version,
3751
+ namespace: installedPackage.manifest.namespace,
3752
+ status: installedPackage.status,
3753
+ message: `Package "${name}" created successfully and set as active package`
3754
+ });
3755
+ };
3756
+ }
3757
+ function createGetActivePackageHandler(ctx) {
3758
+ return async () => {
3759
+ if (!ctx.conversationService || !ctx.conversationId) {
3760
+ return JSON.stringify({
3761
+ activePackageId: null,
3762
+ message: "No conversation context available to track active package"
3763
+ });
3764
+ }
3765
+ try {
3766
+ const metadata = await ctx.conversationService.getMetadata?.(ctx.conversationId);
3767
+ const activePackageId = metadata?.activePackageId;
3768
+ if (!activePackageId) {
3769
+ return JSON.stringify({
3770
+ activePackageId: null,
3771
+ message: "No active package set. Use set_active_package or create a new package."
3772
+ });
3773
+ }
3774
+ const pkg = await ctx.packageRegistry.get(activePackageId);
3775
+ if (!pkg) {
3776
+ return JSON.stringify({
3777
+ activePackageId,
3778
+ error: `Active package "${activePackageId}" not found. It may have been uninstalled.`
3779
+ });
3780
+ }
3781
+ return JSON.stringify({
3782
+ activePackageId: pkg.manifest.id,
3783
+ name: pkg.manifest.name,
3784
+ version: pkg.manifest.version,
3785
+ namespace: pkg.manifest.namespace,
3786
+ type: pkg.manifest.type
3787
+ });
3788
+ } catch (err) {
3789
+ return JSON.stringify({
3790
+ error: `Failed to get active package: ${err.message}`
3791
+ });
3792
+ }
3793
+ };
3794
+ }
3795
+ function createSetActivePackageHandler(ctx) {
3796
+ return async (args) => {
3797
+ const { packageId } = args;
3798
+ if (!packageId) {
3799
+ return JSON.stringify({ error: "packageId is required" });
3800
+ }
3801
+ const pkg = await ctx.packageRegistry.get(packageId);
3802
+ if (!pkg) {
3803
+ return JSON.stringify({ error: `Package "${packageId}" not found` });
3804
+ }
3805
+ if (!ctx.conversationService || !ctx.conversationId) {
3806
+ return JSON.stringify({
3807
+ error: "No conversation context available. Cannot set active package."
3808
+ });
3809
+ }
3810
+ try {
3811
+ await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
3812
+ activePackageId: packageId
3813
+ });
3814
+ return JSON.stringify({
3815
+ activePackageId: packageId,
3816
+ name: pkg.manifest.name,
3817
+ namespace: pkg.manifest.namespace,
3818
+ message: `Active package set to "${pkg.manifest.name}"`
3819
+ });
3820
+ } catch (err) {
3821
+ return JSON.stringify({
3822
+ error: `Failed to set active package: ${err.message}`
3823
+ });
3824
+ }
3825
+ };
3826
+ }
3827
+ function registerPackageTools(registry, context) {
3828
+ registry.register(listPackagesTool, createListPackagesHandler(context));
3829
+ registry.register(getPackageTool, createGetPackageHandler(context));
3830
+ registry.register(createPackageTool, createCreatePackageHandler(context));
3831
+ registry.register(getActivePackageTool, createGetActivePackageHandler(context));
3832
+ registry.register(setActivePackageTool, createSetActivePackageHandler(context));
3833
+ }
2802
3834
  // Annotate the CommonJS export names for ESM import in node:
2803
3835
  0 && (module.exports = {
2804
3836
  AIService,
@@ -2813,20 +3845,29 @@ init_metadata_tools();
2813
3845
  METADATA_TOOL_DEFINITIONS,
2814
3846
  MemoryLLMAdapter,
2815
3847
  ObjectQLConversationService,
3848
+ PACKAGE_TOOL_DEFINITIONS,
3849
+ SkillRegistry,
2816
3850
  ToolRegistry,
2817
3851
  VercelLLMAdapter,
2818
3852
  addFieldTool,
2819
3853
  buildAIRoutes,
2820
3854
  buildAgentRoutes,
3855
+ buildAssistantRoutes,
2821
3856
  buildToolRoutes,
2822
3857
  createObjectTool,
3858
+ createPackageTool,
2823
3859
  deleteFieldTool,
2824
3860
  describeObjectTool,
2825
3861
  encodeStreamPart,
2826
3862
  encodeVercelDataStream,
3863
+ getActivePackageTool,
3864
+ getPackageTool,
2827
3865
  listObjectsTool,
3866
+ listPackagesTool,
2828
3867
  modifyFieldTool,
2829
3868
  registerDataTools,
2830
- registerMetadataTools
3869
+ registerMetadataTools,
3870
+ registerPackageTools,
3871
+ setActivePackageTool
2831
3872
  });
2832
3873
  //# sourceMappingURL=index.cjs.map