Jint — JS-интерпретатор для смарт-скриптов¶
Все доступные Smart Actions с сигнатурами — MCP-серверы
/mcp-actions-*(21 группа, ~205 действий). Каталог:docs/reference/api/mcp-catalog.md.
Статус¶
Реализовано. JS-скриптинг доступен в продакшене как альтернатива Lua.
Обзор¶
Платформа поддерживает пять языков смарт-скриптов:
| Язык | Runtime | ScriptLanguage (enum) |
LanguageId (БД) |
Документация |
|---|---|---|---|---|
| Lua | NLua (in-process) | Lua = 0 |
0 |
— |
| JavaScript | Jint 4.6.0 (in-process) | JavaScript = 1 |
1 |
этот файл |
| Python | Python Executor (HTTP, Docker) | Python = 2 |
2 |
python-scripting.md |
| OneScript | OneScriptEngine (in-process) | OneScript = 3 |
3 |
— |
| C# | Roslyn Scripting (in-process) | CSharp = 4 |
4 |
csharp-scripting-roslyn.md |
C# — добавлен 2026-04-20. Нативный async/await, прямой доступ к сервисам платформы без маршалинга. Скрипт — тот же C#, что и ядро.
Jint — pure .NET JS-движок без внешних зависимостей. Не требует V8 или Node.js.
Версионирование — обязательно¶
Каждый JS SmartScript обязан содержать комментарий с версией и датой в начале скрипта:
// v1 | 2026-03-08 15:30 | Начальная версия
// v2 | 2026-03-08 16:45 | Добавлена проверка прав
Версия инкрементируется при каждом изменении. Подробнее: docs/domains/smart-actions/js-jint-patterns.md §0.
Архитектура¶
Точка входа¶
SmartScriptService.EvaluateSmartScript(SmartScriptDto, ..) — ветвление по языку:
smartScript.Language == Python
→ EvaluatePythonSmartScript → PythonScriptEngine.ExecuteScript(...)
→ HTTP POST → Python Executor
smartScript.Language == JavaScript
→ EvaluateJSSmartScript → JsScriptEngine.ExecuteScript(...)
→ SmartScriptTypesConverter.ConvertResult(...)
smartScript.Language == CSharp
→ EvaluateCSharpSmartScript → RoslynScriptEngine.ExecuteScript(...)
→ CompiledSmartScriptCache (IL-делегат по hash скрипта)
smartScript.Language == OneScript
→ EvaluateOneScriptSmartScript → OneScriptEngine.ExecuteScript(...)
smartScript.Language == Lua (default)
→ EvaluateLuaSmartScript → LuaScriptEngine.ExecuteScript(...)
→ LuaTypesConverter.FromLuaValue(...)
Файл: TCClassLib/Smart/Scripting/SmartScriptService.cs
Ключевые классы¶
| Класс | Файл | Роль |
|---|---|---|
JsScriptEngine |
Engine/JsScriptEngine.cs |
Основной движок: инициализация Jint, регистрация API, запуск скрипта |
JsValueHelper |
Engine/JsValueHelper.cs |
Конвертация JsValue ↔ CLR-типы |
SmartScriptService |
Smart/Scripting/SmartScriptService.cs |
Маршрутизация Lua/JS/Python/OneScript/CSharp |
SmartScriptEditorService |
Smart/Scripting/SmartScriptEditorService.cs |
Редактор в админке, тестовый запуск |
ScriptLanguage |
Valhalla.Integration/Enums/Smart/ScriptLanguage.cs |
Enum Lua=0, JavaScript=1, Python=2, OneScript=3, CSharp=4 |
SmartScriptEntity |
TCDataAccess/../SmartScriptEntity.cs |
БД: поле LanguageId (ScriptLanguage) |
SmartScriptLanguageEntity |
TCDataAccess/../SmartScriptLanguageEntity.cs |
Справочник SmartScriptLanguages |
Конфигурация движка (sandbox)¶
JsScriptEngine создаёт Jint.Engine с фиксированными ограничениями (не конфигурируется извне):
new JintEngine(options =>
{
options.TimeoutInterval(TimeSpan.FromMinutes(5));
options.MaxStatements(10000000);
options.Interop.ExceptionHandler = _ => true;
});
| Параметр | Значение | Назначение |
|---|---|---|
TimeoutInterval |
5 минут | Защита от бесконечных циклов |
MaxStatements |
10 000 000 | Защита от вычислительных бомб (увеличено с 1M до 10M в 2.268.66) |
ExceptionHandler = _ => true |
все CLR-исключения перехватываются | CLR-исключения пробрасываются как JS Error |
CLR-типы по умолчанию недоступны — только то, что явно зарегистрировано через SetValue.
Sync vs Async режимы исполнения¶
JsScriptEngine (281 строка) поддерживает два режима:
| Параметр | Синхронный (ExecuteScript) |
Асинхронный (ExecuteScriptAsync) |
|---|---|---|
| TaskInterop (C# async из JS) | ❌ | ✅ |
| ES Module loader | ❌ | ✅ (SmartScriptModuleLoader) |
| PromiseTimeout | — | 5 минут (переопределён с дефолтных 10 сек) |
| Wrapping скрипта | напрямую | (async () => { <script> }); |
Async execution flow¶
engine = CreateConfiguredEngine(withTaskInterop: true)
wrappedScript = "(async () => { <user_script> })();"
await engine.ExecuteAsync(wrappedScript, sourceName, cancellationToken)
→ PromiseRejectedException → extract JS stack trace from RejectedValue
→ engine.GetValue("RESULT") → .ToObject()
Async-режим используется в:
- SmartScriptEditorService — тестовый запуск из редактора (с логированием и отменой)
- SmartHtmlDataSourcesService — динамические портальные виджеты
- AnfisaStreamController — streaming для AI-агента (с __STREAM_CALLBACK__)
- StandartActionExecuteSmartScript — действие «Выполнить скрипт» (StandardAction = 106)
Special inputParams (не попадают в JS globals)¶
| Key | Type | Назначение |
|---|---|---|
__SOURCE__ |
string |
Имя скрипта для stack traces |
__LOGGER__ |
SmartScriptExecutionLogger |
Логирование шагов выполнения |
__STREAM_CALLBACK__ |
Action<string,string> |
Streaming-вывод (Анфиса) |
__IS_CANCELLED__ |
Func<bool> |
Проверка отмены (CancellationToken) |
API-объекты (глобальные переменные скрипта)¶
Стандартные переменные¶
| Переменная | Тип | Значение |
|---|---|---|
CONTEXT |
object | Контекст (задача, пользователь, письмо и т.д.) |
EVENTPARAMS |
object | Параметры события (если скрипт привязан к событию). ⚠️ Доступен только на верхнем уровне; внутри try-catch теряет scope. Сохраняй в переменную ДО try. Для публикаций: EVENTPARAMS["PublishedObjectParameters"] содержит {requestBody, headers} (вложенная структура, НЕ плоская). Headers в mixed-case (x-Gitlab-Token). Требует eventId=106 (OnCallPublishedObject) в SmartScript — без него EVENTPARAMS undefined |
SESSION_USER |
object | Текущий пользователь |
DB_TYPE |
string | "MSSQL" или "PG" |
SYSTEM_INFO |
object | { version, app } |
APP_PATH |
string | Путь к директории приложения |
CLIENT |
object | { appType, appVersion } — тип клиента |
RESULT |
any | Возвращаемое значение — движок читает RESULT после выполнения |
API-модули¶
| Объект | Класс | Описание |
|---|---|---|
SQL |
JsSqlApi |
SQL-запросы к БД |
UTILS |
JsUtilsApi |
Утилиты (строки, даты, JSON, XML, шифрование) |
SMART |
JsSmartApi |
Выполнение смарт-действий |
HTTP |
JsHttpApi |
HTTP-запросы (включая multipart) |
CACHE |
JsCacheApi |
In-memory кэш скриптов |
REGISTRY |
JsRegistryApi |
Реестры (чтение/запись записей) |
FILES |
JsFilesApi |
Чтение файлов |
META / МЕТА |
JsMetaApi |
Доступ к конфигурационным сущностям по именам |
VECTORDB |
JsVectorDbApi |
Работа с векторной БД (опциональный, регистрируется при наличии JsVectorDbApiFactory). [deprecated 2026-05-08] Csharp 1f-agent больше не использует API; удаление — отдельная инициатива в core/. |
NLP |
JsNlpApi |
Склонение, числа прописью, конвертация форматов, спеллчек, раскладка (см. nlp-api.md) |
include(id) |
JsLibApi |
Подключение библиотечных скриптов |
Глобальные функции¶
print(str) // вывод в output
var_dump(val) // JSON-дамп значения в output
is_empty(val) // true если null/undefined/""/{}/[]
ts2date(timestamp, utc) // Unix timestamp → "yyyy-MM-dd"
ts2datetime(timestamp, utc) // Unix timestamp → "yyyy-MM-dd HH:mm:ss"
string_starts_with(str, prefix) // аналог str.startsWith
string_ends_with(str, suffix) // аналог str.endsWith
table_has_value(obj, value) // поиск значения в объекте
encrypt(str) // шифрование строки
decrypt(str) // расшифровка строки
SQL API (SQL.*)¶
SQL.scalar(query, params) // → scalar value
SQL.query(sql, params) // → .NET List<Dictionary> (все строки)
SQL.query_one(sql, params) // → первая строка или null — ⚠️ см. ниже
⚠️ Это полный список SQL-методов. SQL.exec / SQL.execute не существует. DML (INSERT/UPDATE/DELETE) — через SQL.scalar с возвратом значения или SQL.query.
Параметры — JS-объект, ключи передаются в запрос как именованные параметры.
⚠️ SQL.query_one не работает в контексте публикаций (eventId=106, OnCallPublishedObject) — возвращает null даже для существующих строк. Используй SQL.query + rows[0]:
var rows = SQL.query("SELECT " + topN(1) + "... WHERE TaskID = " + parseInt(id) + limitN(1));
var task = rows.Count > 0 ? rows[0] : null; // .Count — свойство .NET List
⚠️ Весь SQL в SmartScripts обязан быть DB-агностичным (MSSQL + PG). Глобальная переменная DB_TYPE = "MSSQL" или "PG". Запрещено: [Key], WITH(NOLOCK), TOP N, GETDATE, LEN, ISNULL в прямом виде. Обязательно использовать хелперы qi, topN, NL и т.д. — полный набор и примеры в js-jint-patterns.md §0.1.
UTILS API (UTILS.*)¶
UTILS.print(str)
UTILS.json_encode(val) // → JSON string
UTILS.json_decode(str) // → object
UTILS.base64_encode(str) // с 2.268.58 — также доступна как глобальная base64_encode(str)
UTILS.base64_decode(str) // с 2.268.58 — также доступна как глобальная base64_decode(str)
UTILS.xml_parse(xml) // XML → object
UTILS.xml_serialize(obj) // object → XML
UTILS.xml_to_json(xml)
UTILS.json_to_xml(json)
UTILS.encrypt(str)
UTILS.decrypt(str)
UTILS.resolve_instance(asm, type, ctorParams) // DI resolve
UTILS.create_instance(asm, type, ctorParams) // Activator.CreateInstance
UTILS.open_stream(data) // byte[] → MemoryStream
UTILS.getsecretvalue(serviceKey, fieldName) // → string | null (из IntegrationSecrets)
UTILS.getsecretpayload(serviceKey) // → object | null (весь payload секрета)
UTILS.set_secret_value(serviceKey, fieldName, value) // upsert одного поля payload (с 2.268)
UTILS.set_secret_payload(serviceKey, payload) // upsert всего payload целиком (с 2.268)
UTILS.sha256_hash(text) // → Base64 SHA-256 хеш строки (с 2.268)
UTILS.count_tokens(text, encoding?) // → int, BPE token count (с 2.268)
UTILS.trim_to_tokens(text, maxTokens, encoding?) // → string, обрезать до N токенов (с 2.268)
Получение секретов интеграций (getsecretvalue, getsecretpayload)¶
В серверных смарт-скриптах можно читать секреты интеграций из централизованного хранилища IntegrationSecrets.
| Метод | JavaScript | Lua |
|---|---|---|
| Одно значение | UTILS.getsecretvalue(serviceKey, fieldName) |
UTILS:getsecretvalue(serviceKey, fieldName) |
| Весь набор | UTILS.getsecretpayload(serviceKey) |
UTILS:getsecretpayload(serviceKey) |
serviceKey— ключ секрета в хранилище интеграционных секретов.fieldName— имя поля внутри payload секрета.
Пример JavaScript:
var apiKey = UTILS.getsecretvalue("dadata-5", "ApiKey");
var creds = UTILS.getsecretpayload("sbis-3");
Пример Lua:
local apiKey = UTILS:getsecretvalue("dadata-5", "ApiKey")
local creds = UTILS:getsecretpayload("sbis-3")
Поведение при ошибках: если секрет или поле не найдены — возвращается null. HTTP 500 не возникает.
Аудит: каждое обращение фиксируется в IntegrationSecretsAuditLog с источником smartScript. Расшифрованные значения секретов в логи не попадают.
Ограничения: методы работают только с новым хранилищем IntegrationSecrets — не предназначены для чтения legacy-секретов из старых таблиц настроек сервисов.
Запись секретов интеграций (set_secret_value, set_secret_payload)¶
С 2.268 секреты можно обновлять из SmartScript. Нужно, например, для rotating-токенов (refresh token Passwork, OAuth, …) — SS, запускаемый по расписанию, получает новый токен и сохраняет его обратно в IntegrationSecrets.
| Метод | JavaScript | Lua |
|---|---|---|
| Одно поле (upsert) | UTILS.set_secret_value(serviceKey, fieldName, value) |
UTILS:set_secret_value(serviceKey, fieldName, value) |
| Весь payload (upsert) | UTILS.set_secret_payload(serviceKey, payload) |
UTILS:set_secret_payload(serviceKey, payload) |
set_secret_value— читает текущий payload, подменяет одно поле, сохраняет результат. Остальные поля сохраняются.set_secret_payload— полностью заменяет payload. Поля, не указанные вpayload, удаляются.- Если секрет с таким
serviceKeyне существует — создаётся новый.DisplayNameберётся из существующей записи или равенserviceKeyдля новых. - Значения payload всегда сохраняются как строки.
Пример JavaScript (refresh-токен):
var newToken = UTILS.getsecretvalue("passwork-1", "refresh_token");
// ... обмен на новую пару ...
UTILS.set_secret_value("passwork-1", "access_token", resp.access_token);
UTILS.set_secret_value("passwork-1", "refresh_token", resp.refresh_token);
// Или одним вызовом:
UTILS.set_secret_payload("passwork-1", {
access_token: resp.access_token,
refresh_token: resp.refresh_token,
expires_at: resp.expires_at
});
Пример Lua:
UTILS:set_secret_value("passwork-1", "access_token", resp.access_token)
UTILS:set_secret_payload("passwork-1", {
access_token = resp.access_token,
refresh_token = resp.refresh_token,
expires_at = resp.expires_at
})
Аудит: запись фиксируется в IntegrationSecretsAuditLog с источником smartScript и UserId текущего пользователя — так же, как при чтении. Сами значения секретов в лог не попадают.
Хеширование и токенизация¶
sha256_hash(text) → string¶
Возвращает SHA-256 хеш строки в кодировке Base64.
var hash = UTILS.sha256_hash("текст промпта");
// → "x3Xnt1ft5jDNCqERO9ECZhqziCnKUqZCKreChi8mhkY="
Пустая строка и null возвращают пустую строку.
count_tokens(text, encoding?) → int¶
Подсчитывает количество BPE-токенов в тексте. Используется для оценки размера контекста при работе с LLM.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
text |
string | — | Текст для подсчёта |
encoding |
string | "cl100k_base" |
BPE-кодировка |
Поддерживаемые кодировки:
| Кодировка | Модели |
|---|---|
cl100k_base |
Claude (все версии), GPT-4, GPT-4-turbo — по умолчанию |
o200k_base |
GPT-4o, GPT-4o-mini |
var n = UTILS.count_tokens("Привет, как дела?"); // cl100k_base (по умолчанию)
var n = UTILS.count_tokens(text, "o200k_base"); // для GPT-4o
Пустая строка и null возвращают 0.
trim_to_tokens(text, maxTokens, encoding?) → string¶
Обрезает текст до указанного количества токенов. Возвращает подстроку, гарантированно не превышающую лимит.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
text |
string | — | Исходный текст |
maxTokens |
int | — | Максимальное число токенов |
encoding |
string | "cl100k_base" |
BPE-кодировка |
var trimmed = UTILS.trim_to_tokens(longText, 100000); // обрезать до 100K токенов
Если текст уже укладывается в лимит — возвращается без изменений.
SMART API (SMART.*)¶
SMART.execute_action(action, contextId, contextType, params, async)
SMART.execute_action_in_task_context(action, taskId, params, async)
SMART.execute_action_in_user_context(action, userId, params, async)
SMART.execute_action_in_email_context(action, emailId, params, async)
SMART.run_script_background(scriptId, contextId, eventParams, extraParams, ignoreScriptEvent)
SMART.run_script_sync(scriptIdOrName, contextId, contextType, eventParams, extraParams, ignoreScriptEvent)
action — имя из StandardAction (enum). async = true — выполнение в фоне.
run_script_background — асинхронный запуск смарт-скрипта по ID в фоновой задаче. contextId и eventParams — опциональны (можно null). extraParams — JS-объект с произвольными ключами, пробрасывается в целевой скрипт как extraInputParams. Возврата RESULT нет (fire-and-forget). Подробнее: Смарт-скрипты.
run_script_sync — синхронный запуск с возвратом RESULT (добавлен 2026-04-21). Принимает id (number) или name (string). contextType — строка "task"|"user"|"email"|"edocumentlink"|"edocumentlinksbis", default "task". Блокирующий вызов с корректным awaits async-скриптов и top-level await через EvaluateSmartScriptAsync(..).GetAwaiter.GetResult. Child scope через IocContainer.StartChildScopeInUserContext(SessionUserId) — target выполняется под теми же правами что caller.
Глубина каскада ограничена 3 уровнями (SyncScriptCallGuard, AsyncLocal<int>). На 4-м — TCLogicException("Превышена максимальная глубина синхронных вызовов SmartScript (3)").
Когда что использовать:
| Нужно | Метод |
|-------|-------|
| Выполнить SS в фоне, результат не нужен | run_script_background |
| Получить RESULT синхронно, передать extraParams | run_script_sync |
| Выполнить в том же scope (shared globals) | include — но без extraParams и с потерей Promise из async-скриптов |
| Выполнить action (не SmartScript) | execute_action |
⚠️ PostComment в анонимных публикациях: CommentAuthor обязателен (без него SessionUserId=-1 → 500). Используй CommentAuthor: 3 (Робот 1Ф) + ForcedEmail: false, CommentSMS: false, NoSubscription: true.
⚠️ Обновление ДП: действие ChangeExtParamValue (не UpdateExtParam). Параметры: Task, User, ExtParam (ID), Value (строка), WriteCommentOnChange.
HTTP API (HTTP.*)¶
HTTP.send_http_request(method, url, params, headers, rawBody, options)
HTTP.post_files(url, files, params, headers, options, preuploaded)
HTTP.post_multipart(url, parts, params, headers, options, preuploaded)
Возвращает десериализованный JSON-ответ.
CACHE API (CACHE.*)¶
CACHE.set(key, value, lifetime) // lifetime в секундах, default = MaxInt
CACHE.get(key) // → string или null
CACHE.delete(key)
Использует тот же ScriptDataCache (LuaCache), что и Lua-скрипты — кэш общий.
REGISTRY API (REGISTRY.*)¶
REGISTRY.record_add(registryId, values, overwrite) // → recordId
REGISTRY.record_delete(registryId, recordId)
REGISTRY.record_delete_by_key(registryId, keys)
REGISTRY.record_get(registryId, recordId) // → object
REGISTRY.record_find(registryId, keys, strict) // → array
FILES API (FILES.*)¶
FILES.get_file_content(fileId, versionId) // → byte[]
FILES.get_file_content_string(fileId, versionId, encoding) // → string
LIB (include)¶
include(scriptId) // number: by ID
include("ScriptName") // string: by name or parseable ID
Глубина рекурсии включений — не более 5 уровней. Подключать можно только скрипты с IsLibrary = true.
VECTORDB API (VECTORDB.*) — [deprecated 2026-05-08]¶
[deprecated 2026-05-08] Csharp 1f-agent больше не использует
VECTORDBAPI. Раздел сохранён для legacy JS SmartScript'ов; удаление API — отдельная инициатива вcore/. Текущая архитектура поиска по docs:docs/domains/search/end-to-end-pipeline.md(MSSQL cascade FTS + zvec-search HTTP).
Работа с векторной БД (legacy). Опциональный модуль — регистрируется в JsScriptEngine только при наличии JsVectorDbApiFactory (DI).
Реализация: Engine/JsApi/JsVectorDbApiFactory → JsVectorDbApi.
Logging & Streaming¶
Глобальные функции для логирования и streaming-вывода (Анфиса):
log(message) // запись в ExecutionLogger (виден в редакторе при тестовом запуске)
log_step(name, data) // именованный шаг логирования
stream_write(type, data) // streaming-вывод — токенизированный поток для Анфисы
is_cancelled() // проверка отмены (CancellationToken) → boolean
stream_write работает через __STREAM_CALLBACK__ (Action<string,string>), который передаётся из AnfisaStreamController при streaming-запросах (POST /api/anfisa/stream).
is_cancelled проверяет __IS_CANCELLED__ (Func<bool>) — обёртка над CancellationToken. Позволяет скрипту корректно завершиться при отмене пользователем.
META API (МЕТА.* / META.*)¶
МЕТА (alias META) — глобальный объект SmartScript для получения конфигурационных сущностей по именам и путям без жёстко заданных ID.
Регистрируется в JsScriptEngine как META (EN) и МЕТА (RU). Оба алиаса идентичны.
Два способа обращения¶
Dotted path (строка с точками):
МЕТА.Категория("Продажи.Клиенты") // → Категория proxy
МЕТА.ПолучитьДП("Клиенты.Заказчик") // → ДП proxy
МЕТА.Состояние("Клиенты.Новый") // → Состояние proxy
МЕТА.Шаг("Клиенты.Новый->В работе") // → Шаг proxy
МЕТА.Подпись("Клиенты.Согласование") // → Подпись proxy
МЕТА.Событие("Клиенты.ClientCreated") // → Событие proxy
МЕТА.КолонкаДП("Клиенты.Заказы.Кол-во") // → Колонка proxy (мин. 3 сегмента)
Fluent chain (цепочка типизированных объектов):
МЕТА.Раздел("Продажи").Категория("Клиенты").ПолучитьДП("Заказчик")
МЕТА.Раздел("Продажи").Категория("Клиенты").Состояние("Новый")
Точки входа¶
| Метод | Поддержка dotted path | Мин. сегментов |
|---|---|---|
МЕТА.Категория(path) |
да | 1 (уникальное имя) или Раздел.Кат |
МЕТА.ПолучитьДП(path) |
да | 2 (Кат.ДП) |
МЕТА.Состояние(path) |
да | 2 (Кат.Состояние) |
МЕТА.Шаг(path) |
да | Кат.Откуда->Куда (мин. 3 сегмента с ->) |
МЕТА.Подпись(path) |
да | 2 |
МЕТА.Событие(path) |
да | 2 |
МЕТА.КолонкаДП(path) |
да | 3 (Кат.ДП.Колонка) |
МЕТА.Раздел(name) |
fluent entry | — |
МЕТА.Группа(name) |
нет | — |
МЕТА.Модуль(name) |
нет | — |
МЕТА.Отчёт(name) |
нет | — |
МЕТА.Дашборд(name) |
нет | — |
Интроспекция¶
МЕТА.СписокДП("Клиенты") // → ДП[] всех ДП категории
МЕТА.ТипДП("Клиенты.Заказчик") // → string ("Lookup", "String", "Table", ...)
МЕТА.Справочник("Клиенты") // → bool
Ограничения и правила записи путей¶
- Greedy-right парсинг: при нескольких точках платформа ищет максимально длинный категорийный префикс.
Продажи.Клиенты.Заказчик→ subcategory=Продажи.Клиенты, name=Заказчик. - Мин. 2 сегмента для ДП, Состояния, Подписи, События (
Категория.Имя). - Мин. 3 сегмента для КолонкаДП (
Категория.ДПТаблица.Колонка). - Мин. 3 сегмента для Шага: формат
Категория.Откуда->Куда(стрелка без пробелов). - Неоднозначность имени категории — ошибка времени выполнения. При конфликте имён уточняй через раздел:
МЕТА.Раздел("Продажи").Категория("Клиенты"). Fluent-нотация безопаснее короткого dotted path при неуникальных именах. - При ошибке резолвинга (не найдено / неоднозначно) скрипт получает
ArgumentException.
Полная техническая документация (REST endpoints, кеширование, файлы): docs/domains/smart-filters/meta-api.md.
Конвертация типов (JsValueHelper)¶
| JS-тип | CLR-тип |
|---|---|
null / undefined |
null |
boolean |
bool |
| Целое число | int |
| Дробное | double |
string |
string |
| Array | List<object> |
| Object (plain) | Dictionary<string, object> |
| CLR-объект (ObjectWrapper) | исходный CLR-объект (сохраняется тип) |
Jint автоматически оборачивает CLR-объекты, переданные через SetValue. При конвертации обратно ObjectWrapper.Target возвращает оригинальный CLR-объект (важно для CONTEXT, SESSION_USER).
БД¶
-- Справочник языков
SELECT * FROM SmartScriptLanguages;
-- 0 = Lua, 1 = JavaScript, 2 = Python
-- Поле в SmartScripts
SELECT Id, Description, LanguageId FROM SmartScripts;
Маппинг: SmartScriptEntity.LanguageId (тип ScriptLanguage) ↔ SmartScriptDto.Language.
Сравнение с Lua (NLua)¶
| Аспект | Lua (NLua) | JavaScript (Jint) |
|---|---|---|
| Синтаксис вызова API | SQL:query(sql) (двоеточие) |
SQL.query(sql) (точка) |
| Переменные | local x = .. |
const x = .. / let x = .. |
| Конкатенация | "a" . "b" |
`a${b}` / "a" + "b" |
| Null | nil |
null / undefined |
| Sandbox | Ограниченный (LoadCLRPackage даёт доступ к CLR) | Строгий (только явно зарегистрированные объекты) |
| Таймаут | 5 мин | 5 мин |
| Конвертер результата | LuaTypesConverter |
SmartScriptTypesConverter |
| Ошибки .NET 9 | SEHException вместо managed exceptions | Нет (pure .NET) |
Где используется (вызывающие контексты)¶
Все вызовы идут через SmartScriptService.EvaluateSmartScript:
| Контекст | Файл | Sync/Async |
|---|---|---|
| Смарт-действия (пакеты) | PacksLogic.cs |
sync |
| Действие «Выполнить скрипт» (StandardAction = 106) | StandartActionExecuteSmartScript.cs |
sync/async |
| Шаблоны печати (Word/PDF) | TemplateGenerator.cs |
sync |
| Мобильная лента | Syndicate.cs |
sync |
| Видеоконференции (Jitsi) — модератор + security bypass | JitsiService.cs |
sync |
| MCP AI-инструменты | DynamicToolsFactory.cs |
sync |
| Портальные виджеты (источник данных) | SmartHtmlDataSourcesService.cs |
async |
| Редактор скриптов (тестовый запуск) | SmartScriptEditorService.cs |
async |
| AI-агент Анфиса (streaming) | AnfisaStreamController (POST /api/anfisa/stream) |
async + streaming |
Выбор движка прозрачен для всех вызывающих — он определяется smartScript.Language.
Frontend (AdminSPA)¶
Текущее состояние (v2.268.38)¶
Редактор скриптов (EditLuaExpressionComponent) поддерживает три языка: Lua, JavaScript, Python. Маппинг scriptTypeToMonacoLang определяет язык Monaco по languageId. Шаблон Python по умолчанию: def execute(ctx):.
Автодополнение ДП.* работает для JavaScript (UIMonacoEditorGlobalAutocompleteService). Для Python и Lua автодополнение пока не реализовано.
UIMonacoEditorGlobalAutocompleteService (уже готово для JS)¶
Файл: apps/spa/src/app/common/components/monaco-editor/ui-monaco-editor-global-autocomplete.service.ts
Регистрирует провайдеры для языка javascript при useGlobalAutocomplete=true:
- Автодополнение после ДП. — список динамических параметров задачи из window.ДП
- Hover — описание метода при наведении на ДП.ПолеИмя.МетодНазвание
- Поддерживаемые методы: Значение, ТекстовоеЗначение, ВозможныеЗначения,
ИзменитьЗначение, Сохранить, Обновить, Скрыть, Заблокировать,
ИмяДП, Отобразить
Активируется только при this.lang === 'javascript' в initializeEditor.
В редакторе SmartScript автодополнение для META-выражений строится по данным META API (backend endpoint POST /api/meta/completions). Подсказки доступны для разделов, категорий, ДП, lookup-полей, табличных ДП и их колонок. Реализовано в UIMonacoEditorGlobalAutocompleteService рядом с провайдером ДП.*.
Call flow — от триггера до результата¶
ТРИГГЕР (Button / Timer / API / Event / Editor / AI)
│
▼
SmartScriptService.EvaluateSmartScript[Async](id/code)
│ загрузка из dbo.SmartScripts, выбор языка
├── Lua → LuaScriptEngine (NLua)
├── JS → JsScriptEngine (Jint)
│ ├── SQL.* (JsSqlApi)
│ ├── HTTP.* (JsHttpApi)
│ ├── CACHE.* (JsCacheApi)
│ ├── FILES.* (JsFilesApi)
│ ├── REGISTRY.* (JsRegistryApi)
│ ├── SMART.* (JsSmartApi)
│ ├── META.*/МЕТА.*(JsMetaApi)
│ ├── VECTORDB.* (JsVectorDbApi, опц.)
│ ├── NLP.* (JsNlpApi)
│ ├── LIB/include (JsLibApi)
│ └── log/stream_write/is_cancelled
│ │
│ ▼
│ engine.GetValue("RESULT") → .ToObject()
│
└── Python → PythonScriptEngine → HTTP POST → Python Executor
RESULT — глобальная переменная. Скрипт присваивает RESULT = <value> (без var/let/const → global scope). Jint читает engine.GetValue("RESULT") после выполнения.
Связанные документы¶
docs/domains/smart-actions/backend.md— архитектура смарт-действийdocs/domains/smart-actions/readme.md— обзор доменаdocs/domains/smart-actions/faq-lua-pcall-error-handling.md— проблемы NLua на .NET 9docs/domains/smart-actions/faq-jint-method-overload-resolution.md— неоднозначный выбор перегрузки CLR-метода (кейс SS 544)ServiceFlow/jint-js-scripting.md— исходная постановка (для истории)