@adminforth/agent 1.47.2 → 1.48.1

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.
@@ -66,10 +66,15 @@ export function createApiBasedToolsMiddleware(
66
66
  const tools = [...enabledApiToolNames]
67
67
  .filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
68
68
  .map((toolName) => dynamicTools[toolName]);
69
+ const availableTools = [...request.tools, ...tools];
70
+
71
+ logger.info(
72
+ `AdminForth Agent callable tools: ${availableTools.map((tool) => tool.name).join(", ")}`,
73
+ );
69
74
 
70
75
  return handler({
71
76
  ...request,
72
- tools: [...request.tools, ...tools],
77
+ tools: availableTools,
73
78
  });
74
79
  },
75
80
  async wrapToolCall(request, handler) {
@@ -230,6 +230,7 @@ export async function callAgent(params: {
230
230
  adminforth: IAdminForth;
231
231
  apiBasedTools: Record<string, ApiBasedTool>;
232
232
  customComponentsDir: string;
233
+ pluginCustomFolderPaths: string[];
233
234
  sessionId: string;
234
235
  turnId: string;
235
236
  currentPage?: CurrentPageContext;
@@ -249,6 +250,7 @@ export async function callAgent(params: {
249
250
  adminforth,
250
251
  apiBasedTools,
251
252
  customComponentsDir,
253
+ pluginCustomFolderPaths,
252
254
  sessionId,
253
255
  turnId,
254
256
  currentPage,
@@ -258,7 +260,11 @@ export async function callAgent(params: {
258
260
  sequenceDebugSink,
259
261
  } = params;
260
262
 
261
- const tools = await createAgentTools(customComponentsDir, apiBasedTools);
263
+ const tools = await createAgentTools(
264
+ customComponentsDir,
265
+ apiBasedTools,
266
+ pluginCustomFolderPaths,
267
+ );
262
268
  const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
263
269
  const sequenceDebugMiddleware = createSequenceDebugMiddleware(
264
270
  sequenceDebugSink,
@@ -1,16 +1,7 @@
1
1
  import { readdir, readFile } from "fs/promises";
2
2
  import path from "path";
3
- import { fileURLToPath } from "url";
4
3
  import { parse as parseYaml } from "yaml";
5
4
 
6
- const PLUGIN_SKILLS_DIRECTORY_PATH = fileURLToPath(
7
- new URL("../../custom/skills/", import.meta.url),
8
- );
9
- const USER_PLUGIN_SKILLS_DIRECTORY_PATH_SEGMENTS = [
10
- "plugins",
11
- "adminforth-agent",
12
- "skills",
13
- ];
14
5
  const SKILL_MARKDOWN_FILENAME = "SKILL.md";
15
6
  const SKILL_FRONTMATTER_SEPARATOR = "\n---\n";
16
7
 
@@ -21,6 +12,13 @@ export interface AgentSkillManifest {
21
12
  instructions: string;
22
13
  }
23
14
 
15
+ function normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths: string[] = []) {
16
+ return Array.from(new Set(
17
+ pluginCustomFolderPaths
18
+ .map((pluginCustomFolderPath) => path.resolve(pluginCustomFolderPath, "skills")),
19
+ ));
20
+ }
21
+
24
22
  function parseSkillManifest(directoryName: string, markdown: string): AgentSkillManifest {
25
23
  const [frontmatterBlock, instructions = ""] = markdown.split("\r\n").join("\n").split(
26
24
  SKILL_FRONTMATTER_SEPARATOR,
@@ -83,43 +81,42 @@ export function getProjectSkillsDirectoryPath(customComponentsDir: string) {
83
81
  return path.resolve(customComponentsDir, "skills");
84
82
  }
85
83
 
86
- export function getProjectPluginSkillsDirectoryPath(customComponentsDir: string) {
87
- return path.resolve(
88
- customComponentsDir,
89
- ...USER_PLUGIN_SKILLS_DIRECTORY_PATH_SEGMENTS,
90
- );
91
- }
92
-
93
- function getProjectSkillDirectoryPaths(customComponentsDir: string) {
94
- return [
84
+ function getProjectSkillDirectoryPaths(
85
+ customComponentsDir: string,
86
+ pluginCustomFolderPaths: string[] = [],
87
+ ) {
88
+ return Array.from(new Set([
95
89
  getProjectSkillsDirectoryPath(customComponentsDir),
96
- getProjectPluginSkillsDirectoryPath(customComponentsDir),
97
- ];
98
- }
99
-
100
- export async function listBundledSkillManifests() {
101
- return await listDirectorySkillManifests(PLUGIN_SKILLS_DIRECTORY_PATH);
90
+ ...normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths),
91
+ ]));
102
92
  }
103
93
 
104
- export async function listProjectSkillManifests(customComponentsDir: string) {
94
+ export async function listProjectSkillManifests(
95
+ customComponentsDir: string,
96
+ pluginCustomFolderPaths: string[] = [],
97
+ ) {
105
98
  return mergeSkillManifests(
106
99
  await Promise.all(
107
- getProjectSkillDirectoryPaths(customComponentsDir).map(
100
+ getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths).map(
108
101
  listDirectorySkillManifests,
109
102
  ),
110
103
  ),
111
104
  );
112
105
  }
113
106
 
114
- export async function listSkillManifests(customComponentsDir: string) {
115
- return mergeSkillManifests([
116
- await listProjectSkillManifests(customComponentsDir),
117
- await listBundledSkillManifests(),
118
- ]);
107
+ export async function listSkillManifests(
108
+ customComponentsDir: string,
109
+ pluginCustomFolderPaths: string[] = [],
110
+ ) {
111
+ return await listProjectSkillManifests(customComponentsDir, pluginCustomFolderPaths);
119
112
  }
120
113
 
121
- export async function loadSkillManifest(skillName: string, customComponentsDir: string) {
122
- const manifests = await listSkillManifests(customComponentsDir);
114
+ export async function loadSkillManifest(
115
+ skillName: string,
116
+ customComponentsDir: string,
117
+ pluginCustomFolderPaths: string[] = [],
118
+ ) {
119
+ const manifests = await listSkillManifests(customComponentsDir, pluginCustomFolderPaths);
123
120
 
124
121
  return (
125
122
  manifests.find(
@@ -129,16 +126,23 @@ export async function loadSkillManifest(skillName: string, customComponentsDir:
129
126
  );
130
127
  }
131
128
 
132
- export async function loadSkillMarkdown(skillName: string, customComponentsDir: string) {
133
- const manifest = await loadSkillManifest(skillName, customComponentsDir);
129
+ export async function loadSkillMarkdown(
130
+ skillName: string,
131
+ customComponentsDir: string,
132
+ pluginCustomFolderPaths: string[] = [],
133
+ ) {
134
+ const manifest = await loadSkillManifest(
135
+ skillName,
136
+ customComponentsDir,
137
+ pluginCustomFolderPaths,
138
+ );
134
139
 
135
140
  if (!manifest) {
136
141
  return null;
137
142
  }
138
143
 
139
144
  const directories = [
140
- ...getProjectSkillDirectoryPaths(customComponentsDir),
141
- PLUGIN_SKILLS_DIRECTORY_PATH,
145
+ ...getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths),
142
146
  ];
143
147
 
144
148
  for (const skillsDirectoryPath of directories) {
@@ -1,7 +1,6 @@
1
1
  import type { AdminForthResource, AdminUser, IAdminForth } from "adminforth";
2
2
  import type { DetectedLanguage } from "./languageDetect.js";
3
3
  import {
4
- listBundledSkillManifests,
5
4
  listProjectSkillManifests,
6
5
  type AgentSkillManifest,
7
6
  } from "./skills/registry.js";
@@ -96,10 +95,12 @@ export async function buildAgentSystemPrompt(
96
95
  hiddenResourceIds: Iterable<string> = [],
97
96
  ) {
98
97
  const customComponentsDir = adminforth.config.customization.customComponentsDir ?? "custom";
99
- const [primarySkills, defaultSkills] = await Promise.all([
100
- listProjectSkillManifests(customComponentsDir),
101
- listBundledSkillManifests(),
102
- ]);
98
+ const pluginCustomFolderPaths = adminforth.activatedPlugins
99
+ .map((plugin) => plugin.customFolderPath);
100
+ const skills = await listProjectSkillManifests(
101
+ customComponentsDir,
102
+ pluginCustomFolderPaths,
103
+ );
103
104
  const adminBasePath = adminforth.config.baseUrlSlashed;
104
105
  const hiddenResourceIdSet = new Set(hiddenResourceIds);
105
106
  const visibleResources = adminforth.config.resources.filter(
@@ -109,11 +110,9 @@ export async function buildAgentSystemPrompt(
109
110
  DEFAULT_AGENT_SYSTEM_PROMPT,
110
111
  `ADMIN_BASE_PATH: ${adminBasePath}`,
111
112
  `List of resources:\n${formatResources(visibleResources)}`,
112
- primarySkills.length > 0
113
- ? `You have primary skills set:\n${formatSkills(primarySkills, "skill_name")}`
113
+ skills.length > 0
114
+ ? `You have skills set:\n${formatSkills(skills, "skill_name")}`
114
115
  : "",
115
- "You have next default skills which you can fallback to if primary skill set does not provide a good skill:\n" +
116
- formatSkills(defaultSkills, "skill_name"),
117
116
  "Before using any skill, call fetch_skill to load its full instructions.",
118
117
  "The fetched skill response starts with 'Tools mentioned in this skill'. Read that list first.",
119
118
  "You can use get_resource immediately to inspect resource structure and column names.",
@@ -123,8 +122,8 @@ export async function buildAgentSystemPrompt(
123
122
  "If a fetched skill lists a non-base tool you need, call fetch_tool_schema for it immediately instead of telling the user the tool is unavailable.",
124
123
  "For example: for record creation load mutate_data, read its tool list, call fetch_tool_schema for create_record, and then use create_record after confirmation.",
125
124
  "When fetch_tool_schema succeeds, that tool becomes available on the next step.",
126
- "All admin links must be relative paths and must start with ADMIN_BASE_PATH.",
127
- "Build record links as ADMIN_BASE_PATH + resource/{resourceId}/show/{primary key}. Do not prepend any extra slash before resource.",
125
+ "All admin links must be root-relative and start with '/'.",
126
+ "Build record links as '/resource/{resourceId}/show/{primary key}'. Never use bare 'resource/{resourceId}/show/{primary key}' without the leading slash.",
128
127
  "Try to call as many tools as possible in parallel in one step.",
129
128
  ];
130
129
 
@@ -21,14 +21,24 @@ function serializeSkillManifests(skillManifests: AgentSkillManifest[]) {
21
21
  }));
22
22
  }
23
23
 
24
- export async function createFetchSkillTool(customComponentsDir: string) {
25
- const availableSkills = await listSkillManifests(customComponentsDir);
24
+ export async function createFetchSkillTool(
25
+ customComponentsDir: string,
26
+ pluginCustomFolderPaths: string[] = [],
27
+ ) {
28
+ const availableSkills = await listSkillManifests(
29
+ customComponentsDir,
30
+ pluginCustomFolderPaths,
31
+ );
26
32
  const availableSkillNames = availableSkills.map((skill) => skill.name);
27
33
 
28
34
  return tool(
29
35
  async ({ skillName }) => {
30
36
  try {
31
- const skillMarkdown = await loadSkillMarkdown(skillName, customComponentsDir);
37
+ const skillMarkdown = await loadSkillMarkdown(
38
+ skillName,
39
+ customComponentsDir,
40
+ pluginCustomFolderPaths,
41
+ );
32
42
 
33
43
  if (!skillMarkdown) {
34
44
  return [
@@ -10,6 +10,7 @@ export const ALWAYS_AVAILABLE_API_TOOL_NAMES = ["get_resource"] as const;
10
10
  export async function createAgentTools(
11
11
  customComponentsDir: string,
12
12
  apiBasedTools: Record<string, ApiBasedTool>,
13
+ pluginCustomFolderPaths: string[] = [],
13
14
  ): Promise<ClientTool[]> {
14
15
  return [
15
16
  ...ALWAYS_AVAILABLE_API_TOOL_NAMES.map((toolName) => {
@@ -22,7 +23,7 @@ export async function createAgentTools(
22
23
  return createApiTool(toolName, apiBasedTool);
23
24
  }),
24
25
  createGetUserLocationTool(),
25
- await createFetchSkillTool(customComponentsDir),
26
+ await createFetchSkillTool(customComponentsDir, pluginCustomFolderPaths),
26
27
  await createFetchToolSchemaTool(apiBasedTools),
27
28
  ];
28
29
  }
@@ -138,6 +138,7 @@ export class AgentTurnService {
138
138
  adminforth,
139
139
  apiBasedTools,
140
140
  customComponentsDir: adminforth.config.customization.customComponentsDir ?? "custom",
141
+ pluginCustomFolderPaths: adminforth.activatedPlugins.map((plugin) => plugin.customFolderPath),
141
142
  sessionId: input.sessionId,
142
143
  turnId: input.turnId,
143
144
  currentPage: input.currentPage,
package/build.log CHANGED
@@ -62,5 +62,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
62
62
  custom/speech_recognition_frontend/types/
63
63
  custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
64
64
 
65
- sent 1,667,711 bytes received 921 bytes 3,337,264.00 bytes/sec
66
- total size is 1,663,558 speedup is 1.00
65
+ sent 1,668,700 bytes received 905 bytes 3,339,210.00 bytes/sec
66
+ total size is 1,664,547 speedup is 1.00
@@ -3,7 +3,7 @@ import { callAdminForthApi } from '@/utils';
3
3
  import type { Chat } from '../../chat';
4
4
  import type { IAgentSession, ISessionsListItem, IPart } from '../../types';
5
5
  import { PRE_SESSION_ID } from './constants';
6
- import { useI18n } from 'vue-i18n';
6
+ import { i18nInstance } from '@/i18n';
7
7
 
8
8
  type AdminforthLike = {
9
9
  confirm(options: { message: string; yes: string; no: string }): Promise<boolean>;
@@ -42,7 +42,10 @@ export function createAgentSessionManager({
42
42
  return [...sessionsListToSort].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
43
43
  }
44
44
 
45
- const { t } = useI18n();
45
+ function t(key: string) {
46
+ return i18nInstance?.global.t(key) ?? key;
47
+ }
48
+
46
49
  function saveCurrentSessionInCache() {
47
50
  if (currentSession.value) {
48
51
  currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
@@ -342,4 +345,4 @@ export function createAgentSessionManager({
342
345
  setCurrentChatStatus,
343
346
  updateLastAgentMessage
344
347
  };
345
- }
348
+ }
@@ -118,10 +118,13 @@ const THRESHOLD_TO_SHOW_BUTTON = 10;
118
118
  let messageResizeObserver: ResizeObserver | null = null;
119
119
  let observedLastUserMessageElement: HTMLElement | null = null;
120
120
  let observedLastAgentMessageElement: HTMLElement | null = null;
121
+ let updateSpacerFrameId: number | null = null;
122
+ let pendingSpacerUpdate: Promise<void> | null = null;
123
+ let spacerUpdateQueued = false;
121
124
 
122
125
  onMounted(async () => {
123
126
  messageResizeObserver = new ResizeObserver(() => {
124
- updateSpacerHeight();
127
+ scheduleSpacerHeightUpdate();
125
128
  });
126
129
 
127
130
  await import('@incremark/theme/styles.css')
@@ -136,6 +139,10 @@ onUnmounted(() => {
136
139
 
137
140
  stopObservingLastMessages();
138
141
  messageResizeObserver?.disconnect();
142
+ if (updateSpacerFrameId !== null) {
143
+ cancelAnimationFrame(updateSpacerFrameId);
144
+ updateSpacerFrameId = null;
145
+ }
139
146
  agentStore.stopPlaceholderAnimation();
140
147
  });
141
148
 
@@ -221,6 +228,30 @@ async function updateSpacerHeight() {
221
228
  spacerHeight.value = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
222
229
  }
223
230
 
231
+ function scheduleSpacerHeightUpdate() {
232
+ spacerUpdateQueued = true;
233
+
234
+ if (updateSpacerFrameId !== null) {
235
+ return;
236
+ }
237
+
238
+ updateSpacerFrameId = requestAnimationFrame(() => {
239
+ updateSpacerFrameId = null;
240
+
241
+ if (pendingSpacerUpdate) {
242
+ return;
243
+ }
244
+
245
+ spacerUpdateQueued = false;
246
+ pendingSpacerUpdate = updateSpacerHeight().finally(() => {
247
+ pendingSpacerUpdate = null;
248
+ if (spacerUpdateQueued) {
249
+ scheduleSpacerHeightUpdate();
250
+ }
251
+ });
252
+ });
253
+ }
254
+
224
255
  function stopObservingLastMessages() {
225
256
  if (!messageResizeObserver) {
226
257
  return;
@@ -285,4 +316,4 @@ function recalculateScroll() {
285
316
  }
286
317
  }
287
318
 
288
- </script>
319
+ </script>
@@ -115,7 +115,11 @@
115
115
  return `${window.location.pathname}${window.location.search}${href}`;
116
116
  }
117
117
 
118
- const resolvedUrl = new URL(href, window.location.href);
118
+ const isAbsoluteWithScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href);
119
+ const baseUrl = isAbsoluteWithScheme
120
+ ? undefined
121
+ : `${window.location.origin}/`;
122
+ const resolvedUrl = new URL(href, baseUrl ?? window.location.href);
119
123
  if (resolvedUrl.origin !== window.location.origin) {
120
124
  return null;
121
125
  }
@@ -55,7 +55,9 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
55
55
  const tools = [...enabledApiToolNames]
56
56
  .filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
57
57
  .map((toolName) => dynamicTools[toolName]);
58
- return handler(Object.assign(Object.assign({}, request), { tools: [...request.tools, ...tools] }));
58
+ const availableTools = [...request.tools, ...tools];
59
+ logger.info(`AdminForth Agent callable tools: ${availableTools.map((tool) => tool.name).join(", ")}`);
60
+ return handler(Object.assign(Object.assign({}, request), { tools: availableTools }));
59
61
  });
60
62
  },
61
63
  wrapToolCall(request, handler) {
@@ -50,6 +50,7 @@ export declare function callAgent(params: {
50
50
  adminforth: IAdminForth;
51
51
  apiBasedTools: Record<string, ApiBasedTool>;
52
52
  customComponentsDir: string;
53
+ pluginCustomFolderPaths: string[];
53
54
  sessionId: string;
54
55
  turnId: string;
55
56
  currentPage?: CurrentPageContext;
@@ -132,8 +132,8 @@ export function createAgentChatModel(params) {
132
132
  }
133
133
  export function callAgent(params) {
134
134
  return __awaiter(this, void 0, void 0, function* () {
135
- const { name, model, summaryModel, modelMiddleware = [], checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, sessionId, turnId, currentPage, userTimeZone, abortSignal, emitToolCallEvent, sequenceDebugSink, } = params;
136
- const tools = yield createAgentTools(customComponentsDir, apiBasedTools);
135
+ const { name, model, summaryModel, modelMiddleware = [], checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, pluginCustomFolderPaths, sessionId, turnId, currentPage, userTimeZone, abortSignal, emitToolCallEvent, sequenceDebugSink, } = params;
136
+ const tools = yield createAgentTools(customComponentsDir, apiBasedTools, pluginCustomFolderPaths);
137
137
  const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
138
138
  const sequenceDebugMiddleware = createSequenceDebugMiddleware(sequenceDebugSink);
139
139
  const middleware = [
@@ -5,9 +5,7 @@ export interface AgentSkillManifest {
5
5
  instructions: string;
6
6
  }
7
7
  export declare function getProjectSkillsDirectoryPath(customComponentsDir: string): string;
8
- export declare function getProjectPluginSkillsDirectoryPath(customComponentsDir: string): string;
9
- export declare function listBundledSkillManifests(): Promise<AgentSkillManifest[]>;
10
- export declare function listProjectSkillManifests(customComponentsDir: string): Promise<AgentSkillManifest[]>;
11
- export declare function listSkillManifests(customComponentsDir: string): Promise<AgentSkillManifest[]>;
12
- export declare function loadSkillManifest(skillName: string, customComponentsDir: string): Promise<AgentSkillManifest | null>;
13
- export declare function loadSkillMarkdown(skillName: string, customComponentsDir: string): Promise<string | null>;
8
+ export declare function listProjectSkillManifests(customComponentsDir: string, pluginCustomFolderPaths?: string[]): Promise<AgentSkillManifest[]>;
9
+ export declare function listSkillManifests(customComponentsDir: string, pluginCustomFolderPaths?: string[]): Promise<AgentSkillManifest[]>;
10
+ export declare function loadSkillManifest(skillName: string, customComponentsDir: string, pluginCustomFolderPaths?: string[]): Promise<AgentSkillManifest | null>;
11
+ export declare function loadSkillMarkdown(skillName: string, customComponentsDir: string, pluginCustomFolderPaths?: string[]): Promise<string | null>;
@@ -9,16 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { readdir, readFile } from "fs/promises";
11
11
  import path from "path";
12
- import { fileURLToPath } from "url";
13
12
  import { parse as parseYaml } from "yaml";
14
- const PLUGIN_SKILLS_DIRECTORY_PATH = fileURLToPath(new URL("../../custom/skills/", import.meta.url));
15
- const USER_PLUGIN_SKILLS_DIRECTORY_PATH_SEGMENTS = [
16
- "plugins",
17
- "adminforth-agent",
18
- "skills",
19
- ];
20
13
  const SKILL_MARKDOWN_FILENAME = "SKILL.md";
21
14
  const SKILL_FRONTMATTER_SEPARATOR = "\n---\n";
15
+ function normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths = []) {
16
+ return Array.from(new Set(pluginCustomFolderPaths
17
+ .map((pluginCustomFolderPath) => path.resolve(pluginCustomFolderPath, "skills"))));
18
+ }
22
19
  function parseSkillManifest(directoryName, markdown) {
23
20
  var _a, _b;
24
21
  const [frontmatterBlock, instructions = ""] = markdown.split("\r\n").join("\n").split(SKILL_FRONTMATTER_SEPARATOR, 2);
@@ -63,49 +60,37 @@ function mergeSkillManifests(skillGroups) {
63
60
  export function getProjectSkillsDirectoryPath(customComponentsDir) {
64
61
  return path.resolve(customComponentsDir, "skills");
65
62
  }
66
- export function getProjectPluginSkillsDirectoryPath(customComponentsDir) {
67
- return path.resolve(customComponentsDir, ...USER_PLUGIN_SKILLS_DIRECTORY_PATH_SEGMENTS);
68
- }
69
- function getProjectSkillDirectoryPaths(customComponentsDir) {
70
- return [
63
+ function getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths = []) {
64
+ return Array.from(new Set([
71
65
  getProjectSkillsDirectoryPath(customComponentsDir),
72
- getProjectPluginSkillsDirectoryPath(customComponentsDir),
73
- ];
66
+ ...normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths),
67
+ ]));
74
68
  }
75
- export function listBundledSkillManifests() {
76
- return __awaiter(this, void 0, void 0, function* () {
77
- return yield listDirectorySkillManifests(PLUGIN_SKILLS_DIRECTORY_PATH);
69
+ export function listProjectSkillManifests(customComponentsDir_1) {
70
+ return __awaiter(this, arguments, void 0, function* (customComponentsDir, pluginCustomFolderPaths = []) {
71
+ return mergeSkillManifests(yield Promise.all(getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths).map(listDirectorySkillManifests)));
78
72
  });
79
73
  }
80
- export function listProjectSkillManifests(customComponentsDir) {
81
- return __awaiter(this, void 0, void 0, function* () {
82
- return mergeSkillManifests(yield Promise.all(getProjectSkillDirectoryPaths(customComponentsDir).map(listDirectorySkillManifests)));
74
+ export function listSkillManifests(customComponentsDir_1) {
75
+ return __awaiter(this, arguments, void 0, function* (customComponentsDir, pluginCustomFolderPaths = []) {
76
+ return yield listProjectSkillManifests(customComponentsDir, pluginCustomFolderPaths);
83
77
  });
84
78
  }
85
- export function listSkillManifests(customComponentsDir) {
86
- return __awaiter(this, void 0, void 0, function* () {
87
- return mergeSkillManifests([
88
- yield listProjectSkillManifests(customComponentsDir),
89
- yield listBundledSkillManifests(),
90
- ]);
91
- });
92
- }
93
- export function loadSkillManifest(skillName, customComponentsDir) {
94
- return __awaiter(this, void 0, void 0, function* () {
79
+ export function loadSkillManifest(skillName_1, customComponentsDir_1) {
80
+ return __awaiter(this, arguments, void 0, function* (skillName, customComponentsDir, pluginCustomFolderPaths = []) {
95
81
  var _a;
96
- const manifests = yield listSkillManifests(customComponentsDir);
82
+ const manifests = yield listSkillManifests(customComponentsDir, pluginCustomFolderPaths);
97
83
  return ((_a = manifests.find((manifest) => manifest.name === skillName || manifest.directoryName === skillName)) !== null && _a !== void 0 ? _a : null);
98
84
  });
99
85
  }
100
- export function loadSkillMarkdown(skillName, customComponentsDir) {
101
- return __awaiter(this, void 0, void 0, function* () {
102
- const manifest = yield loadSkillManifest(skillName, customComponentsDir);
86
+ export function loadSkillMarkdown(skillName_1, customComponentsDir_1) {
87
+ return __awaiter(this, arguments, void 0, function* (skillName, customComponentsDir, pluginCustomFolderPaths = []) {
88
+ const manifest = yield loadSkillManifest(skillName, customComponentsDir, pluginCustomFolderPaths);
103
89
  if (!manifest) {
104
90
  return null;
105
91
  }
106
92
  const directories = [
107
- ...getProjectSkillDirectoryPaths(customComponentsDir),
108
- PLUGIN_SKILLS_DIRECTORY_PATH,
93
+ ...getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths),
109
94
  ];
110
95
  for (const skillsDirectoryPath of directories) {
111
96
  try {
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { listBundledSkillManifests, listProjectSkillManifests, } from "./skills/registry.js";
10
+ import { listProjectSkillManifests, } from "./skills/registry.js";
11
11
  export const DEFAULT_AGENT_SYSTEM_PROMPT = [
12
12
  "You are helpful AI Assistant for Admin Panel.",
13
13
  // about admin
@@ -75,10 +75,9 @@ export function buildAgentSystemPrompt(adminforth_1) {
75
75
  return __awaiter(this, arguments, void 0, function* (adminforth, hiddenResourceIds = []) {
76
76
  var _a;
77
77
  const customComponentsDir = (_a = adminforth.config.customization.customComponentsDir) !== null && _a !== void 0 ? _a : "custom";
78
- const [primarySkills, defaultSkills] = yield Promise.all([
79
- listProjectSkillManifests(customComponentsDir),
80
- listBundledSkillManifests(),
81
- ]);
78
+ const pluginCustomFolderPaths = adminforth.activatedPlugins
79
+ .map((plugin) => plugin.customFolderPath);
80
+ const skills = yield listProjectSkillManifests(customComponentsDir, pluginCustomFolderPaths);
82
81
  const adminBasePath = adminforth.config.baseUrlSlashed;
83
82
  const hiddenResourceIdSet = new Set(hiddenResourceIds);
84
83
  const visibleResources = adminforth.config.resources.filter((resource) => !hiddenResourceIdSet.has(resource.resourceId));
@@ -86,11 +85,9 @@ export function buildAgentSystemPrompt(adminforth_1) {
86
85
  DEFAULT_AGENT_SYSTEM_PROMPT,
87
86
  `ADMIN_BASE_PATH: ${adminBasePath}`,
88
87
  `List of resources:\n${formatResources(visibleResources)}`,
89
- primarySkills.length > 0
90
- ? `You have primary skills set:\n${formatSkills(primarySkills, "skill_name")}`
88
+ skills.length > 0
89
+ ? `You have skills set:\n${formatSkills(skills, "skill_name")}`
91
90
  : "",
92
- "You have next default skills which you can fallback to if primary skill set does not provide a good skill:\n" +
93
- formatSkills(defaultSkills, "skill_name"),
94
91
  "Before using any skill, call fetch_skill to load its full instructions.",
95
92
  "The fetched skill response starts with 'Tools mentioned in this skill'. Read that list first.",
96
93
  "You can use get_resource immediately to inspect resource structure and column names.",
@@ -100,8 +97,8 @@ export function buildAgentSystemPrompt(adminforth_1) {
100
97
  "If a fetched skill lists a non-base tool you need, call fetch_tool_schema for it immediately instead of telling the user the tool is unavailable.",
101
98
  "For example: for record creation load mutate_data, read its tool list, call fetch_tool_schema for create_record, and then use create_record after confirmation.",
102
99
  "When fetch_tool_schema succeeds, that tool becomes available on the next step.",
103
- "All admin links must be relative paths and must start with ADMIN_BASE_PATH.",
104
- "Build record links as ADMIN_BASE_PATH + resource/{resourceId}/show/{primary key}. Do not prepend any extra slash before resource.",
100
+ "All admin links must be root-relative and start with '/'.",
101
+ "Build record links as '/resource/{resourceId}/show/{primary key}'. Never use bare 'resource/{resourceId}/show/{primary key}' without the leading slash.",
105
102
  "Try to call as many tools as possible in parallel in one step.",
106
103
  ];
107
104
  return sections.filter(Boolean).join("\n\n");
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- export declare function createFetchSkillTool(customComponentsDir: string): Promise<import("langchain").DynamicStructuredTool<z.ZodObject<{
2
+ export declare function createFetchSkillTool(customComponentsDir: string, pluginCustomFolderPaths?: string[]): Promise<import("langchain").DynamicStructuredTool<z.ZodObject<{
3
3
  skillName: z.ZodString;
4
4
  }, z.core.$strip>, {
5
5
  skillName: string;
@@ -21,13 +21,13 @@ function serializeSkillManifests(skillManifests) {
21
21
  description: skill.description,
22
22
  }));
23
23
  }
24
- export function createFetchSkillTool(customComponentsDir) {
25
- return __awaiter(this, void 0, void 0, function* () {
26
- const availableSkills = yield listSkillManifests(customComponentsDir);
24
+ export function createFetchSkillTool(customComponentsDir_1) {
25
+ return __awaiter(this, arguments, void 0, function* (customComponentsDir, pluginCustomFolderPaths = []) {
26
+ const availableSkills = yield listSkillManifests(customComponentsDir, pluginCustomFolderPaths);
27
27
  const availableSkillNames = availableSkills.map((skill) => skill.name);
28
28
  return tool((_a) => __awaiter(this, [_a], void 0, function* ({ skillName }) {
29
29
  try {
30
- const skillMarkdown = yield loadSkillMarkdown(skillName, customComponentsDir);
30
+ const skillMarkdown = yield loadSkillMarkdown(skillName, customComponentsDir, pluginCustomFolderPaths);
31
31
  if (!skillMarkdown) {
32
32
  return [
33
33
  `Skill "${skillName}" not found.`,
@@ -1,4 +1,4 @@
1
1
  import type { ClientTool } from "@langchain/core/tools";
2
2
  import type { ApiBasedTool } from "../../apiBasedTools.js";
3
3
  export declare const ALWAYS_AVAILABLE_API_TOOL_NAMES: readonly ["get_resource"];
4
- export declare function createAgentTools(customComponentsDir: string, apiBasedTools: Record<string, ApiBasedTool>): Promise<ClientTool[]>;
4
+ export declare function createAgentTools(customComponentsDir: string, apiBasedTools: Record<string, ApiBasedTool>, pluginCustomFolderPaths?: string[]): Promise<ClientTool[]>;
@@ -12,8 +12,8 @@ import { createFetchToolSchemaTool } from "./fetchToolSchema.js";
12
12
  import { createApiTool } from "./apiTool.js";
13
13
  import { createGetUserLocationTool } from "./getUserLocation.js";
14
14
  export const ALWAYS_AVAILABLE_API_TOOL_NAMES = ["get_resource"];
15
- export function createAgentTools(customComponentsDir, apiBasedTools) {
16
- return __awaiter(this, void 0, void 0, function* () {
15
+ export function createAgentTools(customComponentsDir_1, apiBasedTools_1) {
16
+ return __awaiter(this, arguments, void 0, function* (customComponentsDir, apiBasedTools, pluginCustomFolderPaths = []) {
17
17
  return [
18
18
  ...ALWAYS_AVAILABLE_API_TOOL_NAMES.map((toolName) => {
19
19
  const apiBasedTool = apiBasedTools[toolName];
@@ -23,7 +23,7 @@ export function createAgentTools(customComponentsDir, apiBasedTools) {
23
23
  return createApiTool(toolName, apiBasedTool);
24
24
  }),
25
25
  createGetUserLocationTool(),
26
- yield createFetchSkillTool(customComponentsDir),
26
+ yield createFetchSkillTool(customComponentsDir, pluginCustomFolderPaths),
27
27
  yield createFetchToolSchemaTool(apiBasedTools),
28
28
  ];
29
29
  });
@@ -86,6 +86,7 @@ export class AgentTurnService {
86
86
  adminforth,
87
87
  apiBasedTools,
88
88
  customComponentsDir: (_f = adminforth.config.customization.customComponentsDir) !== null && _f !== void 0 ? _f : "custom",
89
+ pluginCustomFolderPaths: adminforth.activatedPlugins.map((plugin) => plugin.customFolderPath),
89
90
  sessionId: input.sessionId,
90
91
  turnId: input.turnId,
91
92
  currentPage: input.currentPage,
@@ -3,7 +3,7 @@ import { callAdminForthApi } from '@/utils';
3
3
  import type { Chat } from '../../chat';
4
4
  import type { IAgentSession, ISessionsListItem, IPart } from '../../types';
5
5
  import { PRE_SESSION_ID } from './constants';
6
- import { useI18n } from 'vue-i18n';
6
+ import { i18nInstance } from '@/i18n';
7
7
 
8
8
  type AdminforthLike = {
9
9
  confirm(options: { message: string; yes: string; no: string }): Promise<boolean>;
@@ -42,7 +42,10 @@ export function createAgentSessionManager({
42
42
  return [...sessionsListToSort].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
43
43
  }
44
44
 
45
- const { t } = useI18n();
45
+ function t(key: string) {
46
+ return i18nInstance?.global.t(key) ?? key;
47
+ }
48
+
46
49
  function saveCurrentSessionInCache() {
47
50
  if (currentSession.value) {
48
51
  currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
@@ -342,4 +345,4 @@ export function createAgentSessionManager({
342
345
  setCurrentChatStatus,
343
346
  updateLastAgentMessage
344
347
  };
345
- }
348
+ }
@@ -118,10 +118,13 @@ const THRESHOLD_TO_SHOW_BUTTON = 10;
118
118
  let messageResizeObserver: ResizeObserver | null = null;
119
119
  let observedLastUserMessageElement: HTMLElement | null = null;
120
120
  let observedLastAgentMessageElement: HTMLElement | null = null;
121
+ let updateSpacerFrameId: number | null = null;
122
+ let pendingSpacerUpdate: Promise<void> | null = null;
123
+ let spacerUpdateQueued = false;
121
124
 
122
125
  onMounted(async () => {
123
126
  messageResizeObserver = new ResizeObserver(() => {
124
- updateSpacerHeight();
127
+ scheduleSpacerHeightUpdate();
125
128
  });
126
129
 
127
130
  await import('@incremark/theme/styles.css')
@@ -136,6 +139,10 @@ onUnmounted(() => {
136
139
 
137
140
  stopObservingLastMessages();
138
141
  messageResizeObserver?.disconnect();
142
+ if (updateSpacerFrameId !== null) {
143
+ cancelAnimationFrame(updateSpacerFrameId);
144
+ updateSpacerFrameId = null;
145
+ }
139
146
  agentStore.stopPlaceholderAnimation();
140
147
  });
141
148
 
@@ -221,6 +228,30 @@ async function updateSpacerHeight() {
221
228
  spacerHeight.value = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
222
229
  }
223
230
 
231
+ function scheduleSpacerHeightUpdate() {
232
+ spacerUpdateQueued = true;
233
+
234
+ if (updateSpacerFrameId !== null) {
235
+ return;
236
+ }
237
+
238
+ updateSpacerFrameId = requestAnimationFrame(() => {
239
+ updateSpacerFrameId = null;
240
+
241
+ if (pendingSpacerUpdate) {
242
+ return;
243
+ }
244
+
245
+ spacerUpdateQueued = false;
246
+ pendingSpacerUpdate = updateSpacerHeight().finally(() => {
247
+ pendingSpacerUpdate = null;
248
+ if (spacerUpdateQueued) {
249
+ scheduleSpacerHeightUpdate();
250
+ }
251
+ });
252
+ });
253
+ }
254
+
224
255
  function stopObservingLastMessages() {
225
256
  if (!messageResizeObserver) {
226
257
  return;
@@ -285,4 +316,4 @@ function recalculateScroll() {
285
316
  }
286
317
  }
287
318
 
288
- </script>
319
+ </script>
@@ -115,7 +115,11 @@
115
115
  return `${window.location.pathname}${window.location.search}${href}`;
116
116
  }
117
117
 
118
- const resolvedUrl = new URL(href, window.location.href);
118
+ const isAbsoluteWithScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href);
119
+ const baseUrl = isAbsoluteWithScheme
120
+ ? undefined
121
+ : `${window.location.origin}/`;
122
+ const resolvedUrl = new URL(href, baseUrl ?? window.location.href);
119
123
  if (resolvedUrl.origin !== window.location.origin) {
120
124
  return null;
121
125
  }
@@ -11,6 +11,7 @@ import { randomUUID } from "crypto";
11
11
  function createAgentEventStream(res, options = {}) {
12
12
  let isStreamClosed = false;
13
13
  let activeBlock = null;
14
+ const isAiUiMessageStream = options.vercelAiUiMessageStream === true;
14
15
  res.writeHead(200, Object.assign({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" }, (options.vercelAiUiMessageStream
15
16
  ? { "x-vercel-ai-ui-message-stream": "v1" }
16
17
  : {})));
@@ -89,7 +90,7 @@ function createAgentEventStream(res, options = {}) {
89
90
  },
90
91
  transcript(text, language) {
91
92
  stream.send({
92
- type: "transcript",
93
+ type: isAiUiMessageStream ? "data-transcript" : "transcript",
93
94
  data: {
94
95
  text,
95
96
  language,
@@ -98,7 +99,7 @@ function createAgentEventStream(res, options = {}) {
98
99
  },
99
100
  response(text, sessionId, turnId) {
100
101
  stream.send({
101
- type: "response",
102
+ type: isAiUiMessageStream ? "data-response" : "response",
102
103
  data: {
103
104
  text,
104
105
  sessionId,
@@ -108,7 +109,7 @@ function createAgentEventStream(res, options = {}) {
108
109
  },
109
110
  speechResponse(transcript, response, sessionId, turnId) {
110
111
  stream.send({
111
- type: "speech-response",
112
+ type: isAiUiMessageStream ? "data-speech-response" : "speech-response",
112
113
  data: {
113
114
  transcript,
114
115
  response,
@@ -119,7 +120,7 @@ function createAgentEventStream(res, options = {}) {
119
120
  },
120
121
  audioStart(mimeType, format, sampleRate, channelCount, bitsPerSample) {
121
122
  stream.send({
122
- type: "audio-start",
123
+ type: isAiUiMessageStream ? "data-audio-start" : "audio-start",
123
124
  data: {
124
125
  mimeType,
125
126
  format,
@@ -131,22 +132,25 @@ function createAgentEventStream(res, options = {}) {
131
132
  },
132
133
  audioDelta(value) {
133
134
  stream.send({
134
- type: "audio-delta",
135
+ type: isAiUiMessageStream ? "data-audio-delta" : "audio-delta",
135
136
  data: {
136
137
  base64: Buffer.from(value).toString("base64"),
137
138
  },
138
139
  });
139
140
  },
140
141
  audioDone() {
141
- stream.send({
142
- type: "audio-done",
143
- });
142
+ stream.send(Object.assign({ type: isAiUiMessageStream ? "data-audio-done" : "audio-done" }, (isAiUiMessageStream ? { data: {} } : {})));
144
143
  },
145
144
  error(error) {
146
- stream.send({
147
- type: "error",
148
- error,
149
- });
145
+ stream.send(isAiUiMessageStream
146
+ ? {
147
+ type: "error",
148
+ errorText: error,
149
+ }
150
+ : {
151
+ type: "error",
152
+ error,
153
+ });
150
154
  },
151
155
  end() {
152
156
  if (isStreamClosed || res.writableEnded || res.destroyed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.47.2",
3
+ "version": "1.48.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -22,6 +22,7 @@ function createAgentEventStream(
22
22
  ) {
23
23
  let isStreamClosed = false;
24
24
  let activeBlock: { type: "text" | "reasoning"; id: string } | null = null;
25
+ const isAiUiMessageStream = options.vercelAiUiMessageStream === true;
25
26
 
26
27
  res.writeHead(200, {
27
28
  "Content-Type": "text/event-stream",
@@ -124,7 +125,7 @@ function createAgentEventStream(
124
125
 
125
126
  transcript(text: string, language?: string) {
126
127
  stream.send({
127
- type: "transcript",
128
+ type: isAiUiMessageStream ? "data-transcript" : "transcript",
128
129
  data: {
129
130
  text,
130
131
  language,
@@ -134,7 +135,7 @@ function createAgentEventStream(
134
135
 
135
136
  response(text: string, sessionId: string, turnId: string) {
136
137
  stream.send({
137
- type: "response",
138
+ type: isAiUiMessageStream ? "data-response" : "response",
138
139
  data: {
139
140
  text,
140
141
  sessionId,
@@ -150,7 +151,7 @@ function createAgentEventStream(
150
151
  turnId: string,
151
152
  ) {
152
153
  stream.send({
153
- type: "speech-response",
154
+ type: isAiUiMessageStream ? "data-speech-response" : "speech-response",
154
155
  data: {
155
156
  transcript,
156
157
  response,
@@ -168,7 +169,7 @@ function createAgentEventStream(
168
169
  bitsPerSample: number,
169
170
  ) {
170
171
  stream.send({
171
- type: "audio-start",
172
+ type: isAiUiMessageStream ? "data-audio-start" : "audio-start",
172
173
  data: {
173
174
  mimeType,
174
175
  format,
@@ -181,7 +182,7 @@ function createAgentEventStream(
181
182
 
182
183
  audioDelta(value: Uint8Array) {
183
184
  stream.send({
184
- type: "audio-delta",
185
+ type: isAiUiMessageStream ? "data-audio-delta" : "audio-delta",
185
186
  data: {
186
187
  base64: Buffer.from(value).toString("base64"),
187
188
  },
@@ -190,15 +191,21 @@ function createAgentEventStream(
190
191
 
191
192
  audioDone() {
192
193
  stream.send({
193
- type: "audio-done",
194
+ type: isAiUiMessageStream ? "data-audio-done" : "audio-done",
195
+ ...(isAiUiMessageStream ? { data: {} } : {}),
194
196
  });
195
197
  },
196
198
 
197
199
  error(error: string) {
198
- stream.send({
199
- type: "error",
200
- error,
201
- });
200
+ stream.send(isAiUiMessageStream
201
+ ? {
202
+ type: "error",
203
+ errorText: error,
204
+ }
205
+ : {
206
+ type: "error",
207
+ error,
208
+ });
202
209
  },
203
210
 
204
211
  end() {