@open-mercato/ai-assistant 0.7.0 → 0.7.1-develop.7103.1.41ff100d93
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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -1
- package/dist/modules/ai_assistant/ai-tools/search-pack.js +93 -3
- package/dist/modules/ai_assistant/ai-tools/search-pack.js.map +3 -3
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +2 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js +14 -6
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js +10 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/http-server.js +3 -1
- package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/in-process-client.js +3 -1
- package/dist/modules/ai_assistant/lib/in-process-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-client.js +2 -1
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-server.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js +18 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js.map +7 -0
- package/package.json +8 -7
- package/src/modules/ai_assistant/__tests__/integration/ws-c-tool-pack-coverage.test.ts +5 -0
- package/src/modules/ai_assistant/ai-tools/__tests__/search-pack.test.ts +211 -5
- package/src/modules/ai_assistant/ai-tools/search-pack.ts +110 -4
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +3 -0
- package/src/modules/ai_assistant/lib/__tests__/codemode-tool-annotations.test.ts +58 -0
- package/src/modules/ai_assistant/lib/__tests__/generated-registry-loader.test.ts +16 -0
- package/src/modules/ai_assistant/lib/__tests__/mcp-client.test.ts +30 -0
- package/src/modules/ai_assistant/lib/__tests__/mcp-server-tool-annotations.test.ts +120 -0
- package/src/modules/ai_assistant/lib/__tests__/mcp-tool-annotations.test.ts +57 -0
- package/src/modules/ai_assistant/lib/codemode-tools.ts +21 -7
- package/src/modules/ai_assistant/lib/generated-registry-loader.ts +10 -2
- package/src/modules/ai_assistant/lib/http-server.ts +2 -0
- package/src/modules/ai_assistant/lib/in-process-client.ts +2 -0
- package/src/modules/ai_assistant/lib/mcp-client.ts +1 -0
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +2 -0
- package/src/modules/ai_assistant/lib/mcp-server.ts +2 -0
- package/src/modules/ai_assistant/lib/mcp-tool-annotations.ts +35 -0
- package/src/modules/ai_assistant/lib/types.ts +11 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:ai-assistant] found
|
|
1
|
+
[build:ai-assistant] found 233 entry points
|
|
2
2
|
[build:ai-assistant] built successfully
|
package/AGENTS.md
CHANGED
|
@@ -166,7 +166,7 @@ const listPeopleTool = defineAiTool({
|
|
|
166
166
|
MUST rules:
|
|
167
167
|
- MUST set `requiredFeatures` for any tool that reads or writes tenant data. The wildcard-aware ACL matcher is applied before the handler runs.
|
|
168
168
|
- MUST use Zod for `inputSchema` — never raw JSON Schema.
|
|
169
|
-
- MUST set `isMutation: true` on write tools.
|
|
169
|
+
- MUST set `isMutation: true` on write tools. It gates read-only agents/overrides and drives the MCP `readOnlyHint` in `tools/list`.
|
|
170
170
|
- MUST route every mutation tool through `prepareMutation(...)` (see the Mutation Approvals guide at `/framework/ai-assistant/mutation-approvals`). Writing directly inside the handler bypasses the approval gate — the runtime fails closed and refuses to return a result to the operator.
|
|
171
171
|
- Mutation preview resolvers SHOULD return a normalized `after` snapshot and display hints when tool inputs contain dictionary IDs or other opaque values. Use `display.fieldLabels`, `display.before`, and `display.after` so `field-diff-card` shows operator-friendly names while `field`, `before`, and `after` keep the raw execution values.
|
|
172
172
|
- MUST expose tools to an agent by listing the tool name in the agent's `allowedTools`. Tools not on the whitelist never reach the model.
|
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
canReadSearchEntity,
|
|
4
|
+
filterSearchResultsByEntityAccess,
|
|
5
|
+
resolveReadableEntityTypes
|
|
6
|
+
} from "@open-mercato/shared/lib/search/entityAccess";
|
|
2
7
|
import { defineAiTool } from "../lib/ai-tool-definition.js";
|
|
8
|
+
class SearchToolAuthorizationError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "SearchToolAuthorizationError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function resolveSearchIndexer(ctx) {
|
|
15
|
+
try {
|
|
16
|
+
const indexer = ctx.container.resolve("searchIndexer");
|
|
17
|
+
if (indexer && typeof indexer.getEntityConfig === "function" && typeof indexer.getAllEntityConfigs === "function") {
|
|
18
|
+
return indexer;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
throw new SearchToolAuthorizationError("[internal] Search entity registry unavailable");
|
|
23
|
+
}
|
|
24
|
+
function authorizeEntityAccess(entityType, lookup, subject) {
|
|
25
|
+
if (subject.isSuperAdmin) return;
|
|
26
|
+
let denial;
|
|
27
|
+
const allowed = canReadSearchEntity(entityType, lookup, subject, {
|
|
28
|
+
onDeny: (_, reason) => {
|
|
29
|
+
denial = reason;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (allowed) return;
|
|
33
|
+
if (denial === "unconfigured") {
|
|
34
|
+
throw new SearchToolAuthorizationError(`[internal] Entity type "${entityType}" is not configured for search`);
|
|
35
|
+
}
|
|
36
|
+
if (denial === "no-acl-features") {
|
|
37
|
+
throw new SearchToolAuthorizationError(
|
|
38
|
+
`[internal] Entity type "${entityType}" does not declare aclFeatures; access denied`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const config = lookup.getEntityConfig(entityType);
|
|
42
|
+
const required = config?.aclFeatures ?? [];
|
|
43
|
+
throw new SearchToolAuthorizationError(
|
|
44
|
+
`[internal] Insufficient permissions for entity "${entityType}". Required: ${required.join(", ")}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
3
47
|
const hybridSearchInput = z.object({
|
|
4
48
|
q: z.string().min(1).describe("Search query text."),
|
|
5
49
|
limit: z.number().int().min(1).max(100).optional().describe("Maximum results (default 20, max 100)."),
|
|
@@ -21,13 +65,49 @@ const hybridSearchTool = defineAiTool({
|
|
|
21
65
|
const service = ctx.container.resolve("searchService");
|
|
22
66
|
const limit = input.limit ?? 20;
|
|
23
67
|
const started = Date.now();
|
|
24
|
-
const
|
|
68
|
+
const subject = {
|
|
69
|
+
grantedFeatures: ctx.userFeatures,
|
|
70
|
+
isSuperAdmin: ctx.isSuperAdmin
|
|
71
|
+
};
|
|
72
|
+
if (ctx.isSuperAdmin) {
|
|
73
|
+
const results2 = await service.search(input.q, {
|
|
74
|
+
tenantId: ctx.tenantId,
|
|
75
|
+
organizationId: ctx.organizationId,
|
|
76
|
+
limit,
|
|
77
|
+
strategies: input.strategies,
|
|
78
|
+
entityTypes: input.entityTypes
|
|
79
|
+
});
|
|
80
|
+
const timingMs2 = Date.now() - started;
|
|
81
|
+
const strategiesUsed2 = Array.from(
|
|
82
|
+
new Set(results2.map((result) => result.source).filter((id) => typeof id === "string"))
|
|
83
|
+
);
|
|
84
|
+
return {
|
|
85
|
+
query: input.q,
|
|
86
|
+
totalResults: results2.length,
|
|
87
|
+
results: results2,
|
|
88
|
+
strategiesUsed: strategiesUsed2,
|
|
89
|
+
timing: { ms: timingMs2 }
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const lookup = resolveSearchIndexer(ctx);
|
|
93
|
+
const readableEntityTypes = resolveReadableEntityTypes(lookup, subject, input.entityTypes);
|
|
94
|
+
if (readableEntityTypes && readableEntityTypes.length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
query: input.q,
|
|
97
|
+
totalResults: 0,
|
|
98
|
+
results: [],
|
|
99
|
+
strategiesUsed: [],
|
|
100
|
+
timing: { ms: Date.now() - started }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const rawResults = await service.search(input.q, {
|
|
25
104
|
tenantId: ctx.tenantId,
|
|
26
105
|
organizationId: ctx.organizationId,
|
|
27
106
|
limit,
|
|
28
107
|
strategies: input.strategies,
|
|
29
|
-
entityTypes:
|
|
108
|
+
entityTypes: readableEntityTypes
|
|
30
109
|
});
|
|
110
|
+
const results = filterSearchResultsByEntityAccess(rawResults, lookup, subject);
|
|
31
111
|
const timingMs = Date.now() - started;
|
|
32
112
|
const strategiesUsed = Array.from(
|
|
33
113
|
new Set(results.map((result) => result.source).filter((id) => typeof id === "string"))
|
|
@@ -57,13 +137,23 @@ const getRecordContextTool = defineAiTool({
|
|
|
57
137
|
throw new Error("Tenant context is required for search.get_record_context");
|
|
58
138
|
}
|
|
59
139
|
const input = getRecordContextInput.parse(rawInput);
|
|
140
|
+
const subject = {
|
|
141
|
+
grantedFeatures: ctx.userFeatures,
|
|
142
|
+
isSuperAdmin: ctx.isSuperAdmin
|
|
143
|
+
};
|
|
144
|
+
let lookup;
|
|
145
|
+
if (!ctx.isSuperAdmin) {
|
|
146
|
+
lookup = resolveSearchIndexer(ctx);
|
|
147
|
+
authorizeEntityAccess(input.entityId, lookup, subject);
|
|
148
|
+
}
|
|
60
149
|
const service = ctx.container.resolve("searchService");
|
|
61
|
-
const
|
|
150
|
+
const rawResults = await service.search(input.recordId, {
|
|
62
151
|
tenantId: ctx.tenantId,
|
|
63
152
|
organizationId: ctx.organizationId,
|
|
64
153
|
limit: 5,
|
|
65
154
|
entityTypes: [input.entityId]
|
|
66
155
|
});
|
|
156
|
+
const results = lookup ? filterSearchResultsByEntityAccess(rawResults, lookup, subject) : rawResults;
|
|
67
157
|
const match = results.find((result) => result.recordId === input.recordId);
|
|
68
158
|
if (!match) {
|
|
69
159
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/ai-tools/search-pack.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * General-purpose `search.*` tool pack (Phase 1 WS-C, Step 3.8).\n *\n * These tools are discovered by the generator alongside any other module\n * `ai-tools.ts`; they expose the existing `@open-mercato/search` runtime to\n * agents that whitelist them via `allowedTools`.\n */\nimport { z } from 'zod'\nimport type { SearchOptions, SearchResult, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { defineAiTool } from '../lib/ai-tool-definition'\nimport type { AiToolDefinition } from '../lib/types'\n\ntype SearchServiceLike = {\n search: (query: string, options: SearchOptions) => Promise<SearchResult[]>\n}\n\nconst hybridSearchInput = z.object({\n q: z.string().min(1).describe('Search query text.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .optional()\n .describe('Maximum results (default 20, max 100).'),\n strategies: z\n .array(z.enum(['fulltext', 'vector', 'tokens']))\n .optional()\n .describe('Subset of strategies to run; defaults to the module defaults.'),\n entityTypes: z\n .array(z.string())\n .optional()\n .describe('Filter to specific entity ids (e.g. \"catalog:product\").'),\n})\n\nconst hybridSearchTool = defineAiTool({\n name: 'search.hybrid_search',\n displayName: 'Hybrid search',\n description:\n 'Run a global fulltext + vector + token search across enabled entities for the current tenant/organization.',\n inputSchema: hybridSearchInput,\n requiredFeatures: ['search.view'],\n tags: ['read', 'search'],\n handler: async (rawInput, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for search.hybrid_search')\n }\n const input = hybridSearchInput.parse(rawInput)\n const service = ctx.container.resolve<SearchServiceLike>('searchService')\n const limit = input.limit ?? 20\n const started = Date.now()\n const results = await service.search(input.q, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n limit,\n strategies: input.strategies as SearchStrategyId[] | undefined,\n entityTypes:
|
|
5
|
-
"mappings": "AAOA,SAAS,SAAS;AAElB,SAAS,oBAAoB;AAO7B,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACjC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,oBAAoB;AAAA,EAClD,OAAO,EACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,SAAS,wCAAwC;AAAA,EACpD,YAAY,EACT,MAAM,EAAE,KAAK,CAAC,YAAY,UAAU,QAAQ,CAAC,CAAC,EAC9C,SAAS,EACT,SAAS,+DAA+D;AAAA,EAC3E,aAAa,EACV,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SAAS,yDAAyD;AACvE,CAAC;AAED,MAAM,mBAAmB,aAAa;AAAA,EACpC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,aAAa;AAAA,EAChC,MAAM,CAAC,QAAQ,QAAQ;AAAA,EACvB,SAAS,OAAO,UAAU,QAAQ;AAChC,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AACA,UAAM,QAAQ,kBAAkB,MAAM,QAAQ;AAC9C,UAAM,UAAU,IAAI,UAAU,QAA2B,eAAe;AACxE,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,UAAU,KAAK,IAAI;AACzB,UAAM,UAAU,MAAM,QAAQ,OAAO,MAAM,GAAG;AAAA,
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["/**\n * General-purpose `search.*` tool pack (Phase 1 WS-C, Step 3.8).\n *\n * These tools are discovered by the generator alongside any other module\n * `ai-tools.ts`; they expose the existing `@open-mercato/search` runtime to\n * agents that whitelist them via `allowedTools`.\n */\nimport { z } from 'zod'\nimport type { SearchOptions, SearchResult, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport {\n canReadSearchEntity,\n filterSearchResultsByEntityAccess,\n resolveReadableEntityTypes,\n type SearchEntityAccessSubject,\n type SearchEntityConfigLookup,\n type SearchEntityDenyReason,\n} from '@open-mercato/shared/lib/search/entityAccess'\nimport { defineAiTool } from '../lib/ai-tool-definition'\nimport type { AiToolDefinition, McpToolContext } from '../lib/types'\n\ntype SearchServiceLike = {\n search: (query: string, options: SearchOptions) => Promise<SearchResult[]>\n}\n\nclass SearchToolAuthorizationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SearchToolAuthorizationError'\n }\n}\n\nfunction resolveSearchIndexer(ctx: McpToolContext): SearchEntityConfigLookup {\n try {\n const indexer = ctx.container.resolve('searchIndexer') as SearchEntityConfigLookup | undefined\n if (indexer && typeof indexer.getEntityConfig === 'function' && typeof indexer.getAllEntityConfigs === 'function') {\n return indexer\n }\n } catch {\n // fall through to throw\n }\n throw new SearchToolAuthorizationError('[internal] Search entity registry unavailable')\n}\n\nfunction authorizeEntityAccess(\n entityType: string,\n lookup: SearchEntityConfigLookup,\n subject: SearchEntityAccessSubject,\n): void {\n if (subject.isSuperAdmin) return\n let denial: SearchEntityDenyReason | undefined\n const allowed = canReadSearchEntity(entityType, lookup, subject, {\n onDeny: (_, reason) => {\n denial = reason\n },\n })\n if (allowed) return\n if (denial === 'unconfigured') {\n throw new SearchToolAuthorizationError(`[internal] Entity type \"${entityType}\" is not configured for search`)\n }\n if (denial === 'no-acl-features') {\n throw new SearchToolAuthorizationError(\n `[internal] Entity type \"${entityType}\" does not declare aclFeatures; access denied`,\n )\n }\n const config = lookup.getEntityConfig(entityType)\n const required = config?.aclFeatures ?? []\n throw new SearchToolAuthorizationError(\n `[internal] Insufficient permissions for entity \"${entityType}\". Required: ${required.join(', ')}`,\n )\n}\n\nconst hybridSearchInput = z.object({\n q: z.string().min(1).describe('Search query text.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .optional()\n .describe('Maximum results (default 20, max 100).'),\n strategies: z\n .array(z.enum(['fulltext', 'vector', 'tokens']))\n .optional()\n .describe('Subset of strategies to run; defaults to the module defaults.'),\n entityTypes: z\n .array(z.string())\n .optional()\n .describe('Filter to specific entity ids (e.g. \"catalog:product\").'),\n})\n\nconst hybridSearchTool = defineAiTool({\n name: 'search.hybrid_search',\n displayName: 'Hybrid search',\n description:\n 'Run a global fulltext + vector + token search across enabled entities for the current tenant/organization.',\n inputSchema: hybridSearchInput,\n requiredFeatures: ['search.view'],\n tags: ['read', 'search'],\n handler: async (rawInput, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for search.hybrid_search')\n }\n const input = hybridSearchInput.parse(rawInput)\n const service = ctx.container.resolve<SearchServiceLike>('searchService')\n const limit = input.limit ?? 20\n const started = Date.now()\n const subject: SearchEntityAccessSubject = {\n grantedFeatures: ctx.userFeatures,\n isSuperAdmin: ctx.isSuperAdmin,\n }\n\n if (ctx.isSuperAdmin) {\n const results = await service.search(input.q, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n limit,\n strategies: input.strategies as SearchStrategyId[] | undefined,\n entityTypes: input.entityTypes,\n })\n const timingMs = Date.now() - started\n const strategiesUsed = Array.from(\n new Set(results.map((result) => result.source).filter((id): id is SearchStrategyId => typeof id === 'string')),\n )\n return {\n query: input.q,\n totalResults: results.length,\n results,\n strategiesUsed,\n timing: { ms: timingMs },\n }\n }\n\n const lookup = resolveSearchIndexer(ctx)\n const readableEntityTypes = resolveReadableEntityTypes(lookup, subject, input.entityTypes)\n if (readableEntityTypes && readableEntityTypes.length === 0) {\n return {\n query: input.q,\n totalResults: 0,\n results: [],\n strategiesUsed: [],\n timing: { ms: Date.now() - started },\n }\n }\n\n const rawResults = await service.search(input.q, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n limit,\n strategies: input.strategies as SearchStrategyId[] | undefined,\n entityTypes: readableEntityTypes,\n })\n const results = filterSearchResultsByEntityAccess(rawResults, lookup, subject)\n const timingMs = Date.now() - started\n const strategiesUsed = Array.from(\n new Set(results.map((result) => result.source).filter((id): id is SearchStrategyId => typeof id === 'string')),\n )\n return {\n query: input.q,\n totalResults: results.length,\n results,\n strategiesUsed,\n timing: { ms: timingMs },\n }\n },\n})\n\nconst getRecordContextInput = z.object({\n entityId: z.string().min(1).describe('Entity identifier (e.g. \"customers:customer_person_profile\").'),\n recordId: z.string().min(1).describe('Record primary key (UUID).'),\n})\n\nconst getRecordContextTool = defineAiTool({\n name: 'search.get_record_context',\n displayName: 'Get record context',\n description:\n 'Resolve presenter, links, and URL for a specific record by re-querying the search index. Returns { found: false } when no hit matches the recordId.',\n inputSchema: getRecordContextInput,\n requiredFeatures: ['search.view'],\n tags: ['read', 'search'],\n handler: async (rawInput, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for search.get_record_context')\n }\n const input = getRecordContextInput.parse(rawInput)\n const subject: SearchEntityAccessSubject = {\n grantedFeatures: ctx.userFeatures,\n isSuperAdmin: ctx.isSuperAdmin,\n }\n\n let lookup: SearchEntityConfigLookup | undefined\n if (!ctx.isSuperAdmin) {\n lookup = resolveSearchIndexer(ctx)\n authorizeEntityAccess(input.entityId, lookup, subject)\n }\n\n const service = ctx.container.resolve<SearchServiceLike>('searchService')\n const rawResults = await service.search(input.recordId, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n limit: 5,\n entityTypes: [input.entityId],\n })\n const results = lookup ? filterSearchResultsByEntityAccess(rawResults, lookup, subject) : rawResults\n const match = results.find((result) => result.recordId === input.recordId)\n if (!match) {\n return {\n found: false as const,\n entityId: input.entityId,\n recordId: input.recordId,\n }\n }\n return {\n found: true as const,\n entityId: match.entityId,\n recordId: match.recordId,\n presenter: match.presenter,\n url: match.url,\n links: match.links,\n metadata: match.metadata,\n source: match.source,\n score: match.score,\n }\n },\n})\n\nexport const searchAiTools: AiToolDefinition<any, any>[] = [hybridSearchTool, getRecordContextTool]\n\nexport default searchAiTools\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["results", "timingMs", "strategiesUsed"]
|
|
7
7
|
}
|
|
@@ -77,6 +77,7 @@ function AiModerationFlagsPageClient() {
|
|
|
77
77
|
[t]
|
|
78
78
|
);
|
|
79
79
|
const total = query.data?.total ?? 0;
|
|
80
|
+
const totalIsCapped = query.data?.totalIsCapped === true;
|
|
80
81
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
81
82
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
82
83
|
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: t("ai_assistant.moderationFlags.title", "Moderation flags") }),
|
|
@@ -125,6 +126,7 @@ function AiModerationFlagsPageClient() {
|
|
|
125
126
|
pageSize: PAGE_SIZE,
|
|
126
127
|
total,
|
|
127
128
|
totalPages: Math.max(1, Math.ceil(total / PAGE_SIZE)),
|
|
129
|
+
totalIsCapped,
|
|
128
130
|
onPageChange: setPage
|
|
129
131
|
}
|
|
130
132
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\nconst PAGE_SIZE = 50\n\ntype ModerationCategory = { flagged: boolean; score: number }\n\ntype ModerationFlagRow = {\n id: string\n tenantId: string\n organizationId: string | null\n agentId: string\n userId: string\n providerId: string\n modelId: string\n categories: Record<string, ModerationCategory>\n createdAt: string\n}\n\ntype ModerationFlagsResponse = {\n items: ModerationFlagRow[]\n total: number\n page: number\n pageSize: number\n}\n\nasync function fetchModerationFlags(params: {\n page: number\n from: string\n to: string\n agentId: string\n userId: string\n}): Promise<ModerationFlagsResponse> {\n const search = new URLSearchParams()\n search.set('page', String(params.page))\n search.set('pageSize', String(PAGE_SIZE))\n if (params.from) search.set('from', params.from)\n if (params.to) search.set('to', params.to)\n if (params.agentId) search.set('agentId', params.agentId)\n if (params.userId) search.set('userId', params.userId)\n const { result, status } = await apiCallOrThrow<ModerationFlagsResponse>(\n `/api/ai_assistant/moderation-flags?${search.toString()}`,\n undefined,\n { errorMessage: 'Failed to load moderation flags' },\n )\n if (!result) throw new Error(`Failed to load moderation flags (${status})`)\n return result\n}\n\nfunction flaggedCategoryNames(categories: Record<string, ModerationCategory>): string[] {\n return Object.entries(categories)\n .filter(([, value]) => value.flagged)\n .map(([name]) => name)\n}\n\nexport function AiModerationFlagsPageClient() {\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [from, setFrom] = React.useState('')\n const [to, setTo] = React.useState('')\n const [agentId, setAgentId] = React.useState('')\n const [userId, setUserId] = React.useState('')\n const [applied, setApplied] = React.useState({ from: '', to: '', agentId: '', userId: '' })\n\n const query = useQuery<ModerationFlagsResponse>({\n queryKey: ['ai_assistant', 'moderation_flags', page, applied],\n queryFn: () => fetchModerationFlags({ page, ...applied }),\n retry: false,\n })\n\n const applyFilters = React.useCallback(() => {\n setPage(1)\n setApplied({ from, to, agentId, userId })\n }, [from, to, agentId, userId])\n\n const columns = React.useMemo<ColumnDef<ModerationFlagRow, unknown>[]>(\n () => [\n {\n accessorKey: 'agentId',\n header: t('ai_assistant.moderationFlags.columns.agent', 'Agent'),\n },\n {\n accessorKey: 'userId',\n header: t('ai_assistant.moderationFlags.columns.user', 'User'),\n meta: { truncate: true, maxWidth: 220 },\n },\n {\n accessorKey: 'categories',\n header: t('ai_assistant.moderationFlags.columns.categories', 'Categories'),\n cell: ({ row }) => {\n const names = flaggedCategoryNames(row.original.categories)\n if (names.length === 0) {\n return <span className=\"text-muted-foreground\">\u2014</span>\n }\n return (\n <div className=\"flex flex-wrap gap-1\">\n {names.map((name) => (\n <StatusBadge key={name} variant=\"error\" dot>\n {name}\n </StatusBadge>\n ))}\n </div>\n )\n },\n },\n {\n accessorKey: 'createdAt',\n header: t('ai_assistant.moderationFlags.columns.createdAt', 'Flagged at'),\n cell: ({ row }) => new Date(row.original.createdAt).toLocaleString(),\n },\n ],\n [t],\n )\n\n const total = query.data?.total ?? 0\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div>\n <h2 className=\"text-base font-semibold\">\n {t('ai_assistant.moderationFlags.title', 'Moderation flags')}\n </h2>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'ai_assistant.moderationFlags.subtitle',\n 'Inputs blocked by the content safety filter. Category flags and scores only \u2014 no prompt content is stored.',\n )}\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-from\">{t('ai_assistant.moderationFlags.filters.from', 'From')}</Label>\n <Input id=\"moderation-from\" type=\"date\" value={from} onChange={(e) => setFrom(e.target.value)} />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-to\">{t('ai_assistant.moderationFlags.filters.to', 'To')}</Label>\n <Input id=\"moderation-to\" type=\"date\" value={to} onChange={(e) => setTo(e.target.value)} />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-agent\">{t('ai_assistant.moderationFlags.columns.agent', 'Agent')}</Label>\n <Input\n id=\"moderation-agent\"\n value={agentId}\n onChange={(e) => setAgentId(e.target.value)}\n placeholder={t('ai_assistant.moderationFlags.filters.agentPlaceholder', 'module.agent')}\n />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-user\">{t('ai_assistant.moderationFlags.columns.user', 'User')}</Label>\n <Input id=\"moderation-user\" value={userId} onChange={(e) => setUserId(e.target.value)} />\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={applyFilters}>\n {t('ai_assistant.moderationFlags.filters.apply', 'Apply')}\n </Button>\n </div>\n\n <DataTable<ModerationFlagRow>\n columns={columns}\n data={query.data?.items ?? []}\n isLoading={query.isLoading}\n error={query.error ? (query.error as Error).message : undefined}\n emptyState={t('ai_assistant.moderationFlags.empty', 'No flagged messages.')}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages: Math.max(1, Math.ceil(total / PAGE_SIZE)),\n onPageChange: setPage,\n }}\n />\n </div>\n )\n}\n\nexport default AiModerationFlagsPageClient\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\nconst PAGE_SIZE = 50\n\ntype ModerationCategory = { flagged: boolean; score: number }\n\ntype ModerationFlagRow = {\n id: string\n tenantId: string\n organizationId: string | null\n agentId: string\n userId: string\n providerId: string\n modelId: string\n categories: Record<string, ModerationCategory>\n createdAt: string\n}\n\ntype ModerationFlagsResponse = {\n items: ModerationFlagRow[]\n total: number\n page: number\n pageSize: number\n totalIsCapped?: boolean\n}\n\nasync function fetchModerationFlags(params: {\n page: number\n from: string\n to: string\n agentId: string\n userId: string\n}): Promise<ModerationFlagsResponse> {\n const search = new URLSearchParams()\n search.set('page', String(params.page))\n search.set('pageSize', String(PAGE_SIZE))\n if (params.from) search.set('from', params.from)\n if (params.to) search.set('to', params.to)\n if (params.agentId) search.set('agentId', params.agentId)\n if (params.userId) search.set('userId', params.userId)\n const { result, status } = await apiCallOrThrow<ModerationFlagsResponse>(\n `/api/ai_assistant/moderation-flags?${search.toString()}`,\n undefined,\n { errorMessage: 'Failed to load moderation flags' },\n )\n if (!result) throw new Error(`Failed to load moderation flags (${status})`)\n return result\n}\n\nfunction flaggedCategoryNames(categories: Record<string, ModerationCategory>): string[] {\n return Object.entries(categories)\n .filter(([, value]) => value.flagged)\n .map(([name]) => name)\n}\n\nexport function AiModerationFlagsPageClient() {\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [from, setFrom] = React.useState('')\n const [to, setTo] = React.useState('')\n const [agentId, setAgentId] = React.useState('')\n const [userId, setUserId] = React.useState('')\n const [applied, setApplied] = React.useState({ from: '', to: '', agentId: '', userId: '' })\n\n const query = useQuery<ModerationFlagsResponse>({\n queryKey: ['ai_assistant', 'moderation_flags', page, applied],\n queryFn: () => fetchModerationFlags({ page, ...applied }),\n retry: false,\n })\n\n const applyFilters = React.useCallback(() => {\n setPage(1)\n setApplied({ from, to, agentId, userId })\n }, [from, to, agentId, userId])\n\n const columns = React.useMemo<ColumnDef<ModerationFlagRow, unknown>[]>(\n () => [\n {\n accessorKey: 'agentId',\n header: t('ai_assistant.moderationFlags.columns.agent', 'Agent'),\n },\n {\n accessorKey: 'userId',\n header: t('ai_assistant.moderationFlags.columns.user', 'User'),\n meta: { truncate: true, maxWidth: 220 },\n },\n {\n accessorKey: 'categories',\n header: t('ai_assistant.moderationFlags.columns.categories', 'Categories'),\n cell: ({ row }) => {\n const names = flaggedCategoryNames(row.original.categories)\n if (names.length === 0) {\n return <span className=\"text-muted-foreground\">\u2014</span>\n }\n return (\n <div className=\"flex flex-wrap gap-1\">\n {names.map((name) => (\n <StatusBadge key={name} variant=\"error\" dot>\n {name}\n </StatusBadge>\n ))}\n </div>\n )\n },\n },\n {\n accessorKey: 'createdAt',\n header: t('ai_assistant.moderationFlags.columns.createdAt', 'Flagged at'),\n cell: ({ row }) => new Date(row.original.createdAt).toLocaleString(),\n },\n ],\n [t],\n )\n\n const total = query.data?.total ?? 0\n const totalIsCapped = query.data?.totalIsCapped === true\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div>\n <h2 className=\"text-base font-semibold\">\n {t('ai_assistant.moderationFlags.title', 'Moderation flags')}\n </h2>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'ai_assistant.moderationFlags.subtitle',\n 'Inputs blocked by the content safety filter. Category flags and scores only \u2014 no prompt content is stored.',\n )}\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-from\">{t('ai_assistant.moderationFlags.filters.from', 'From')}</Label>\n <Input id=\"moderation-from\" type=\"date\" value={from} onChange={(e) => setFrom(e.target.value)} />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-to\">{t('ai_assistant.moderationFlags.filters.to', 'To')}</Label>\n <Input id=\"moderation-to\" type=\"date\" value={to} onChange={(e) => setTo(e.target.value)} />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-agent\">{t('ai_assistant.moderationFlags.columns.agent', 'Agent')}</Label>\n <Input\n id=\"moderation-agent\"\n value={agentId}\n onChange={(e) => setAgentId(e.target.value)}\n placeholder={t('ai_assistant.moderationFlags.filters.agentPlaceholder', 'module.agent')}\n />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Label htmlFor=\"moderation-user\">{t('ai_assistant.moderationFlags.columns.user', 'User')}</Label>\n <Input id=\"moderation-user\" value={userId} onChange={(e) => setUserId(e.target.value)} />\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={applyFilters}>\n {t('ai_assistant.moderationFlags.filters.apply', 'Apply')}\n </Button>\n </div>\n\n <DataTable<ModerationFlagRow>\n columns={columns}\n data={query.data?.items ?? []}\n isLoading={query.isLoading}\n error={query.error ? (query.error as Error).message : undefined}\n emptyState={t('ai_assistant.moderationFlags.empty', 'No flagged messages.')}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages: Math.max(1, Math.ceil(total / PAGE_SIZE)),\n totalIsCapped,\n onPageChange: setPage,\n }}\n />\n </div>\n )\n}\n\nexport default AiModerationFlagsPageClient\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -378,6 +378,9 @@ function buildEntitySchemas(graph) {
|
|
|
378
378
|
}
|
|
379
379
|
const CODE_MODE_MAX_API_CALLS = 50;
|
|
380
380
|
const CODE_MODE_MAX_MUTATION_CALLS = 20;
|
|
381
|
+
function registerCodeModeTool(tool) {
|
|
382
|
+
registerMcpTool(tool, { moduleId: "codemode" });
|
|
383
|
+
}
|
|
381
384
|
async function loadCodeModeTools() {
|
|
382
385
|
const commonTypes = await generateCommonTypes();
|
|
383
386
|
registerSearchTool();
|
|
@@ -385,9 +388,10 @@ async function loadCodeModeTools() {
|
|
|
385
388
|
return 2;
|
|
386
389
|
}
|
|
387
390
|
function registerSearchTool() {
|
|
388
|
-
|
|
391
|
+
registerCodeModeTool(
|
|
389
392
|
{
|
|
390
393
|
name: "search",
|
|
394
|
+
isMutation: false,
|
|
391
395
|
description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.
|
|
392
396
|
Globals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.
|
|
393
397
|
Use BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,
|
|
@@ -447,17 +451,22 @@ Use BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common
|
|
|
447
451
|
_memoryContext: memoryContext
|
|
448
452
|
};
|
|
449
453
|
}
|
|
450
|
-
}
|
|
451
|
-
{ moduleId: "codemode" }
|
|
454
|
+
}
|
|
452
455
|
);
|
|
453
456
|
}
|
|
454
457
|
function registerExecuteTool(commonTypes) {
|
|
455
458
|
const typesBlock = commonTypes ? `
|
|
456
459
|
|
|
457
460
|
${commonTypes}` : "";
|
|
458
|
-
|
|
461
|
+
registerCodeModeTool(
|
|
459
462
|
{
|
|
460
463
|
name: "execute",
|
|
464
|
+
// api.request() reaches every documented endpoint, including POST/PUT/DELETE,
|
|
465
|
+
// so the tool is neither read-only nor guaranteed non-destructive. It is
|
|
466
|
+
// intentionally exempt from prepareMutation: arbitrary sandbox code cannot
|
|
467
|
+
// provide the structured before/after preview that approval flow requires.
|
|
468
|
+
isMutation: true,
|
|
469
|
+
isDestructive: true,
|
|
461
470
|
description: `Make API calls. Returns JSON.
|
|
462
471
|
Globals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.
|
|
463
472
|
RULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,
|
|
@@ -526,8 +535,7 @@ RULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collecti
|
|
|
526
535
|
_memoryContext: memoryContext
|
|
527
536
|
};
|
|
528
537
|
}
|
|
529
|
-
}
|
|
530
|
-
{ moduleId: "codemode" }
|
|
538
|
+
}
|
|
531
539
|
);
|
|
532
540
|
}
|
|
533
541
|
function createApiRequestFn(ctx, onCall) {
|