@naparnik/mcp 0.10.21 → 0.10.25
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/CHANGELOG.md +30 -0
- package/dist/{chunk-5DKUJSAN.js → chunk-F5B2GHZW.js} +1074 -295
- package/dist/cli.js +26 -12
- package/dist/index.d.ts +842 -633
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -119,748 +119,856 @@ declare class NaparnikApiClient {
|
|
|
119
119
|
private parse;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/** Определение ресурса в MCP */
|
|
123
|
+
interface McpResource {
|
|
124
|
+
/** URI ресурса (уникальный идентификатор) */
|
|
125
|
+
uri: string;
|
|
126
|
+
/** Человекочитаемое имя */
|
|
127
|
+
name: string;
|
|
128
|
+
/** Описание ресурса (опционально) */
|
|
129
|
+
description?: string;
|
|
130
|
+
/** MIME-тип содержимого (опционально) */
|
|
131
|
+
mimeType?: string;
|
|
132
|
+
}
|
|
133
|
+
/** Текстовое содержимое ресурса */
|
|
134
|
+
interface McpResourceTextContent {
|
|
135
|
+
uri: string;
|
|
136
|
+
mimeType?: string;
|
|
137
|
+
text: string;
|
|
138
|
+
/** Метаданные расширений: у виджета MCP Apps здесь `ui.csp`, `ui.prefersBorder` */
|
|
139
|
+
_meta?: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
/** Бинарное содержимое ресурса */
|
|
142
|
+
interface McpResourceBlobContent {
|
|
143
|
+
uri: string;
|
|
144
|
+
mimeType?: string;
|
|
145
|
+
/** Base64-кодированные данные */
|
|
146
|
+
blob: string;
|
|
147
|
+
}
|
|
148
|
+
/** Содержимое ресурса — текст или бинарные данные */
|
|
149
|
+
type McpResourceItemContent = McpResourceTextContent | McpResourceBlobContent;
|
|
150
|
+
|
|
122
151
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* ⚠ ТРИ ВАРИАНТА, А НЕ ДВА, И РОВНО ТЕ ЖЕ, ЧТО У ДВИЖКА. Источник правды —
|
|
126
|
-
* `engine/shell-contract.json` → `portable_python.inside`: оттуда их берёт и
|
|
127
|
-
* питон (`engine/setup/python_finder.py`). Копия здесь неизбежна — питон мы
|
|
128
|
-
* ищем ДО того, как есть чем запустить питон, — но прошлая копия знала два
|
|
129
|
-
* варианта из трёх: на сборке, где внутри лежит только `bin/python`, движок
|
|
130
|
-
* питон видел, а сервер MCP нет. Правится сначала JSON, потом сюда; расхождение
|
|
131
|
-
* ловит тест границы (`engine.test.ts`) сверкой с этим JSON.
|
|
132
|
-
*/
|
|
133
|
-
declare const INSIDE_PYTHON: string[];
|
|
134
|
-
/** Дом по умолчанию — ровно тот же литерал, что в `engine/kit/home.py`. */
|
|
135
|
-
declare const DEFAULT_HOME = "~/.naparnik";
|
|
136
|
-
/**
|
|
137
|
-
* Шаги установки, которые идут минутами и часами: их нельзя держать внутри
|
|
138
|
-
* вызова инструмента. Секундами считается только `check`.
|
|
139
|
-
*
|
|
140
|
-
* ⚠ «python» здесь вместе с портативной сборкой: шаг, который раньше лишь
|
|
141
|
-
* советовал сходить на python.org, теперь может качать от 24 до 104 МБ.
|
|
142
|
-
* Оставь его быстрым — и на медленной связи клиентская программа оборвала бы
|
|
143
|
-
* вызов по своему таймауту, а скачивание осталось бы сиротой; повтор начал бы
|
|
144
|
-
* второе.
|
|
145
|
-
*/
|
|
146
|
-
declare const LONG_STEPS: Set<string>;
|
|
147
|
-
/**
|
|
148
|
-
* Все шаги установки, которые понимает движок.
|
|
149
|
-
*
|
|
150
|
-
* ⚠ ЭТО ЗЕРКАЛО `STEPS` движка, и оно уже один раз разошлось: движок печатал
|
|
151
|
-
* «Шаг установки „вырез“», а здешний список о таком шаге не знал — клиент
|
|
152
|
-
* оказывался в тупике, потому что инструмент не принимал значение, которое
|
|
153
|
-
* движок сам же и назвал. Источник правды — `engine/shell-contract.json` →
|
|
154
|
-
* `install_steps`: оттуда `STEPS` берёт и `setup/install.py`. Правится сверху
|
|
155
|
-
* вниз: сначала JSON, потом сюда. Расхождение сторожит `engine.test.ts` сверкой
|
|
156
|
-
* с этим JSON.
|
|
157
|
-
*/
|
|
158
|
-
declare const INSTALL_STEPS: string[];
|
|
159
|
-
/**
|
|
160
|
-
* Паспорт пака: имена этапов, список моделей, чем запускать.
|
|
152
|
+
* Типы и утилиты для инструментов агентного цикла.
|
|
161
153
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
154
|
+
* Порт из packages/agent-core/src/tools/tool.ts с расширениями:
|
|
155
|
+
* - readOnly флаг для параллельного выполнения
|
|
156
|
+
* - group — категория инструмента для фильтрации и UI
|
|
157
|
+
* - emitProgress в ToolContext — стриминг прогресса без возврата из execute
|
|
158
|
+
* - agentId в ToolContext — для мульти-агентных сценариев
|
|
165
159
|
*/
|
|
166
160
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* имена полей — его. Переименование здесь ничего не меняет на той стороне:
|
|
170
|
-
* оболочка просто перестала бы находить поля и объявила бы «паспорт не
|
|
171
|
-
* прочитан» на каждом запуске. Поля, несущие имена, сверяет с настоящими
|
|
172
|
-
* `pack.json` и рецептом `tool-declaration.test.ts`.
|
|
161
|
+
* Примитивный тип JSON Schema для параметра инструмента.
|
|
162
|
+
* Ограниченное подмножество JSON Schema — только то, что понимают LLM-провайдеры.
|
|
173
163
|
*/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
164
|
+
type JSONSchemaType = {
|
|
165
|
+
type: 'string';
|
|
166
|
+
description?: string;
|
|
167
|
+
enum?: string[];
|
|
168
|
+
/** Максимальная длина строки — защита от DoS через гигантский input. */
|
|
169
|
+
maxLength?: number;
|
|
170
|
+
/** Минимальная длина строки. */
|
|
171
|
+
minLength?: number;
|
|
172
|
+
} | {
|
|
173
|
+
type: 'number';
|
|
174
|
+
description?: string;
|
|
175
|
+
} | {
|
|
176
|
+
type: 'integer';
|
|
177
|
+
description?: string;
|
|
178
|
+
} | {
|
|
179
|
+
type: 'boolean';
|
|
180
|
+
description?: string;
|
|
181
|
+
} | {
|
|
182
|
+
type: 'array';
|
|
183
|
+
items: JSONSchemaType;
|
|
184
|
+
description?: string;
|
|
185
|
+
} | {
|
|
186
|
+
type: 'object';
|
|
187
|
+
properties: Record<string, JSONSchemaType>;
|
|
188
|
+
required?: string[];
|
|
189
|
+
description?: string;
|
|
186
190
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* того, кто печатает в терминале, — через инструмент их попросить было нечем.
|
|
193
|
-
* Старый пак поля не отдаёт: тогда схема остаётся с обязательными ключами.
|
|
191
|
+
* Разрешить произвольные ключи в объекте (валидный JSON Schema keyword).
|
|
192
|
+
* Полезно для object-параметров с динамическими полями (params интеграций),
|
|
193
|
+
* где нужно явно подсказать LLM что это словарь произвольных ключей.
|
|
194
|
+
* При использовании совмещать с `properties: {}` для соответствия общему
|
|
195
|
+
* стилю кодовой базы.
|
|
194
196
|
*/
|
|
195
|
-
|
|
196
|
-
'kind': string;
|
|
197
|
-
'about': string;
|
|
198
|
-
}>;
|
|
199
|
-
}
|
|
200
|
-
/** Ответ команды движка в виде, пригодном для инструмента. */
|
|
201
|
-
type CommandOutcome = {
|
|
202
|
-
ok: true;
|
|
203
|
-
text: string;
|
|
204
|
-
} | {
|
|
205
|
-
ok: false;
|
|
206
|
-
text: string;
|
|
197
|
+
additionalProperties?: boolean;
|
|
207
198
|
};
|
|
208
|
-
/**
|
|
209
|
-
interface
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
pack: string;
|
|
214
|
-
/** Полный путь к `console.py`. */
|
|
215
|
-
consolePy: string;
|
|
199
|
+
/** Схема входных параметров инструмента (JSON Schema объект верхнего уровня) */
|
|
200
|
+
interface ToolInputSchema {
|
|
201
|
+
type: 'object';
|
|
202
|
+
properties: Record<string, JSONSchemaType>;
|
|
203
|
+
required?: string[];
|
|
216
204
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Контекст, передаваемый в каждый вызов инструмента.
|
|
207
|
+
*
|
|
208
|
+
* readFileTimestamps — критически важен для защиты от устаревших записей:
|
|
209
|
+
* перед перезаписью файла инструменты write/edit проверяют, что файл был
|
|
210
|
+
* прочитан в текущей сессии и не изменился с момента чтения.
|
|
211
|
+
*/
|
|
212
|
+
interface ToolContext {
|
|
213
|
+
/** Уникальный идентификатор текущей сессии */
|
|
214
|
+
sessionId: string;
|
|
215
|
+
/** Сигнал отмены — инструменты должны проверять его при длительных операциях */
|
|
216
|
+
abortSignal?: AbortSignal;
|
|
217
|
+
/** Среда выполнения — влияет на доступные операции */
|
|
218
|
+
env: 'electron' | 'server' | 'test';
|
|
219
|
+
/** Рабочая директория для файловых операций */
|
|
220
|
+
workingDirectory?: string;
|
|
223
221
|
/**
|
|
224
|
-
*
|
|
222
|
+
* Временные метки последнего чтения файлов (путь → timestamp в мс).
|
|
225
223
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* `engine/kit/home.py`.
|
|
224
|
+
* Заполняется инструментом чтения при каждом успешном обращении.
|
|
225
|
+
* Инструменты записи и редактирования проверяют этот словарь перед записью,
|
|
226
|
+
* чтобы обнаружить устаревшие правки (stale write detection).
|
|
230
227
|
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* и до 2026-09-10 движок искался в папке с таким именем: у человека он лежал
|
|
234
|
-
* в `~/.naparnik`, а расширение отвечало «не установлен — монтировать нечем».
|
|
228
|
+
* Ключ: абсолютный путь к файлу.
|
|
229
|
+
* Значение: Date.now() в момент прочтения.
|
|
235
230
|
*/
|
|
236
|
-
|
|
231
|
+
readFileTimestamps: Record<string, number>;
|
|
237
232
|
/**
|
|
238
|
-
*
|
|
239
|
-
* задана. Наружу нужна затем, что окружение у Движка своё (передано в
|
|
240
|
-
* конструктор), а `process.env` в тестах не тот же самый.
|
|
233
|
+
* Содержимое прочитанных файлов (путь → текст).
|
|
241
234
|
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
235
|
+
* Опциональный словарь для post-compact restore — заполняется инструментами
|
|
236
|
+
* чтения когда featureGates.postCompactRestore=true.
|
|
237
|
+
* Позволяет postCompactRestore() восстановить содержимое без повторного чтения с диска.
|
|
238
|
+
*
|
|
239
|
+
* Ключ: абсолютный путь к файлу (совпадает с readFileTimestamps).
|
|
240
|
+
* Значение: текстовое содержимое на момент последнего чтения.
|
|
245
241
|
*/
|
|
246
|
-
|
|
247
|
-
/** Папка версий движка: `<дом>/engine`. */
|
|
248
|
-
versionsDir(): string;
|
|
242
|
+
readFileContents?: Record<string, string>;
|
|
249
243
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* режима разработчика или прав администратора, а строка в файле переносима и
|
|
254
|
-
* переименовывается атомарно.
|
|
255
|
-
*
|
|
256
|
-
* ⚠ ЗАПОМИНАЕТСЯ НА ВСЮ ЖИЗНЬ ПРОЦЕССА, И ЭТО НЕСУЩЕЕ. Так работает обещание
|
|
257
|
-
* «подмена начинки — только на старте»: обновление меняет файл `current`, а
|
|
258
|
-
* запущенный процесс продолжает считать на той версии, с которой начал.
|
|
259
|
-
* Читай мы файл каждый раз — задача, заведённая старой версией, дочитывалась
|
|
260
|
-
* бы новой. У движка кэш по отпечаткам, и в отпечаток входит код шага: смесь
|
|
261
|
-
* двух версий внутри одной задачи не упала бы, а тихо смешала результаты.
|
|
262
|
-
*
|
|
263
|
-
* `freshVersion()` даёт незакэшированное значение — она нужна ровно двум местам:
|
|
264
|
-
* самому обновлению и рассказу человеку о том, что новая версия уже готова и
|
|
265
|
-
* подхватится после перезапуска.
|
|
244
|
+
* Функция для эмиссии событий прогресса во время выполнения инструмента.
|
|
245
|
+
* Позволяет стримить промежуточные результаты в UI без завершения execute().
|
|
246
|
+
* Отсутствует, если потребитель не поддерживает стриминг прогресса.
|
|
266
247
|
*/
|
|
267
|
-
|
|
268
|
-
/** Что записано в файле `current` прямо сейчас, мимо памяти процесса. */
|
|
269
|
-
freshVersion(): string | null;
|
|
248
|
+
emitProgress?: (content: string) => void;
|
|
270
249
|
/**
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* Порядок: явное указание переменной (лаборатория, тесты, ручная сборка) →
|
|
274
|
-
* версионная раскладка `<дом>/engine/<текущая>`. Третьего варианта нет
|
|
275
|
-
* намеренно: каждый «а ещё поищем вот тут» — это место, где TypeScript и
|
|
276
|
-
* питон однажды разойдутся.
|
|
250
|
+
* Идентификатор агента в мульти-агентных сценариях.
|
|
251
|
+
* Используется для изоляции состояния между агентами и корректного логирования.
|
|
277
252
|
*/
|
|
253
|
+
agentId?: string;
|
|
278
254
|
/**
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* `<дом>/engine/<версия>` и переставит указатель — на который эта машина не
|
|
284
|
-
* смотрит вовсе. Получилось бы обновление, которое честно отчитывается об
|
|
285
|
-
* успехе и не меняет ничего.
|
|
255
|
+
* Программа, которая нас позвала: `clientInfo` из рукопожатия MCP
|
|
256
|
+
* («claude-ai», «codex-mcp-client»). Нужна отчёту о поломке
|
|
257
|
+
* (`crash-report.ts`): одна и та же беда у Claude Desktop и у Codex — это
|
|
258
|
+
* часто две разные беды. Нет — хост не представился.
|
|
286
259
|
*/
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
260
|
+
host?: {
|
|
261
|
+
name: string;
|
|
262
|
+
version: string;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Текстовый блок в результате инструмента.
|
|
267
|
+
* Используется внутри mixed-content для аннотаций к изображениям.
|
|
268
|
+
*/
|
|
269
|
+
interface ToolTextBlock {
|
|
270
|
+
type: 'text';
|
|
271
|
+
text: string;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Блок изображения в результате инструмента (vision support).
|
|
275
|
+
*
|
|
276
|
+
* Tool возвращает картинку «по-человечески»: base64 + mediaType.
|
|
277
|
+
* Конвертация в нативный формат API провайдера (Anthropic source/Google inlineData/
|
|
278
|
+
* OpenAI image_url) — обязанность нижележащего слоя (tool-dispatch + LLM provider).
|
|
279
|
+
*
|
|
280
|
+
* Используется для:
|
|
281
|
+
* - чтения сканированных документов (vision fallback при отказе OCR)
|
|
282
|
+
* - анализа скриншотов
|
|
283
|
+
* - обработки фото с телефона пользователя
|
|
284
|
+
*/
|
|
285
|
+
interface ToolImageBlock {
|
|
286
|
+
type: 'image';
|
|
287
|
+
/** Base64-кодированные данные изображения (без префикса data:...) */
|
|
288
|
+
base64: string;
|
|
289
|
+
/** MIME-тип — модели нужен корректный формат для декодирования */
|
|
290
|
+
mediaType: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Блок-ссылка на изображение в vision-cache (disk storage).
|
|
294
|
+
*
|
|
295
|
+
* Альтернатива ToolImageBlock когда vision-cache сконфигурирован
|
|
296
|
+
* (`ctx.visionCache` есть). Tool пишет raw bytes в cache и возвращает
|
|
297
|
+
* file_ref вместо inline base64. Это снимает нагрузку с RAM/БД/PHP:
|
|
298
|
+
* tool_result в messages.content становится ~200 байт вместо ~500 KB.
|
|
299
|
+
*
|
|
300
|
+
* Раскрытие в base64 происходит непосредственно перед `provider.send()`
|
|
301
|
+
* через `expandVisionRefs` в `super-agent-core/src/loop/query.ts`.
|
|
302
|
+
*
|
|
303
|
+
* @see super-agent-core/src/loop/vision-cache.ts
|
|
304
|
+
*/
|
|
305
|
+
interface ToolImageRefBlock {
|
|
306
|
+
type: 'image_ref';
|
|
307
|
+
/** Относительный путь от rootPath: `<sessionId>/<sha256>.<ext>` */
|
|
308
|
+
pathSuffix: string;
|
|
309
|
+
mediaType: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
310
|
+
}
|
|
311
|
+
/** Объединённый тип контентного блока в результате инструмента */
|
|
312
|
+
type ToolContentBlock = ToolTextBlock | ToolImageBlock | ToolImageRefBlock;
|
|
313
|
+
/**
|
|
314
|
+
* Успешный результат инструмента.
|
|
315
|
+
*
|
|
316
|
+
* Может быть простой строкой (обычный случай) или массивом блоков text+image
|
|
317
|
+
* для мультимодальных результатов (vision). Image-блоки требуются чтобы
|
|
318
|
+
* модель могла «видеть» картинку, а не получать строку base64 в text-блоке.
|
|
319
|
+
*/
|
|
320
|
+
interface ToolResultSuccess {
|
|
321
|
+
type: 'success';
|
|
322
|
+
content: string | ToolContentBlock[];
|
|
292
323
|
/**
|
|
293
|
-
*
|
|
324
|
+
* Данные для ВИДЖЕТА, мимо контекста модели.
|
|
294
325
|
*
|
|
295
|
-
* ⚠
|
|
296
|
-
*
|
|
297
|
-
*
|
|
326
|
+
* ⚠ РАЗДЕЛЕНИЕ НЕ РАДИ ПОРЯДКА, А РАДИ ДЕНЕГ И ТОЛКУ. Настоящий кадр ролика
|
|
327
|
+
* в base64 весит восемь килобайт; три кадра — двадцать четыре, и модель
|
|
328
|
+
* перечитывает их каждый ход, ничего в них не видя: base64 в тексте она
|
|
329
|
+
* «посмотреть» не может. Картинки нужны ГЛАЗАМ человека, то есть форме, а
|
|
330
|
+
* модели хватает названий словами. Уезжает как `structuredContent` в ответе
|
|
331
|
+
* `tools/call` — так это и разведено в спеке MCP.
|
|
298
332
|
*/
|
|
299
|
-
|
|
333
|
+
structured?: Record<string, unknown>;
|
|
334
|
+
}
|
|
335
|
+
/** Результат с ошибкой без машиночитаемого кода */
|
|
336
|
+
interface ToolResultErrorBase {
|
|
337
|
+
type: 'error';
|
|
338
|
+
error: string;
|
|
339
|
+
}
|
|
340
|
+
/** Результат с ошибкой с машиночитаемым кодом */
|
|
341
|
+
interface ToolResultErrorWithCode {
|
|
342
|
+
type: 'error';
|
|
343
|
+
error: string;
|
|
344
|
+
/** Код ошибки для программной обработки (retry logic, UI, логирование) */
|
|
345
|
+
code: string;
|
|
346
|
+
}
|
|
347
|
+
/** Результат с ошибкой — с кодом или без */
|
|
348
|
+
type ToolResultError = ToolResultErrorBase | ToolResultErrorWithCode;
|
|
349
|
+
/** Финальный результат выполнения инструмента */
|
|
350
|
+
type ToolResult = ToolResultSuccess | ToolResultError;
|
|
351
|
+
/**
|
|
352
|
+
* Категория инструмента — для фильтрации, UI-группировки и политик доступа.
|
|
353
|
+
* Аналог group:* из конфигурации OpenClaw.
|
|
354
|
+
*/
|
|
355
|
+
type ToolGroup = 'fs' | 'runtime' | 'web' | 'memory' | 'knowledge' | 'ui' | 'system' | 'automation' | 'confirmation' | 'collaboration' | 'integration' | 'skill';
|
|
356
|
+
/**
|
|
357
|
+
* Универсальный интерфейс инструмента агента.
|
|
358
|
+
*
|
|
359
|
+
* Инструменты регистрируются в агентском цикле и вызываются моделью
|
|
360
|
+
* по имени через механизм tool_use. Каждый инструмент описывает себя
|
|
361
|
+
* через JSON Schema и реализует функцию execute.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```typescript
|
|
365
|
+
* const readFileTool: Tool = {
|
|
366
|
+
* name: 'read_file',
|
|
367
|
+
* description: 'Читает содержимое файла по указанному пути',
|
|
368
|
+
* inputSchema: {
|
|
369
|
+
* type: 'object',
|
|
370
|
+
* properties: {
|
|
371
|
+
* path: { type: 'string', description: 'Путь к файлу' }
|
|
372
|
+
* },
|
|
373
|
+
* required: ['path']
|
|
374
|
+
* },
|
|
375
|
+
* readOnly: true,
|
|
376
|
+
* group: 'fs',
|
|
377
|
+
* async execute(input, ctx) {
|
|
378
|
+
* // ...
|
|
379
|
+
* }
|
|
380
|
+
* }
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
interface Tool<TInput = any> {
|
|
384
|
+
/** Уникальное имя инструмента (snake_case) */
|
|
385
|
+
name: string;
|
|
386
|
+
/** Человекочитаемое описание для модели */
|
|
387
|
+
description: string;
|
|
300
388
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* старте), и сразу после доставки следующий же вызов получил бы
|
|
306
|
-
* закэшированное «версии нет» про версию, которую мы сами и разложили.
|
|
307
|
-
* Здесь это не нарушает обещания: смешивать две версии внутри задачи нечем,
|
|
308
|
-
* до этого момента версии не было вовсе.
|
|
389
|
+
* Название по-русски — для ЧЕЛОВЕКА, в отличие от `description` для модели.
|
|
390
|
+
* Уезжает хосту как `annotations.title` и показывается в настройках вместо
|
|
391
|
+
* имени из кода: без него человек читает «Choose variant», решая, разрешать
|
|
392
|
+
* ли инструмент.
|
|
309
393
|
*/
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
/** Python окружения пака — им считается монтаж. Может ещё не существовать. */
|
|
314
|
-
packPython(): string | null;
|
|
394
|
+
title?: string;
|
|
395
|
+
/** JSON Schema входных параметров */
|
|
396
|
+
inputSchema: ToolInputSchema;
|
|
315
397
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* здесь, и получится: установка принесла питон, окружение на нём собралось,
|
|
321
|
-
* а сервер MCP по-прежнему зовёт системный «python», которого нет или
|
|
322
|
-
* который заглушка из Microsoft Store.
|
|
398
|
+
* Флаг «только чтение».
|
|
399
|
+
* readOnly-инструменты не изменяют внешнее состояние и могут выполняться
|
|
400
|
+
* параллельно с другими readOnly-инструментами без риска конфликтов.
|
|
401
|
+
* Агентский цикл использует этот флаг для параллельного выполнения.
|
|
323
402
|
*/
|
|
324
|
-
|
|
403
|
+
readOnly?: boolean;
|
|
325
404
|
/**
|
|
326
|
-
*
|
|
405
|
+
* Переписывает то, что у человека уже есть.
|
|
327
406
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
407
|
+
* По умолчанию `false`, и это не оптимизм: монтаж кладёт НОВЫЙ файл рядом с
|
|
408
|
+
* исходником, обновление держит прежнюю версию на диске, установка кладёт
|
|
409
|
+
* недостающее в свою папку — ничего из этого не затирает чужого. `true`
|
|
410
|
+
* ставится там, где вызов переписывает содержимое, которое человек считал
|
|
411
|
+
* своим: например, готовый фильтр цвета в его паке стиля.
|
|
332
412
|
*/
|
|
333
|
-
|
|
334
|
-
/** Python подготовки кандидата — намеренно без fallback на venv старого пака. */
|
|
335
|
-
bootstrapPython(): string;
|
|
336
|
-
/** Python ровно названного кандидата, а не текущей версии. */
|
|
337
|
-
candidatePython(place: EnginePlace): string | null;
|
|
413
|
+
destructive?: boolean;
|
|
338
414
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
* на английской), а Node декодирует ребёнка как UTF-8 — и весь ответ
|
|
344
|
-
* приезжал нечитаемым: «????? ???: /??? ??????.mp4». Отдельно ломался
|
|
345
|
-
* preview: строка JSON у нас с русскими ключами, и поле `файл` выходило
|
|
346
|
-
* undefined. Свой код мы чиним вызовом (`engine/kit/encoding.py`), а чужие
|
|
347
|
-
* библиотеки внутри того же процесса — только средой. Нужны оба заслона.
|
|
415
|
+
* Виджет инструмента (MCP Apps): адрес `ui://…` ресурса, HTML которого хост
|
|
416
|
+
* рисует в ленте вместо текстового ответа. Ресурс регистрируется на
|
|
417
|
+
* сервере отдельно (`McpServer.addResource`); здесь только ссылка, и в
|
|
418
|
+
* `tools/list` она уезжает как `_meta.ui.resourceUri`.
|
|
348
419
|
*/
|
|
349
|
-
|
|
420
|
+
ui?: {
|
|
421
|
+
resourceUri: string;
|
|
422
|
+
};
|
|
350
423
|
/**
|
|
351
|
-
*
|
|
424
|
+
* Флаг «обратимое действие» — действие остаётся в рамках текущего диалога
|
|
425
|
+
* с этим пользователем и не выходит наружу.
|
|
352
426
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* недостижим по устройству: он сам запускается python'ом.
|
|
427
|
+
* `true` (по умолчанию) — безопасно вызывать без явного человеческого approve:
|
|
428
|
+
* recall, web_search, view, create_file (в workspace), send_file_to_chat,
|
|
429
|
+
* send_task_to_agent (внутреннему агенту), bash_tool (sandbox-изолирован),
|
|
430
|
+
* integration_call с read-методами.
|
|
358
431
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
|
|
363
|
-
pythonTrouble(): Promise<string | null>;
|
|
364
|
-
/**
|
|
365
|
-
* Запустить команду движка и отдать её текст как есть.
|
|
432
|
+
* `false` — действие выходит наружу и должно сопровождаться явной формулировкой
|
|
433
|
+
* подтверждения от пользователя (через диалог) перед выполнением:
|
|
434
|
+
* отправка наружу (письмо/SMS внешнему адресату), запись в CRM/ERP,
|
|
435
|
+
* финансовые операции, удаление persistent данных.
|
|
366
436
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* означает, что внутренний недостижим: пробник убивался снаружи, и его
|
|
370
|
-
* объяснение не доезжало до человека вовсе.
|
|
437
|
+
* Используется для генерации списков в system prompt и (в будущем)
|
|
438
|
+
* для автоматического enforcement в loop.
|
|
371
439
|
*/
|
|
372
|
-
|
|
373
|
-
maxBuffer?: number;
|
|
374
|
-
timeout?: number;
|
|
375
|
-
key?: string;
|
|
376
|
-
}): Promise<CommandOutcome>;
|
|
440
|
+
reversible?: boolean;
|
|
377
441
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
442
|
+
* Группа инструмента — для фильтрации, UI-группировки и политик доступа.
|
|
443
|
+
* Соответствует group:* из конфигурации OpenClaw sandbox.
|
|
380
444
|
*/
|
|
381
|
-
|
|
382
|
-
maxBuffer?: number;
|
|
383
|
-
timeout?: number;
|
|
384
|
-
key?: string;
|
|
385
|
-
}): Promise<{
|
|
386
|
-
stdout: string;
|
|
387
|
-
stderr: string;
|
|
388
|
-
}>;
|
|
389
|
-
/** Запуск через явно выбранный Python нужен проверке кандидата до current. */
|
|
390
|
-
runWith(executable: string, place: EnginePlace, args: string[], { maxBuffer, timeout, key, }?: {
|
|
391
|
-
maxBuffer?: number;
|
|
392
|
-
timeout?: number;
|
|
393
|
-
key?: string;
|
|
394
|
-
}): Promise<{
|
|
395
|
-
stdout: string;
|
|
396
|
-
stderr: string;
|
|
397
|
-
}>;
|
|
398
|
-
/** Одна строка вывода команды — например, номер заведённого задания. */
|
|
399
|
-
asString(place: EnginePlace, args: string[], key?: string): Promise<string>;
|
|
445
|
+
group?: ToolGroup;
|
|
400
446
|
/**
|
|
401
|
-
*
|
|
402
|
-
* работающей программе не меняется, а спрашивать его подпроцессом на каждый
|
|
403
|
-
* вызов инструмента — лишние полсекунды на ровном месте.
|
|
447
|
+
* Флаг «отложенная загрузка схемы» (lazy tool loading).
|
|
404
448
|
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
449
|
+
* Если `true` — JSON Schema этого tool'а НЕ передаётся в LLM API в каждом
|
|
450
|
+
* запросе. Модель видит только имя + 1-строчное описание в специальном
|
|
451
|
+
* блоке `<deferred_tools>` системного промпта и подгружает полную схему
|
|
452
|
+
* через ToolSearch tool когда нужно её вызвать.
|
|
453
|
+
*
|
|
454
|
+
* Используется для редких/тяжёлых tools чтобы экономить токены input'а:
|
|
455
|
+
* - integration_call (динамический, описание ~жирное)
|
|
456
|
+
* - browse_page (тяжёлый prompt)
|
|
457
|
+
* - observer_*, credential_*, skill_* (admin-flow, редко)
|
|
458
|
+
*
|
|
459
|
+
* НЕ deferr'им: bash, view, read, write, edit, grep, glob, web_*,
|
|
460
|
+
* todo_write, Task, ToolSearch, memory tools — это core.
|
|
461
|
+
*
|
|
462
|
+
* После вызова ToolSearch с `select:<name>` или релевантного keyword-search
|
|
463
|
+
* — схема активируется на оставшийся срок жизни сессии (обычный tools[] payload).
|
|
412
464
|
*/
|
|
413
|
-
|
|
465
|
+
isDeferred?: boolean;
|
|
414
466
|
/**
|
|
415
|
-
*
|
|
467
|
+
* Максимальный размер `tool_result.content` в символах ДО persistence-cap.
|
|
416
468
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
469
|
+
* Семантика 1-в-1 как у Claude Code 2.1.88 (`Tool.ts:466`):
|
|
470
|
+
* - Эффективный порог = `min(maxResultSizeChars, DEFAULT_MAX_RESULT_SIZE_CHARS=50_000)`.
|
|
471
|
+
* Tool может объявить **БОЛЬШЕ** 50K — глобальный cap всё равно применит 50K.
|
|
472
|
+
* Tool может объявить **МЕНЬШЕ** 50K (например Grep 20K) — будет применён tool-specific лимит.
|
|
473
|
+
* - `Infinity` = hard opt-out, никогда не persist (для FileRead/view —
|
|
474
|
+
* нет смысла записывать файл во второй файл).
|
|
475
|
+
* - `undefined` = поведение как 100_000 (clamp до 50K).
|
|
421
476
|
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
477
|
+
* Применяется в `maybePersistLargeToolResult` (слой A) сразу после
|
|
478
|
+
* выполнения tool'а в `tool-dispatch.ts`, ДО того как tool_result попадёт
|
|
479
|
+
* в messages history. Большие результаты пишутся на диск
|
|
480
|
+
* `<rootPath>/<sessionId>/<tool_use_id>.{json,txt}`, в messages летит
|
|
481
|
+
* preview ~2000 символов внутри `<persisted-output>` тегов.
|
|
482
|
+
*
|
|
483
|
+
* Подробности — `new-version/docs/claude-code-optimizations.md` (Слой A).
|
|
426
484
|
*/
|
|
427
|
-
|
|
428
|
-
place: EnginePlace;
|
|
429
|
-
executable: string;
|
|
430
|
-
args: (id: string) => string[];
|
|
431
|
-
startJob: string[];
|
|
432
|
-
advice: string;
|
|
433
|
-
/**
|
|
434
|
-
* Ключ подписки — ТОЛЬКО этому запуску, не общей среде питона.
|
|
435
|
-
*
|
|
436
|
-
* ⚠ БЕЗ НЕГО МОНТАЖ ОТКАЗЫВАЕТ КАЖДОМУ ПЛАТЯЩЕМУ. С 2026-09-09 движок
|
|
437
|
-
* спрашивает подписку сам (`client/engine/account/subscription.py`) и берёт ключ
|
|
438
|
-
* из `NAPARNIK_API_KEY`, а `pythonEnv()` его оттуда вычищает намеренно:
|
|
439
|
-
* иначе ключ уезжает в каждого потомка питона — ffmpeg, pip, чужой код
|
|
440
|
-
* пака. Поэтому не «вернуть ключ в среду», а доложить его точечно, ровно
|
|
441
|
-
* как уже сделано у скачивания движка (`run`, поле `key`).
|
|
442
|
-
*/
|
|
443
|
-
key: string | undefined;
|
|
444
|
-
}): Promise<{
|
|
445
|
-
id: string;
|
|
446
|
-
} | {
|
|
447
|
-
refusal: string;
|
|
448
|
-
}>;
|
|
485
|
+
maxResultSizeChars?: number;
|
|
449
486
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* Держим последние `KEEP_LOGS` — полоса загрузки модели пишет сотни
|
|
455
|
-
* килобайт, и вечная папка таких файлов была бы платой человека за то, что
|
|
456
|
-
* он однажды монтировал.
|
|
487
|
+
* Выполняет инструмент с заданными параметрами.
|
|
488
|
+
* @param input - Валидированные входные параметры
|
|
489
|
+
* @param ctx - Контекст выполнения (сессия, сигнал отмены, среда, временные метки файлов)
|
|
490
|
+
* @returns Результат выполнения или ошибка
|
|
457
491
|
*/
|
|
458
|
-
|
|
492
|
+
execute(input: TInput, ctx: ToolContext): Promise<ToolResult>;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* «Не спи»: компьютер не засыпает, пока идёт наша работа, и ещё хвост после.
|
|
497
|
+
*
|
|
498
|
+
* ⚠ ЗАЧЕМ. Установка качает гигабайты, монтаж идёт минутами, и человек уходит
|
|
499
|
+
* от компьютера — ровно тогда, когда тот засыпает от простоя. Проснулся —
|
|
500
|
+
* скачивание оборвано, монтаж стоит. Просьба владельца 2026-09-24: держать
|
|
501
|
+
* компьютер бодрым на ЛЮБОЙ нашей команде, не только на тяжёлой, потому что
|
|
502
|
+
* модель может думать пять минут между вызовами.
|
|
503
|
+
*
|
|
504
|
+
* ⚠ ТЕМ ЖЕ СПОСОБОМ, ЧТО ВИДЕОПЛЕЕР, А НЕ ДВИЖЕНИЕМ МЫШИ. Плеер говорит
|
|
505
|
+
* системе «я занят» её же средством. Двигать мышь на macOS можно только с
|
|
506
|
+
* разрешением «Универсального доступа» — страшное системное окно, которого
|
|
507
|
+
* человек не ждёт, — и антивирусы такое не любят. Наши средства разрешений не
|
|
508
|
+
* просят: `caffeinate` на macOS, `SetThreadExecutionState` на Windows.
|
|
509
|
+
*
|
|
510
|
+
* ⚠ ЛАМП ДВА РОДА, И ЭТО НЕ ДУБЛЬ.
|
|
511
|
+
* - Лампа вызовов — ОДНА на программу. Каждый вызов инструмента держит её, пока
|
|
512
|
+
* идёт, после последнего она горит ещё `TAIL_MS`. Держатели — множество
|
|
513
|
+
* жетонов, а не счётчик запусков: снять один жетон дважды нельзя, поэтому
|
|
514
|
+
* «пятьсот раз не спи, пятьсот раз усни» разойтись не может по построению.
|
|
515
|
+
* Привязана к НАШЕМУ процессу: умерла оболочка — лампа погасла сама.
|
|
516
|
+
* - Лампа фоновой работы (монтаж, установка) — своя у каждой и привязана к
|
|
517
|
+
* процессу САМОЙ РАБОТЫ. Ревью плана 2026-09-24: работа переживает закрытие
|
|
518
|
+
* Claude Desktop (`engine.ts`, `background`), а лампа, привязанная к нам,
|
|
519
|
+
* умерла бы вместе с нами — и компьютер уснул бы посреди монтажа ровно в том
|
|
520
|
+
* случае, ради которого всё затевалось. Гасить её не нужно: система гасит
|
|
521
|
+
* её сама, когда процесс работы кончился.
|
|
522
|
+
*
|
|
523
|
+
* ⚠ ЗАКРЫТУЮ КРЫШКУ НОУТБУКА ЭТО НЕ ДЕРЖИТ. Обойти её можно только командой с
|
|
524
|
+
* паролем администратора, и так мы не делаем. Поэтому человеку говорится
|
|
525
|
+
* словами — `AWAY_NOTE` в ответе на запуск работы.
|
|
526
|
+
*/
|
|
527
|
+
|
|
528
|
+
/** Горящая лампа: погасить руками или узнать, что она погасла сама. */
|
|
529
|
+
interface Lamp {
|
|
530
|
+
stop(): void;
|
|
531
|
+
onExit(cb: () => void): void;
|
|
532
|
+
}
|
|
533
|
+
/** Зажечь лампу, которая горит, пока жив процесс `pid`. `null` — система не умеет. */
|
|
534
|
+
type Blocker = (pid: number) => Lamp | null;
|
|
535
|
+
/** Время — параметром, ради теста: ожидания по таймеру в тестах запрещены. */
|
|
536
|
+
interface Clock {
|
|
537
|
+
now(): number;
|
|
538
|
+
/** Позвать `fire` через `ms`; вернуть отмену. */
|
|
539
|
+
later(ms: number, fire: () => void): () => void;
|
|
540
|
+
}
|
|
541
|
+
declare class Wakefulness {
|
|
542
|
+
private readonly blocker;
|
|
543
|
+
private readonly holders;
|
|
544
|
+
private until;
|
|
545
|
+
private lamp;
|
|
459
546
|
/**
|
|
460
|
-
*
|
|
547
|
+
* Система однажды лампу не дала — больше не просим. Иначе каждый вызов
|
|
548
|
+
* пытался бы заново запустить то, чего нет, а работа человека важнее.
|
|
549
|
+
*/
|
|
550
|
+
private refused;
|
|
551
|
+
private cancelTail;
|
|
552
|
+
private readonly clock;
|
|
553
|
+
private readonly pid;
|
|
554
|
+
constructor(blocker: Blocker, opts?: {
|
|
555
|
+
clock?: Clock;
|
|
556
|
+
pid?: number;
|
|
557
|
+
tailMs?: number;
|
|
558
|
+
});
|
|
559
|
+
private readonly tailMs;
|
|
560
|
+
/** Держать компьютер бодрым; вернуть снятие. Снять второй раз — ничего не делает. */
|
|
561
|
+
hold(): () => void;
|
|
562
|
+
/**
|
|
563
|
+
* Держать, пока идёт работа, — и когда она кончилась успехом, и когда упала.
|
|
461
564
|
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
565
|
+
* ⚠ Работа — функцией, а не готовым обещанием. Обещание уже запущено: тело
|
|
566
|
+
* async-функции до первого await идёт при вычислении аргумента, и начало
|
|
567
|
+
* работы оставалось без лампы (тест обёртки поймал ровно это).
|
|
464
568
|
*/
|
|
465
|
-
|
|
569
|
+
during<T>(work: () => Promise<T>): Promise<T>;
|
|
570
|
+
/**
|
|
571
|
+
* Своя лампа для фоновой работы, привязанная к её процессу. Гаснет сама, когда
|
|
572
|
+
* работа кончилась, — даже если нас к тому времени уже закрыли.
|
|
573
|
+
*/
|
|
574
|
+
follow(pid: number): void;
|
|
575
|
+
private sync;
|
|
576
|
+
private light;
|
|
466
577
|
}
|
|
578
|
+
|
|
467
579
|
/**
|
|
468
|
-
*
|
|
469
|
-
* папки, а не от чужой.
|
|
580
|
+
* Где внутри распакованной портативной сборки лежит сам интерпретатор.
|
|
470
581
|
*
|
|
471
|
-
*
|
|
472
|
-
* `
|
|
473
|
-
* `
|
|
474
|
-
*
|
|
582
|
+
* ⚠ ТРИ ВАРИАНТА, А НЕ ДВА, И РОВНО ТЕ ЖЕ, ЧТО У ДВИЖКА. Источник правды —
|
|
583
|
+
* `engine/shell-contract.json` → `portable_python.inside`: оттуда их берёт и
|
|
584
|
+
* питон (`engine/setup/python_finder.py`). Копия здесь неизбежна — питон мы
|
|
585
|
+
* ищем ДО того, как есть чем запустить питон, — но прошлая копия знала два
|
|
586
|
+
* варианта из трёх: на сборке, где внутри лежит только `bin/python`, движок
|
|
587
|
+
* питон видел, а сервер MCP нет. Правится сначала JSON, потом сюда; расхождение
|
|
588
|
+
* ловит тест границы (`engine.test.ts`) сверкой с этим JSON.
|
|
475
589
|
*/
|
|
476
|
-
declare
|
|
590
|
+
declare const INSIDE_PYTHON: string[];
|
|
591
|
+
/** Дом по умолчанию — ровно тот же литерал, что в `engine/kit/home.py`. */
|
|
592
|
+
declare const DEFAULT_HOME = "~/.naparnik";
|
|
477
593
|
/**
|
|
478
|
-
*
|
|
594
|
+
* Шаги установки, которые идут минутами и часами: их нельзя держать внутри
|
|
595
|
+
* вызова инструмента. Секундами считается только `check`.
|
|
479
596
|
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
597
|
+
* ⚠ «python» здесь вместе с портативной сборкой: шаг, который раньше лишь
|
|
598
|
+
* советовал сходить на python.org, теперь может качать от 24 до 104 МБ.
|
|
599
|
+
* Оставь его быстрым — и на медленной связи клиентская программа оборвала бы
|
|
600
|
+
* вызов по своему таймауту, а скачивание осталось бы сиротой; повтор начал бы
|
|
601
|
+
* второе.
|
|
483
602
|
*/
|
|
484
|
-
declare
|
|
485
|
-
path: string;
|
|
486
|
-
} | {
|
|
487
|
-
refusal: string;
|
|
488
|
-
};
|
|
603
|
+
declare const LONG_STEPS: Set<string>;
|
|
489
604
|
/**
|
|
490
|
-
*
|
|
605
|
+
* Все шаги установки, которые понимает движок.
|
|
491
606
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
607
|
+
* ⚠ ЭТО ЗЕРКАЛО `STEPS` движка, и оно уже один раз разошлось: движок печатал
|
|
608
|
+
* «Шаг установки „вырез“», а здешний список о таком шаге не знал — клиент
|
|
609
|
+
* оказывался в тупике, потому что инструмент не принимал значение, которое
|
|
610
|
+
* движок сам же и назвал. Источник правды — `engine/shell-contract.json` →
|
|
611
|
+
* `install_steps`: оттуда `STEPS` берёт и `setup/install.py`. Правится сверху
|
|
612
|
+
* вниз: сначала JSON, потом сюда. Расхождение сторожит `engine.test.ts` сверкой
|
|
613
|
+
* с этим JSON.
|
|
497
614
|
*/
|
|
498
|
-
declare
|
|
499
|
-
/** Последняя непустая строка вывода — там, где команда печатает результат. */
|
|
500
|
-
declare function lastLine(output: string): string;
|
|
501
|
-
|
|
502
|
-
/** Определение ресурса в MCP */
|
|
503
|
-
interface McpResource {
|
|
504
|
-
/** URI ресурса (уникальный идентификатор) */
|
|
505
|
-
uri: string;
|
|
506
|
-
/** Человекочитаемое имя */
|
|
507
|
-
name: string;
|
|
508
|
-
/** Описание ресурса (опционально) */
|
|
509
|
-
description?: string;
|
|
510
|
-
/** MIME-тип содержимого (опционально) */
|
|
511
|
-
mimeType?: string;
|
|
512
|
-
}
|
|
513
|
-
/** Текстовое содержимое ресурса */
|
|
514
|
-
interface McpResourceTextContent {
|
|
515
|
-
uri: string;
|
|
516
|
-
mimeType?: string;
|
|
517
|
-
text: string;
|
|
518
|
-
/** Метаданные расширений: у виджета MCP Apps здесь `ui.csp`, `ui.prefersBorder` */
|
|
519
|
-
_meta?: Record<string, unknown>;
|
|
520
|
-
}
|
|
521
|
-
/** Бинарное содержимое ресурса */
|
|
522
|
-
interface McpResourceBlobContent {
|
|
523
|
-
uri: string;
|
|
524
|
-
mimeType?: string;
|
|
525
|
-
/** Base64-кодированные данные */
|
|
526
|
-
blob: string;
|
|
527
|
-
}
|
|
528
|
-
/** Содержимое ресурса — текст или бинарные данные */
|
|
529
|
-
type McpResourceItemContent = McpResourceTextContent | McpResourceBlobContent;
|
|
530
|
-
|
|
615
|
+
declare const INSTALL_STEPS: string[];
|
|
531
616
|
/**
|
|
532
|
-
*
|
|
617
|
+
* Паспорт пака: имена этапов, список моделей, чем запускать.
|
|
533
618
|
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
* - emitProgress в ToolContext — стриминг прогресса без возврата из execute
|
|
538
|
-
* - agentId в ToolContext — для мульти-агентных сценариев
|
|
619
|
+
* Спрашивается у движка ОДНОЙ командой, а не переписывается сюда. Пока его не
|
|
620
|
+
* было, списки этапов и моделей жили здесь второй правдой, а точка входа была
|
|
621
|
+
* прибита строкой "scripts/edit.py".
|
|
539
622
|
*/
|
|
540
623
|
/**
|
|
541
|
-
*
|
|
542
|
-
*
|
|
624
|
+
* ⚠ ИМЕНА ПОЛЕЙ — ОТВЕТ ПИТОНА, А НЕ НАШ ОБЪЕКТ. Паспорт печатает движок
|
|
625
|
+
* монтажа (`engine/passport.py` → `as_json`, команда `pack-description`), и
|
|
626
|
+
* имена полей — его. Переименование здесь ничего не меняет на той стороне:
|
|
627
|
+
* оболочка просто перестала бы находить поля и объявила бы «паспорт не
|
|
628
|
+
* прочитан» на каждом запуске. Поля, несущие имена, сверяет с настоящими
|
|
629
|
+
* `pack.json` и рецептом `tool-declaration.test.ts`.
|
|
543
630
|
*/
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
/** Минимальная длина строки. */
|
|
551
|
-
minLength?: number;
|
|
552
|
-
} | {
|
|
553
|
-
type: 'number';
|
|
554
|
-
description?: string;
|
|
555
|
-
} | {
|
|
556
|
-
type: 'integer';
|
|
557
|
-
description?: string;
|
|
558
|
-
} | {
|
|
559
|
-
type: 'boolean';
|
|
560
|
-
description?: string;
|
|
561
|
-
} | {
|
|
562
|
-
type: 'array';
|
|
563
|
-
items: JSONSchemaType;
|
|
564
|
-
description?: string;
|
|
565
|
-
} | {
|
|
566
|
-
type: 'object';
|
|
567
|
-
properties: Record<string, JSONSchemaType>;
|
|
568
|
-
required?: string[];
|
|
569
|
-
description?: string;
|
|
631
|
+
interface PackPassport {
|
|
632
|
+
'name': string;
|
|
633
|
+
'title': string;
|
|
634
|
+
'entry': string;
|
|
635
|
+
'stages': string[];
|
|
636
|
+
'models': string[];
|
|
570
637
|
/**
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
* При использовании совмещать с `properties: {}` для соответствия общему
|
|
575
|
-
* стилю кодовой базы.
|
|
638
|
+
* Движок распознавания → модель по умолчанию. Карта, а не одно имя: на
|
|
639
|
+
* видеокарте Apple берётся medium, на процессоре turbo — разница в скорости
|
|
640
|
+
* десятикратная, замерено.
|
|
576
641
|
*/
|
|
577
|
-
|
|
578
|
-
};
|
|
579
|
-
/** Схема входных параметров инструмента (JSON Schema объект верхнего уровня) */
|
|
580
|
-
interface ToolInputSchema {
|
|
581
|
-
type: 'object';
|
|
582
|
-
properties: Record<string, JSONSchemaType>;
|
|
583
|
-
required?: string[];
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Контекст, передаваемый в каждый вызов инструмента.
|
|
587
|
-
*
|
|
588
|
-
* readFileTimestamps — критически важен для защиты от устаревших записей:
|
|
589
|
-
* перед перезаписью файла инструменты write/edit проверяют, что файл был
|
|
590
|
-
* прочитан в текущей сессии и не изменился с момента чтения.
|
|
591
|
-
*/
|
|
592
|
-
interface ToolContext {
|
|
593
|
-
/** Уникальный идентификатор текущей сессии */
|
|
594
|
-
sessionId: string;
|
|
595
|
-
/** Сигнал отмены — инструменты должны проверять его при длительных операциях */
|
|
596
|
-
abortSignal?: AbortSignal;
|
|
597
|
-
/** Среда выполнения — влияет на доступные операции */
|
|
598
|
-
env: 'electron' | 'server' | 'test';
|
|
599
|
-
/** Рабочая директория для файловых операций */
|
|
600
|
-
workingDirectory?: string;
|
|
642
|
+
'default_model': Record<string, string>;
|
|
601
643
|
/**
|
|
602
|
-
*
|
|
644
|
+
* Что монтаж умеет СВЕРХ обязательного пути: имя ключа → вид и пояснение.
|
|
603
645
|
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
646
|
+
* Объявляется паком, а не здесь: набор зависит от того, что пак умеет, и
|
|
647
|
+
* оболочка знать его заранее не может. Пока этого не было, шесть ключей
|
|
648
|
+
* (фон, свет, экран, слои, музыка, без акцентов) существовали только для
|
|
649
|
+
* того, кто печатает в терминале, — через инструмент их попросить было нечем.
|
|
650
|
+
* Старый пак поля не отдаёт: тогда схема остаётся с обязательными ключами.
|
|
651
|
+
*/
|
|
652
|
+
'capabilities'?: Record<string, {
|
|
653
|
+
'kind': string;
|
|
654
|
+
'about': string;
|
|
655
|
+
}>;
|
|
656
|
+
}
|
|
657
|
+
/** Ответ команды движка в виде, пригодном для инструмента. */
|
|
658
|
+
type CommandOutcome = {
|
|
659
|
+
ok: true;
|
|
660
|
+
text: string;
|
|
661
|
+
} | {
|
|
662
|
+
ok: false;
|
|
663
|
+
text: string;
|
|
664
|
+
};
|
|
665
|
+
/** Где лежит установленная сборка движка. */
|
|
666
|
+
interface EnginePlace {
|
|
667
|
+
/** Папка с `console.py`. */
|
|
668
|
+
engine: string;
|
|
669
|
+
/** Папка пака монтажа: `.venv`, `pack.json`, точка входа. */
|
|
670
|
+
pack: string;
|
|
671
|
+
/** Полный путь к `console.py`. */
|
|
672
|
+
consolePy: string;
|
|
673
|
+
}
|
|
674
|
+
declare class Engine {
|
|
675
|
+
private readonly env;
|
|
676
|
+
/**
|
|
677
|
+
* «Не спи» для фоновой работы (`keep-awake.ts`). По умолчанию выключено:
|
|
678
|
+
* тесты и одноразовые `new Engine()` для отката и паспорта систему не
|
|
679
|
+
* трогают. Движок, который качает и монтирует, заводит `wakefulEngine()`
|
|
680
|
+
* в `cli.ts` — и сервер, и установщик Codex.
|
|
681
|
+
*/
|
|
682
|
+
private readonly awake;
|
|
683
|
+
private passportCache;
|
|
684
|
+
private versionAtStart;
|
|
685
|
+
private pythonChecked;
|
|
686
|
+
constructor(env?: NodeJS.ProcessEnv,
|
|
687
|
+
/**
|
|
688
|
+
* «Не спи» для фоновой работы (`keep-awake.ts`). По умолчанию выключено:
|
|
689
|
+
* тесты и одноразовые `new Engine()` для отката и паспорта систему не
|
|
690
|
+
* трогают. Движок, который качает и монтирует, заводит `wakefulEngine()`
|
|
691
|
+
* в `cli.ts` — и сервер, и установщик Codex.
|
|
692
|
+
*/
|
|
693
|
+
awake?: Wakefulness);
|
|
694
|
+
/**
|
|
695
|
+
* Корень Напарника на машине клиента.
|
|
607
696
|
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
697
|
+
* ⚠ ПУСТАЯ СТРОКА — ЭТО «НЕ ЗАДАНО», А НЕ «ТЕКУЩАЯ ПАПКА». Прочитай мы её
|
|
698
|
+
* как путь — дом уехал бы в рабочий каталог процесса, и питон с TypeScript
|
|
699
|
+
* разошлись бы в том, где вообще живут задачи и стиль. Тот же `or` стоит в
|
|
700
|
+
* `engine/kit/home.py`.
|
|
701
|
+
*
|
|
702
|
+
* ⚠ И ШАБЛОН — ТОЖЕ «НЕ ЗАДАНО», см. `fromHost` в `config.ts`. Пустое поле
|
|
703
|
+
* настроек приезжает от Claude Desktop строкой `${user_config.engine_home}`,
|
|
704
|
+
* и до 2026-09-10 движок искался в папке с таким именем: у человека он лежал
|
|
705
|
+
* в `~/.naparnik`, а расширение отвечало «не установлен — монтировать нечем».
|
|
610
706
|
*/
|
|
611
|
-
|
|
707
|
+
home(): string;
|
|
612
708
|
/**
|
|
613
|
-
*
|
|
709
|
+
* Значение переменной окружения — или undefined, если она пуста либо не
|
|
710
|
+
* задана. Наружу нужна затем, что окружение у Движка своё (передано в
|
|
711
|
+
* конструктор), а `process.env` в тестах не тот же самый.
|
|
614
712
|
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
713
|
+
* ⚠ ПУСТАЯ СТРОКА И ШАБЛОН СЧИТАЮТСЯ «НЕ ЗАДАНО» — по той же причине, что
|
|
714
|
+
* и у дома: незаполненное поле настроек приезжает от Claude Desktop либо
|
|
715
|
+
* пустым, либо строкой `${user_config.…}` (`fromHost` в `config.ts`).
|
|
716
|
+
*/
|
|
717
|
+
variable(name: string): string | undefined;
|
|
718
|
+
/** Папка версий движка: `<дом>/engine`. */
|
|
719
|
+
versionsDir(): string;
|
|
720
|
+
/**
|
|
721
|
+
* Какая версия движка объявлена рабочей.
|
|
618
722
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
723
|
+
* Обычный ТЕКСТОВЫЙ файл, а не симлинк: создание симлинка на Windows требует
|
|
724
|
+
* режима разработчика или прав администратора, а строка в файле переносима и
|
|
725
|
+
* переименовывается атомарно.
|
|
726
|
+
*
|
|
727
|
+
* ⚠ ЗАПОМИНАЕТСЯ НА ВСЮ ЖИЗНЬ ПРОЦЕССА, И ЭТО НЕСУЩЕЕ. Так работает обещание
|
|
728
|
+
* «подмена начинки — только на старте»: обновление меняет файл `current`, а
|
|
729
|
+
* запущенный процесс продолжает считать на той версии, с которой начал.
|
|
730
|
+
* Читай мы файл каждый раз — задача, заведённая старой версией, дочитывалась
|
|
731
|
+
* бы новой. У движка кэш по отпечаткам, и в отпечаток входит код шага: смесь
|
|
732
|
+
* двух версий внутри одной задачи не упала бы, а тихо смешала результаты.
|
|
733
|
+
*
|
|
734
|
+
* `freshVersion()` даёт незакэшированное значение — она нужна ровно двум местам:
|
|
735
|
+
* самому обновлению и рассказу человеку о том, что новая версия уже готова и
|
|
736
|
+
* подхватится после перезапуска.
|
|
621
737
|
*/
|
|
622
|
-
|
|
738
|
+
currentVersion(): string | null;
|
|
739
|
+
/** Что записано в файле `current` прямо сейчас, мимо памяти процесса. */
|
|
740
|
+
freshVersion(): string | null;
|
|
623
741
|
/**
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
742
|
+
* Где установлен движок — или null, если нигде.
|
|
743
|
+
*
|
|
744
|
+
* Порядок: явное указание переменной (лаборатория, тесты, ручная сборка) →
|
|
745
|
+
* версионная раскладка `<дом>/engine/<текущая>`. Третьего варианта нет
|
|
746
|
+
* намеренно: каждый «а ещё поищем вот тут» — это место, где TypeScript и
|
|
747
|
+
* питон однажды разойдутся.
|
|
627
748
|
*/
|
|
628
|
-
emitProgress?: (content: string) => void;
|
|
629
749
|
/**
|
|
630
|
-
*
|
|
631
|
-
*
|
|
750
|
+
* Указан ли движок переменной вручную.
|
|
751
|
+
*
|
|
752
|
+
* ⚠ ЭТО НЕ МЕЛОЧЬ ДЛЯ ОБНОВЛЕНИЯ. Ручной путь ПОБЕЖДАЕТ версионную
|
|
753
|
+
* раскладку, а обновление умеет только её: оно распакует новую версию в
|
|
754
|
+
* `<дом>/engine/<версия>` и переставит указатель — на который эта машина не
|
|
755
|
+
* смотрит вовсе. Получилось бы обновление, которое честно отчитывается об
|
|
756
|
+
* успехе и не меняет ничего.
|
|
632
757
|
*/
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
*/
|
|
639
|
-
interface ToolTextBlock {
|
|
640
|
-
type: 'text';
|
|
641
|
-
text: string;
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Блок изображения в результате инструмента (vision support).
|
|
645
|
-
*
|
|
646
|
-
* Tool возвращает картинку «по-человечески»: base64 + mediaType.
|
|
647
|
-
* Конвертация в нативный формат API провайдера (Anthropic source/Google inlineData/
|
|
648
|
-
* OpenAI image_url) — обязанность нижележащего слоя (tool-dispatch + LLM provider).
|
|
649
|
-
*
|
|
650
|
-
* Используется для:
|
|
651
|
-
* - чтения сканированных документов (vision fallback при отказе OCR)
|
|
652
|
-
* - анализа скриншотов
|
|
653
|
-
* - обработки фото с телефона пользователя
|
|
654
|
-
*/
|
|
655
|
-
interface ToolImageBlock {
|
|
656
|
-
type: 'image';
|
|
657
|
-
/** Base64-кодированные данные изображения (без префикса data:...) */
|
|
658
|
-
base64: string;
|
|
659
|
-
/** MIME-тип — модели нужен корректный формат для декодирования */
|
|
660
|
-
mediaType: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Блок-ссылка на изображение в vision-cache (disk storage).
|
|
664
|
-
*
|
|
665
|
-
* Альтернатива ToolImageBlock когда vision-cache сконфигурирован
|
|
666
|
-
* (`ctx.visionCache` есть). Tool пишет raw bytes в cache и возвращает
|
|
667
|
-
* file_ref вместо inline base64. Это снимает нагрузку с RAM/БД/PHP:
|
|
668
|
-
* tool_result в messages.content становится ~200 байт вместо ~500 KB.
|
|
669
|
-
*
|
|
670
|
-
* Раскрытие в base64 происходит непосредственно перед `provider.send()`
|
|
671
|
-
* через `expandVisionRefs` в `super-agent-core/src/loop/query.ts`.
|
|
672
|
-
*
|
|
673
|
-
* @see super-agent-core/src/loop/vision-cache.ts
|
|
674
|
-
*/
|
|
675
|
-
interface ToolImageRefBlock {
|
|
676
|
-
type: 'image_ref';
|
|
677
|
-
/** Относительный путь от rootPath: `<sessionId>/<sha256>.<ext>` */
|
|
678
|
-
pathSuffix: string;
|
|
679
|
-
mediaType: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
680
|
-
}
|
|
681
|
-
/** Объединённый тип контентного блока в результате инструмента */
|
|
682
|
-
type ToolContentBlock = ToolTextBlock | ToolImageBlock | ToolImageRefBlock;
|
|
683
|
-
/**
|
|
684
|
-
* Успешный результат инструмента.
|
|
685
|
-
*
|
|
686
|
-
* Может быть простой строкой (обычный случай) или массивом блоков text+image
|
|
687
|
-
* для мультимодальных результатов (vision). Image-блоки требуются чтобы
|
|
688
|
-
* модель могла «видеть» картинку, а не получать строку base64 в text-блоке.
|
|
689
|
-
*/
|
|
690
|
-
interface ToolResultSuccess {
|
|
691
|
-
type: 'success';
|
|
692
|
-
content: string | ToolContentBlock[];
|
|
758
|
+
pathSetByHand(): boolean;
|
|
759
|
+
place(): EnginePlace | null;
|
|
760
|
+
/** Явное место кандидата: не читает и не меняет current. */
|
|
761
|
+
placeForVersion(version: string): EnginePlace | null;
|
|
762
|
+
private assemble;
|
|
693
763
|
/**
|
|
694
|
-
*
|
|
764
|
+
* Почему монтировать нечем — словами для нейросети клиента.
|
|
695
765
|
*
|
|
696
|
-
* ⚠
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
* «посмотреть» не может. Картинки нужны ГЛАЗАМ человека, то есть форме, а
|
|
700
|
-
* модели хватает названий словами. Уезжает как `structuredContent` в ответе
|
|
701
|
-
* `tools/call` — так это и разведено в спеке MCP.
|
|
766
|
+
* ⚠ ЭТО НЕ ОШИБКА ИНСТРУМЕНТА, а состояние машины. Отдай мы сюда `spawn
|
|
767
|
+
* python3 ENOENT` или молчание — модель начала бы извиняться и гадать
|
|
768
|
+
* вместо того, чтобы объяснить человеку один недостающий шаг.
|
|
702
769
|
*/
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
/** Результат с ошибкой без машиночитаемого кода */
|
|
706
|
-
interface ToolResultErrorBase {
|
|
707
|
-
type: 'error';
|
|
708
|
-
error: string;
|
|
709
|
-
}
|
|
710
|
-
/** Результат с ошибкой с машиночитаемым кодом */
|
|
711
|
-
interface ToolResultErrorWithCode {
|
|
712
|
-
type: 'error';
|
|
713
|
-
error: string;
|
|
714
|
-
/** Код ошибки для программной обработки (retry logic, UI, логирование) */
|
|
715
|
-
code: string;
|
|
716
|
-
}
|
|
717
|
-
/** Результат с ошибкой — с кодом или без */
|
|
718
|
-
type ToolResultError = ToolResultErrorBase | ToolResultErrorWithCode;
|
|
719
|
-
/** Финальный результат выполнения инструмента */
|
|
720
|
-
type ToolResult = ToolResultSuccess | ToolResultError;
|
|
721
|
-
/**
|
|
722
|
-
* Категория инструмента — для фильтрации, UI-группировки и политик доступа.
|
|
723
|
-
* Аналог group:* из конфигурации OpenClaw.
|
|
724
|
-
*/
|
|
725
|
-
type ToolGroup = 'fs' | 'runtime' | 'web' | 'memory' | 'knowledge' | 'ui' | 'system' | 'automation' | 'confirmation' | 'collaboration' | 'integration' | 'skill';
|
|
726
|
-
/**
|
|
727
|
-
* Универсальный интерфейс инструмента агента.
|
|
728
|
-
*
|
|
729
|
-
* Инструменты регистрируются в агентском цикле и вызываются моделью
|
|
730
|
-
* по имени через механизм tool_use. Каждый инструмент описывает себя
|
|
731
|
-
* через JSON Schema и реализует функцию execute.
|
|
732
|
-
*
|
|
733
|
-
* @example
|
|
734
|
-
* ```typescript
|
|
735
|
-
* const readFileTool: Tool = {
|
|
736
|
-
* name: 'read_file',
|
|
737
|
-
* description: 'Читает содержимое файла по указанному пути',
|
|
738
|
-
* inputSchema: {
|
|
739
|
-
* type: 'object',
|
|
740
|
-
* properties: {
|
|
741
|
-
* path: { type: 'string', description: 'Путь к файлу' }
|
|
742
|
-
* },
|
|
743
|
-
* required: ['path']
|
|
744
|
-
* },
|
|
745
|
-
* readOnly: true,
|
|
746
|
-
* group: 'fs',
|
|
747
|
-
* async execute(input, ctx) {
|
|
748
|
-
* // ...
|
|
749
|
-
* }
|
|
750
|
-
* }
|
|
751
|
-
* ```
|
|
752
|
-
*/
|
|
753
|
-
interface Tool<TInput = any> {
|
|
754
|
-
/** Уникальное имя инструмента (snake_case) */
|
|
755
|
-
name: string;
|
|
756
|
-
/** Человекочитаемое описание для модели */
|
|
757
|
-
description: string;
|
|
770
|
+
whyNoEngine(): string;
|
|
758
771
|
/**
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
772
|
+
* Забыть, где движок, — после того как мы его только что поставили.
|
|
773
|
+
*
|
|
774
|
+
* ⚠ БЕЗ ЭТОГО ПЕРВАЯ УСТАНОВКА НЕ ВИДНА САМОЙ СЕБЕ. `currentVersion`
|
|
775
|
+
* запоминается на всю жизнь процесса нарочно (подмена начинки — только на
|
|
776
|
+
* старте), и сразу после доставки следующий же вызов получил бы
|
|
777
|
+
* закэшированное «версии нет» про версию, которую мы сами и разложили.
|
|
778
|
+
* Здесь это не нарушает обещания: смешивать две версии внутри задачи нечем,
|
|
779
|
+
* до этого момента версии не было вовсе.
|
|
763
780
|
*/
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
|
|
781
|
+
forgetPlace(): void;
|
|
782
|
+
/** Забыть ответ на вопрос «есть ли питон» — после того как мы его принесли. */
|
|
783
|
+
forgetPython(): void;
|
|
784
|
+
/** Python окружения пака — им считается монтаж. Может ещё не существовать. */
|
|
785
|
+
packPython(): string | null;
|
|
767
786
|
/**
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
787
|
+
* Портативный python, который движок доставил сам (шаг «python» установки).
|
|
788
|
+
*
|
|
789
|
+
* ⚠ ЗНАТЬ ПРО НЕГО ОБЯЗАНА И ЭТА СТОРОНА. Он лежит в `<дом>/bin/python` и в
|
|
790
|
+
* PATH не попадает намеренно — PATH человека мы не трогаем. Пропусти его
|
|
791
|
+
* здесь, и получится: установка принесла питон, окружение на нём собралось,
|
|
792
|
+
* а сервер MCP по-прежнему зовёт системный «python», которого нет или
|
|
793
|
+
* который заглушка из Microsoft Store.
|
|
772
794
|
*/
|
|
773
|
-
|
|
795
|
+
ourPython(): string | null;
|
|
774
796
|
/**
|
|
775
|
-
*
|
|
797
|
+
* Python для команд движка.
|
|
776
798
|
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
* своим: например, готовый фильтр цвета в его паке стиля.
|
|
799
|
+
* Окружения пака может не быть вовсе — именно это и проверяет `check_setup`,
|
|
800
|
+
* поэтому запускать проверку его питоном нельзя: получилась бы курица и
|
|
801
|
+
* яйцо. Команды движка написаны на голой стандартной библиотеке и идут
|
|
802
|
+
* системным python'ом, когда своего ещё нет.
|
|
782
803
|
*/
|
|
783
|
-
|
|
804
|
+
enginePython(): string;
|
|
805
|
+
/** Python подготовки кандидата — намеренно без fallback на venv старого пака. */
|
|
806
|
+
bootstrapPython(): string;
|
|
807
|
+
/** Python ровно названного кандидата, а не текущей версии. */
|
|
808
|
+
candidatePython(place: EnginePlace): string | null;
|
|
784
809
|
/**
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
810
|
+
* Среда для КАЖДОГО питона, которого мы запускаем.
|
|
811
|
+
*
|
|
812
|
+
* ⚠ БЕЗ ЭТОГО НА WINDOWS НЕ РАБОТАЕТ НИЧЕГО. Пока в питоне не включён режим
|
|
813
|
+
* UTF-8, он печатает в кодовой странице системы (cp1251 на русской, cp1252
|
|
814
|
+
* на английской), а Node декодирует ребёнка как UTF-8 — и весь ответ
|
|
815
|
+
* приезжал нечитаемым: «????? ???: /??? ??????.mp4». Отдельно ломался
|
|
816
|
+
* preview: строка JSON у нас с русскими ключами, и поле `файл` выходило
|
|
817
|
+
* undefined. Свой код мы чиним вызовом (`engine/kit/encoding.py`), а чужие
|
|
818
|
+
* библиотеки внутри того же процесса — только средой. Нужны оба заслона.
|
|
789
819
|
*/
|
|
790
|
-
|
|
791
|
-
resourceUri: string;
|
|
792
|
-
};
|
|
820
|
+
pythonEnv(): NodeJS.ProcessEnv;
|
|
793
821
|
/**
|
|
794
|
-
*
|
|
795
|
-
* с этим пользователем и не выходит наружу.
|
|
822
|
+
* Есть ли на машине python вообще — и словами, если нет.
|
|
796
823
|
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
824
|
+
* Весь движок (включая проверку и установку) идёт через запуск python'а.
|
|
825
|
+
* Когда его нет, `execFile` бросает ENOENT, и клиентской нейросети
|
|
826
|
+
* доставалось «spawn python3 ENOENT» — текст, из которого человеку не понять
|
|
827
|
+
* ничего. Шаг «python», написанный ровно для этого случая, при этом
|
|
828
|
+
* недостижим по устройству: он сам запускается python'ом.
|
|
801
829
|
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
|
|
830
|
+
* На Windows есть отдельная беда: в PATH лежит заглушка Microsoft Store —
|
|
831
|
+
* `python` там есть, он печатает рекламу магазина и выходит с кодом 9009.
|
|
832
|
+
* Поэтому проверяем не наличие файла, а ответ на вопрос о версии.
|
|
833
|
+
*/
|
|
834
|
+
pythonTrouble(): Promise<string | null>;
|
|
835
|
+
/**
|
|
836
|
+
* Запустить команду движка и отдать её текст как есть.
|
|
806
837
|
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
838
|
+
* Умолчание таймаута заведомо БОЛЬШЕ внутренних таймаутов движка (самый
|
|
839
|
+
* долгий — опрос окружения пака, 180 с). Внешний таймаут короче внутреннего
|
|
840
|
+
* означает, что внутренний недостижим: пробник убивался снаружи, и его
|
|
841
|
+
* объяснение не доезжало до человека вовсе.
|
|
809
842
|
*/
|
|
810
|
-
|
|
843
|
+
command(args: string[], { maxBuffer, timeout, key, }?: {
|
|
844
|
+
maxBuffer?: number;
|
|
845
|
+
timeout?: number;
|
|
846
|
+
key?: string;
|
|
847
|
+
}): Promise<CommandOutcome>;
|
|
811
848
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
849
|
+
* Сырой запуск консоли движка. Наружу нужен затем, что часть работы
|
|
850
|
+
* (заведение задания, показ кадра) читает не текст, а JSON.
|
|
814
851
|
*/
|
|
815
|
-
|
|
852
|
+
run(place: EnginePlace, args: string[], { maxBuffer, timeout, key, }?: {
|
|
853
|
+
maxBuffer?: number;
|
|
854
|
+
timeout?: number;
|
|
855
|
+
key?: string;
|
|
856
|
+
}): Promise<{
|
|
857
|
+
stdout: string;
|
|
858
|
+
stderr: string;
|
|
859
|
+
}>;
|
|
860
|
+
/** Запуск через явно выбранный Python нужен проверке кандидата до current. */
|
|
861
|
+
runWith(executable: string, place: EnginePlace, args: string[], { maxBuffer, timeout, key, }?: {
|
|
862
|
+
maxBuffer?: number;
|
|
863
|
+
timeout?: number;
|
|
864
|
+
key?: string;
|
|
865
|
+
}): Promise<{
|
|
866
|
+
stdout: string;
|
|
867
|
+
stderr: string;
|
|
868
|
+
}>;
|
|
869
|
+
/** Одна строка вывода команды — например, номер заведённого задания. */
|
|
870
|
+
asString(place: EnginePlace, args: string[], key?: string): Promise<string>;
|
|
816
871
|
/**
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
* запросе. Модель видит только имя + 1-строчное описание в специальном
|
|
821
|
-
* блоке `<deferred_tools>` системного промпта и подгружает полную схему
|
|
822
|
-
* через ToolSearch tool когда нужно её вызвать.
|
|
823
|
-
*
|
|
824
|
-
* Используется для редких/тяжёлых tools чтобы экономить токены input'а:
|
|
825
|
-
* - integration_call (динамический, описание ~жирное)
|
|
826
|
-
* - browse_page (тяжёлый prompt)
|
|
827
|
-
* - observer_*, credential_*, skill_* (admin-flow, редко)
|
|
828
|
-
*
|
|
829
|
-
* НЕ deferr'им: bash, view, read, write, edit, grep, glob, web_*,
|
|
830
|
-
* todo_write, Task, ToolSearch, memory tools — это core.
|
|
872
|
+
* Паспорт пака. Читается один раз за жизнь процесса: состав пака при
|
|
873
|
+
* работающей программе не меняется, а спрашивать его подпроцессом на каждый
|
|
874
|
+
* вызов инструмента — лишние полсекунды на ровном месте.
|
|
831
875
|
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
876
|
+
* ⚠ ТАЙМАУТ КОРОТКИЙ, И ЭТО НЕСУЩЕЕ. Паспорт читается НА СТАРТЕ, до
|
|
877
|
+
* регистрации инструментов, — то есть внутри рукопожатия, которое клиент
|
|
878
|
+
* ждёт по своему таймауту (у Codex это 10 секунд по умолчанию). Команда
|
|
879
|
+
* честно занимает доли секунды: запустить питон и прочитать pack.json.
|
|
880
|
+
* Не уложилась — с паком что-то не так, и правильный размен здесь такой:
|
|
881
|
+
* потерять подсказки со списками моделей, но поднять сервер. Обратный
|
|
882
|
+
* размен означал бы «сервер не запустился» и молчание вместо объяснения.
|
|
834
883
|
*/
|
|
835
|
-
|
|
884
|
+
passport(): Promise<PackPassport | null>;
|
|
836
885
|
/**
|
|
837
|
-
*
|
|
886
|
+
* Запустить фоновую работу движка, заранее заведя под неё задание.
|
|
838
887
|
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* - `Infinity` = hard opt-out, никогда не persist (для FileRead/view —
|
|
844
|
-
* нет смысла записывать файл во второй файл).
|
|
845
|
-
* - `undefined` = поведение как 100_000 (clamp до 50K).
|
|
888
|
+
* Задание заводится ДО запуска и отдельной командой: если запуск не удастся,
|
|
889
|
+
* задача всё равно видна, и человеку есть что показать. Формат файла знает
|
|
890
|
+
* только питон — эта сторона его не пишет, чтобы у файла не было двух хозяев
|
|
891
|
+
* на разных языках.
|
|
846
892
|
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
|
|
893
|
+
* Предохранителя параллельности здесь нет намеренно. Он в питоне
|
|
894
|
+
* (`engine/state.slot`) — файловые замки в `<дом>/busy`. Счётчик в
|
|
895
|
+
* памяти защищал только свой процесс: Claude Desktop и Claude Code поднимают
|
|
896
|
+
* по своему, и вместе они давали вдвое больше тяжёлой работы, чем разрешено.
|
|
897
|
+
*/
|
|
898
|
+
background(opts: {
|
|
899
|
+
place: EnginePlace;
|
|
900
|
+
executable: string;
|
|
901
|
+
args: (id: string) => string[];
|
|
902
|
+
startJob: string[];
|
|
903
|
+
advice: string;
|
|
904
|
+
/**
|
|
905
|
+
* Ключ подписки — ТОЛЬКО этому запуску, не общей среде питона.
|
|
906
|
+
*
|
|
907
|
+
* ⚠ БЕЗ НЕГО МОНТАЖ ОТКАЗЫВАЕТ КАЖДОМУ ПЛАТЯЩЕМУ. С 2026-09-09 движок
|
|
908
|
+
* спрашивает подписку сам (`client/engine/account/subscription.py`) и берёт ключ
|
|
909
|
+
* из `NAPARNIK_API_KEY`, а `pythonEnv()` его оттуда вычищает намеренно:
|
|
910
|
+
* иначе ключ уезжает в каждого потомка питона — ffmpeg, pip, чужой код
|
|
911
|
+
* пака. Поэтому не «вернуть ключ в среду», а доложить его точечно, ровно
|
|
912
|
+
* как уже сделано у скачивания движка (`run`, поле `key`).
|
|
913
|
+
*/
|
|
914
|
+
key: string | undefined;
|
|
915
|
+
}): Promise<{
|
|
916
|
+
id: string;
|
|
917
|
+
} | {
|
|
918
|
+
refusal: string;
|
|
919
|
+
}>;
|
|
920
|
+
/**
|
|
921
|
+
* Файл, куда фоновая работа пишет свой stderr.
|
|
852
922
|
*
|
|
853
|
-
*
|
|
923
|
+
* Лежит в доме, а не рядом с видео: у установки видео нет вовсе, а ответ на
|
|
924
|
+
* вопрос «что она успела сказать перед смертью» нужен одинаково обоим.
|
|
925
|
+
* Держим последние `KEEP_LOGS` — полоса загрузки модели пишет сотни
|
|
926
|
+
* килобайт, и вечная папка таких файлов была бы платой человека за то, что
|
|
927
|
+
* он однажды монтировал.
|
|
854
928
|
*/
|
|
855
|
-
|
|
929
|
+
private openLog;
|
|
856
930
|
/**
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
931
|
+
* Присмотр за фоновой работой: перевести её смерть в состояние задания.
|
|
932
|
+
*
|
|
933
|
+
* Один на монтаж и на установку. Двумя копиями это было бы двумя разными
|
|
934
|
+
* ответами человеку на одну и ту же беду.
|
|
861
935
|
*/
|
|
862
|
-
|
|
936
|
+
private watch;
|
|
863
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Путь, каким его понимает питон: с раскрытой тильдой и от нашей рабочей
|
|
940
|
+
* папки, а не от чужой.
|
|
941
|
+
*
|
|
942
|
+
* Нейросеть пишет «~/Movies/ролик.mp4» постоянно — тем более что описание
|
|
943
|
+
* `style_new` само подсказывает «~/.naparnik/стиль» как образец.
|
|
944
|
+
* `existsSync` тильду не раскрывает, и монтаж отвечал «Файла нет» о
|
|
945
|
+
* существующем файле. Питон везде делает expanduser; тут делаем то же самое.
|
|
946
|
+
*/
|
|
947
|
+
declare function path(candidate: string): string;
|
|
948
|
+
/**
|
|
949
|
+
* Путь к существующему файлу — или человеческий отказ.
|
|
950
|
+
*
|
|
951
|
+
* Копий этой проверки было четыре, слово в слово, и текст у них был хуже
|
|
952
|
+
* питоновского: там на этот случай уже сказано, что ролик мог остаться в
|
|
953
|
+
* «Загрузках» или его переименовали. Одна дверь.
|
|
954
|
+
*/
|
|
955
|
+
declare function fileOrRefusal(candidate: string): {
|
|
956
|
+
path: string;
|
|
957
|
+
} | {
|
|
958
|
+
refusal: string;
|
|
959
|
+
};
|
|
960
|
+
/**
|
|
961
|
+
* Разбор неудачи команды движка — ОДИН на все её виды.
|
|
962
|
+
*
|
|
963
|
+
* Своё сообщение команды важнее сообщения запускателя: «Command failed: …» с
|
|
964
|
+
* полным путём к python'у человеку не говорит ничего. Код 2 значит «чего-то не
|
|
965
|
+
* хватает» или «задача не удалась» — это НОРМАЛЬНЫЙ ответ, а не сбой
|
|
966
|
+
* инструмента: подай его ошибкой, и нейросеть начнёт извиняться вместо того,
|
|
967
|
+
* чтобы доставить недостающее.
|
|
968
|
+
*/
|
|
969
|
+
declare function parseFailure(err: unknown, fallback: string): CommandOutcome;
|
|
970
|
+
/** Последняя непустая строка вывода — там, где команда печатает результат. */
|
|
971
|
+
declare function lastLine(output: string): string;
|
|
864
972
|
|
|
865
973
|
/**
|
|
866
974
|
* Аргументы инструмента: ОДНА запись на объявление модели и на проверку входа.
|
|
@@ -1015,6 +1123,95 @@ interface Pace {
|
|
|
1015
1123
|
rest: (ms: number) => Promise<void>;
|
|
1016
1124
|
}
|
|
1017
1125
|
|
|
1126
|
+
interface ScrubContext {
|
|
1127
|
+
/** Домашняя папка ОС (`os.homedir()`). */
|
|
1128
|
+
home: string;
|
|
1129
|
+
/** Имя пользователя ОС: оно бывает в путях чужих машин и в текстах ошибок. */
|
|
1130
|
+
user: string;
|
|
1131
|
+
/** Дом Напарника (`Engine.home()`): бывает задан не в `~/.naparnik`. */
|
|
1132
|
+
naparnikHome: string;
|
|
1133
|
+
/** Папка, из которой запущена сама оболочка. */
|
|
1134
|
+
shellDir: string;
|
|
1135
|
+
/**
|
|
1136
|
+
* Строки из аргументов вызова: путь к ролику, папка, текст правки. Их
|
|
1137
|
+
* знаем точно — и вычёркиваем в любом виде, в котором они встречаются.
|
|
1138
|
+
*/
|
|
1139
|
+
secrets: string[];
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/** Хост — программа, в которой живёт оболочка (`clientInfo` рукопожатия MCP). */
|
|
1143
|
+
type Host = {
|
|
1144
|
+
name: string;
|
|
1145
|
+
version: string;
|
|
1146
|
+
};
|
|
1147
|
+
/** Поломка до обезличивания: всё, что узнали на месте. */
|
|
1148
|
+
interface Crash {
|
|
1149
|
+
where: 'tool' | 'job' | 'process';
|
|
1150
|
+
tool: string | null;
|
|
1151
|
+
stage: string | null;
|
|
1152
|
+
errorType: string;
|
|
1153
|
+
message: string;
|
|
1154
|
+
trace: string;
|
|
1155
|
+
/** Какой конец трейса ценнее: у питона — низ, у JS — верх. */
|
|
1156
|
+
keep: 'head' | 'tail';
|
|
1157
|
+
/** Кадры для отпечатка: «файл:функция» без версий и номеров строк. */
|
|
1158
|
+
frames: string[];
|
|
1159
|
+
python: string | null;
|
|
1160
|
+
/** Строки из аргументов вызова — их вычёркиваем в любом виде. */
|
|
1161
|
+
secrets: string[];
|
|
1162
|
+
host: Host | null;
|
|
1163
|
+
/** Строка для модели: суть одной строкой и место. */
|
|
1164
|
+
essence: string;
|
|
1165
|
+
}
|
|
1166
|
+
interface CrashReporterDeps {
|
|
1167
|
+
/** Клиент нашего сервера — тот же, что спрашивает подписку: ключ, отпечаток машины, версии. */
|
|
1168
|
+
client: Pick<NaparnikApiClient, 'postMontage'>;
|
|
1169
|
+
/** Дом Напарника — `Engine.home()`. */
|
|
1170
|
+
home: string;
|
|
1171
|
+
engineVersion: () => string | null;
|
|
1172
|
+
/** Есть ли ключ: без него отчёт не уйдёт, и человеку это обещать нельзя. */
|
|
1173
|
+
canSend: boolean;
|
|
1174
|
+
now?: () => number;
|
|
1175
|
+
/** Версия ffmpeg; спрашивается при отправке, мимо ответа инструмента. */
|
|
1176
|
+
ffmpegVersion?: () => Promise<string | null>;
|
|
1177
|
+
/** Папка, откуда запущена оболочка: её пути в трейсе — наши. */
|
|
1178
|
+
shellDir?: string;
|
|
1179
|
+
osHome?: string;
|
|
1180
|
+
osUser?: string;
|
|
1181
|
+
timeoutMs?: number;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Один на процесс оболочки: его держат и обёртка инструментов, и перехват
|
|
1185
|
+
* падения процесса (`cli.ts`). Состояние, которое переживает перезапуск
|
|
1186
|
+
* (очередь, частота, номера заданий), лежит на диске в `<дом>/crash-reports/`.
|
|
1187
|
+
*/
|
|
1188
|
+
declare class CrashReporter {
|
|
1189
|
+
#private;
|
|
1190
|
+
constructor(deps: CrashReporterDeps);
|
|
1191
|
+
get canSend(): boolean;
|
|
1192
|
+
get home(): string;
|
|
1193
|
+
/** Запомнить программу-хост: при падении всего процесса контекста вызова нет. */
|
|
1194
|
+
/** Запомнить строки аргументов вызова — чтобы вычеркнуть их из будущих отчётов. */
|
|
1195
|
+
noteSecrets(secrets: string[]): void;
|
|
1196
|
+
noteHost(host: Host | null): void;
|
|
1197
|
+
/** Отправить отчёт мимо ответа. Для тестов и смоука — `settled()`. */
|
|
1198
|
+
submit(crash: Crash): void;
|
|
1199
|
+
/**
|
|
1200
|
+
* Процесс падает: отчёт кладётся на диск СИНХРОННО и уходит на следующем
|
|
1201
|
+
* запуске. Сеть здесь не трогаем — процессу осталось жить миллисекунды, и
|
|
1202
|
+
* недоставленный наполовину запрос хуже файла, который точно дойдёт.
|
|
1203
|
+
*/
|
|
1204
|
+
persist(crash: Crash): void;
|
|
1205
|
+
/** Досылка очереди — не чаще раза в минуту. */
|
|
1206
|
+
flushSoon(): void;
|
|
1207
|
+
/** Дождаться всего, что ушло мимо ответа. Боевому пути не нужно. */
|
|
1208
|
+
settled(): Promise<void>;
|
|
1209
|
+
/** Задание уже разобрано: отчёт о нём ушёл раньше, в этом или прошлом запуске. */
|
|
1210
|
+
jobSeen(id: string): boolean;
|
|
1211
|
+
rememberJob(id: string): void;
|
|
1212
|
+
scrubContext(secrets: string[]): ScrubContext;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1018
1215
|
/**
|
|
1019
1216
|
* Набор инструментов покупателя — одним списком и под одними обёртками.
|
|
1020
1217
|
*
|
|
@@ -1077,7 +1274,19 @@ baseUrl: string,
|
|
|
1077
1274
|
* а цикл без подмены времени проверялся бы минутным прогоном — то есть
|
|
1078
1275
|
* никогда. Умолчание обычное, вызывающему про это знать не надо.
|
|
1079
1276
|
*/
|
|
1080
|
-
pace?: Pace
|
|
1277
|
+
pace?: Pace,
|
|
1278
|
+
/**
|
|
1279
|
+
* «Не спи» (`keep-awake.ts`): один экземпляр на программу, его же держит
|
|
1280
|
+
* движок. Параметром — чтобы тест проверил проводку НАСТОЯЩЕГО списка, не
|
|
1281
|
+
* трогая систему; умолчание выключено по той же причине.
|
|
1282
|
+
*/
|
|
1283
|
+
awake?: Wakefulness,
|
|
1284
|
+
/**
|
|
1285
|
+
* Отчёты о поломках (`crash-report.ts`): один на программу, его же держит
|
|
1286
|
+
* перехват падения процесса в `cli.ts`. `null` — отчётов нет: соседние тесты
|
|
1287
|
+
* собирают набор без сервера и не должны слать ничего.
|
|
1288
|
+
*/
|
|
1289
|
+
crashes?: CrashReporter | null): Tool[];
|
|
1081
1290
|
/**
|
|
1082
1291
|
* Ресурсы виджетов — парой к `buildAll`, из того же файла: кто собирает
|
|
1083
1292
|
* сервер из инструментов, здесь же берёт и HTML к ним. Сторож пары — тест
|
|
@@ -1309,9 +1518,9 @@ declare class NaparnikConfigError extends NaparnikError {
|
|
|
1309
1518
|
* годится ли она для новой начинки: разойдись она с пакетом, и в логах будет
|
|
1310
1519
|
* стоять чужое число, а обновление начнёт считать оболочку не той.
|
|
1311
1520
|
*/
|
|
1312
|
-
declare const VERSION = "0.10.
|
|
1521
|
+
declare const VERSION = "0.10.25";
|
|
1313
1522
|
/** Как мы представляемся серверу Напарника. По нему в логах видно оболочку. */
|
|
1314
|
-
declare const USER_AGENT = "naparnik-mcp/0.10.
|
|
1523
|
+
declare const USER_AGENT = "naparnik-mcp/0.10.25";
|
|
1315
1524
|
/**
|
|
1316
1525
|
* Самая старая начинка (движок монтажа), с которой эта оболочка работает.
|
|
1317
1526
|
* Обновление начинки ниже этой версии не ставится, а выше — требует оболочки
|