Перейти к содержанию

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 больше не использует VECTORDB API. Раздел сохранён для 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/JsVectorDbApiFactoryJsVectorDbApi.

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 9
  • docs/domains/smart-actions/faq-jint-method-overload-resolution.md — неоднозначный выбор перегрузки CLR-метода (кейс SS 544)
  • ServiceFlow/jint-js-scripting.md — исходная постановка (для истории)