@naparnik/mcp 0.10.52 → 0.10.54

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/dist/cli.d.ts CHANGED
@@ -1,4 +1,33 @@
1
1
  #!/usr/bin/env node
2
+ import { T as Tool, M as McpResource, a as McpResourceItemContent } from './tool-1kGke9dT.js';
3
+
4
+ /** Номер договора `toolset()` между телами. */
5
+ declare const SWAP_PROTOCOL = 1;
6
+ /** Что самообновление поставило за проход; `null` — ничего. */
7
+ interface UpdatedVersions {
8
+ engine: string | null;
9
+ shell: string | null;
10
+ }
11
+ /** Набор тела: всё, что хозяину нужно от кода оболочки. */
12
+ interface Toolset {
13
+ /** Инструменты — те самые, что уезжают в `tools/list`. */
14
+ tools: Tool[];
15
+ /** Виджеты парой к инструментам. */
16
+ resources: Array<{
17
+ resource: McpResource;
18
+ contents: McpResourceItemContent[];
19
+ }>;
20
+ /** Идёт ли работа, которую нельзя рвать заменой: задача движка или установки. */
21
+ busy(): boolean;
22
+ /** Один проход самообновления (`self-update.ts`): что поставил. */
23
+ selfUpdate(): Promise<UpdatedVersions>;
24
+ }
25
+ /** Чем хозяин может попросить набор. Часть договора `SWAP_PROTOCOL`. */
26
+ interface ToolsetHooks {
27
+ /** Сервер сказал, что вышла новая версия: пройти самообновление сейчас. */
28
+ requestUpdate(): void;
29
+ }
30
+
2
31
  /**
3
32
  * Режим установщика. Возвращает true, если аргументы разобраны и сервер
4
33
  * поднимать не надо.
@@ -11,6 +40,14 @@
11
40
  * `main()` зовётся только при прямом запуске (`launchedDirectly`).
12
41
  */
13
42
  declare function installer(args: string[]): Promise<boolean>;
43
+ /**
44
+ * Набор этого тела — для хозяина, который подхватывает новое тело на ходу.
45
+ *
46
+ * ⚠ ЭКСПОРТ — ЧАСТЬ ДОГОВОРА `SWAP_PROTOCOL` (`body-swap.ts`). Имя, форма
47
+ * `Toolset` и крючки хозяина меняются только вместе с номером.
48
+ */
49
+ declare function toolset(hooks: ToolsetHooks): Promise<Toolset>;
50
+
14
51
  /**
15
52
  * Поднять сервер MCP.
16
53
  *
@@ -20,7 +57,10 @@ declare function installer(args: string[]): Promise<boolean>;
20
57
  * `catch → process.exit(1)` убил бы процесс мимо перехвата пускового файла, и
21
58
  * обещанный откат на встроенную версию остался бы обещанием. Имя `start` —
22
59
  * часть договора, за номером которого следит `LAUNCHER_PROTOCOL`.
60
+ *
61
+ * ⚠ ТОТ, КТО ПОДНЯЛ `start`, — ХОЗЯИН ПРОВОДА НА ВСЮ ЖИЗНЬ ПРОЦЕССА. Новое
62
+ * тело он подхватывает сам (`body-swap.ts`), без перезапуска Клода.
23
63
  */
24
64
  declare function start(): Promise<void>;
25
65
 
26
- export { installer, start };
66
+ export { SWAP_PROTOCOL, installer, start, toolset };
package/dist/cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { Engine, chooseBody, handOverToBody, VERSION, rollback, rollbackShell, remove, fromNpxCache, install, keyFromBlock, readConfig, ForeignBlockError, currentMachineFingerprint, CrashReporter, watchProcessCrashes, McpServer, buildAll, realPace, buildResources, StdioServerTransport, reportInstallFaults, fromHost, WHERE_TO_GET_KEY, Wakefulness, systemBlocker, updateEngine, installStep, NaparnikApiClient, timeToCheck2, manifestUrlFor, fetchManifest, markChecked2, decide, shellDir, timeToCheck, bundleUrlFor, fetchShellManifest, markChecked, decideShell, installedShell, NaparnikConfigError } from './chunk-CXFF4BPP.js';
3
- import { fileURLToPath } from 'url';
2
+ import { Engine, chooseBody, handOverToBody, VERSION, rollback, rollbackShell, remove, fromNpxCache, install, keyFromBlock, readConfig, ForeignBlockError, McpServer, watchProcessCrashes, reportInstallFaults, noteInstallFault, StdioServerTransport, fromHost, WHERE_TO_GET_KEY, Wakefulness, systemBlocker, updateEngine, installStep, currentMachineFingerprint, CrashReporter, whenNewerReleased, buildAll, realPace, buildResources, shellDir, describeFailure, compare, toolError, NaparnikApiClient, ownSetupJobRunning, forModel, installedShell, startSetupJob, TITLE_UPDATE, runUpdate, setupJobDone, reportJobFaults, setupJobEnding, NaparnikConfigError, installMissing } from './chunk-USWWD5M5.js';
4
3
  import { realpathSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { fileURLToPath, pathToFileURL } from 'url';
5
6
 
6
7
  // src/api-client.ts
7
8
  function apiClientFor(state, machine = currentMachineFingerprint, engineVersion = () => null) {
@@ -43,7 +44,7 @@ var HOW_MONTAGE_WORKS = `
43
44
  6. \u041F\u0440\u0430\u0432\u043A\u0430 \u0436\u0434\u0451\u0442 \u043F\u0435\u0447\u0430\u0442\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B (stamps, document/edits.md):
44
45
  \u0431\u0435\u0437 \u043D\u0435\u0451 plan_edit \u043E\u0442\u043A\u0430\u0436\u0435\u0442 \u0438 \u043F\u0440\u0438\u0448\u043B\u0451\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443.
45
46
  7. \u0422\u044F\u0436\u0451\u043B\u043E\u0435 (\u0432\u044B\u0440\u0435\u0437 \u0433\u0435\u0440\u043E\u044F, \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438) \u2014 \u041F\u041E \u041F\u0420\u041E\u0421\u042C\u0411\u0415, \u0432\u0435\u0441 \u0438\u0437 setup.
46
- \xAB\u0414\u0430\xBB, \xAB\u043E\u0431\u043D\u043E\u0432\u0438\xBB \u2014 \u041E\u0414\u0418\u041D setup \u0441 consent=true.
47
+ \xAB\u0414\u0430\xBB, \xAB\u043E\u0431\u043D\u043E\u0432\u0438\xBB \u2014 \u041E\u0414\u0418\u041D install, \u0434\u0430\u043B\u044C\u0448\u0435 wait.
47
48
 
48
49
  \u0421\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0438 \u0431\u0435\u0437 \u0440\u043E\u043B\u0438\u043A\u0430: \u043F\u0440\u0435\u0437\u0435\u043D\u0442\u0430\u0446\u0438\u044F.
49
50
  \u041D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439; \u0432\u0440\u0435\u043C\u044F \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0442 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430.
@@ -90,23 +91,6 @@ ${HOW_MONTAGE_WORKS}
90
91
  \u041C\u043E\u043D\u0442\u0430\u0436 \u041D\u0415 \u0442\u0440\u0430\u0442\u0438\u0442 \u043D\u0438 \u0431\u0430\u043B\u0430\u043D\u0441, \u043D\u0438 \u0442\u043E\u043A\u0435\u043D\u044B.
91
92
  `.trim();
92
93
  }
93
- var HOST_CUT = 2048;
94
- var BASE_CHARS = 1500;
95
- var NEWS_SEPARATOR = "\n\n";
96
- var NEWS_LIMIT = Math.floor((HOST_CUT - BASE_CHARS - 2 * NEWS_SEPARATOR.length) / 2);
97
- function aboutNewPayload(ownDir, fresh, megabytes) {
98
- const now = ownDir === null ? "\u0434\u0432\u0438\u0436\u043E\u043A \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D" : `\u0441\u0442\u043E\u0438\u0442 ${ownDir}`;
99
- return `\u0412\u042B\u0428\u041B\u0410 \u041D\u041E\u0412\u0410\u042F \u0412\u0415\u0420\u0421\u0418\u042F \u0414\u0412\u0418\u0416\u041A\u0410: ${now}, \u0435\u0441\u0442\u044C ${fresh} (${megabytes.toFixed(1)} \u041C\u0411). \u0421\u0430\u043C\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u0435\u0442\u0441\u044F. \u0417\u0430\u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043E \u043C\u043E\u043D\u0442\u0430\u0436\u0435 \u2014 \u0441\u043A\u0430\u0436\u0438 \u043E\u0434\u043D\u043E\u0439 \u0444\u0440\u0430\u0437\u043E\u0439 \u0438 \u0441\u043F\u0440\u043E\u0441\u0438; \xAB\u0434\u0430\xBB \u2014 setup \u0441 what=engine \u0438 consent=true. \u041C\u043E\u043B\u0447\u0430 \u043D\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0439 \u0438 \u0435\u0433\u043E \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0431\u0440\u043E\u0441\u0430\u0439.`;
100
- }
101
- function aboutNewShell(installed, fresh, notes) {
102
- const head = `\u0412\u042B\u0428\u041B\u0410 \u041D\u041E\u0412\u0410\u042F \u0412\u0415\u0420\u0421\u0418\u042F \u0420\u0410\u0421\u0428\u0418\u0420\u0415\u041D\u0418\u042F: \u0441\u0442\u043E\u0438\u0442 ${installed}, \u0432\u044B\u0448\u043B\u0430 ${fresh}, \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F \u043E\u0442\u0441\u044E\u0434\u0430.`;
103
- const tail = "\u0417\u0430\u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043E \u043C\u043E\u043D\u0442\u0430\u0436\u0435 \u2014 \u0441\u043A\u0430\u0436\u0438 \u043E\u0434\u043D\u043E\u0439 \u0444\u0440\u0430\u0437\u043E\u0439 \u0438 \u0441\u043F\u0440\u043E\u0441\u0438; \xAB\u0434\u0430\xBB \u2014 setup \u0441 what=shell \u0438 consent=true. \u0412 \u0441\u0438\u043B\u0435 \u043F\u043E\u0441\u043B\u0435 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u041A\u043B\u043E\u0434\u0430 \u0438\u043B\u0438 Codex \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u044D\u0442\u043E.";
104
- if (notes === null || notes.trim() === "") return `${head} ${tail}`;
105
- const room = NEWS_LIMIT - head.length - tail.length - " \u0427\u0442\u043E \u043D\u043E\u0432\u043E\u0433\u043E: . ".length;
106
- const said = notes.replace(/\s+/g, " ").trim();
107
- const cut = said.length <= room ? said : `${said.slice(0, Math.max(0, room - 1)).trimEnd()}\u2026`;
108
- return `${head} \u0427\u0442\u043E \u043D\u043E\u0432\u043E\u0433\u043E: ${cut}. ${tail}`;
109
- }
110
94
 
111
95
  // src/auto-install.ts
112
96
  var NO_AUTO = "NAPARNIK_NO_AUTOINSTALL";
@@ -126,7 +110,7 @@ async function prepareEngine(engine, opts) {
126
110
  const plan = decideAuto({ off, hasKey, byHand, ready, refresh: opts.refresh === true });
127
111
  if (plan === "nothing") return quiet;
128
112
  if (plan === "refresh") {
129
- const fresh = await updateEngine(engine, true, opts.key, opts.baseUrl);
113
+ const fresh = await updateEngine(engine, opts.key, opts.baseUrl);
130
114
  const news = "error" in fresh ? fresh.error : typeof fresh.content === "string" ? fresh.content : "";
131
115
  if (news !== "") say(news);
132
116
  return { worked: true, ok: !("error" in fresh), lines: news === "" ? [] : [news] };
@@ -137,6 +121,186 @@ async function prepareEngine(engine, opts) {
137
121
  if (text !== "") say(text);
138
122
  return { worked: true, ok: !("error" in done), lines: text === "" ? [] : [text] };
139
123
  }
124
+ var SWAP_PROTOCOL = 1;
125
+ function updatedWords(version) {
126
+ return forModel(`\u041E\u0431\u043D\u043E\u0432\u0438\u043B \u043C\u043E\u043D\u0442\u0430\u0436 \u0434\u043E ${version} \u2014 \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442.`);
127
+ }
128
+ function needsRestartWords(version) {
129
+ return forModel(
130
+ `\u041D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043C\u043E\u043D\u0442\u0430\u0436\u0430 ${version} \u0441\u043A\u0430\u0447\u0430\u043D\u0430, \u043D\u043E \u0432\u0441\u0442\u0430\u043D\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430 Claude \u0438\u043B\u0438 Codex \u2014 \u0441\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043E\u0434\u043D\u043E\u0439 \u0444\u0440\u0430\u0437\u043E\u0439, \u043A\u043E\u0433\u0434\u0430 \u043E\u043D \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0435.`
131
+ );
132
+ }
133
+ var BodySwap = class {
134
+ constructor(o) {
135
+ this.o = o;
136
+ this.toolset = o.first;
137
+ this.version = o.version;
138
+ this.install(o.first);
139
+ }
140
+ o;
141
+ toolset;
142
+ /** Версия тела, которое сейчас отвечает; `null` — тело из `.mcpb` без папки версии. */
143
+ version;
144
+ inFlight = 0;
145
+ swapping = null;
146
+ /** Тела, которые не встали: второй раз не пробуем до смены `current`. */
147
+ refused = /* @__PURE__ */ new Set();
148
+ news = [];
149
+ /** Версия тела, которое отвечает сейчас. */
150
+ current() {
151
+ return this.version;
152
+ }
153
+ /** Самообновление сейчас: что встало — строкой модели в ближайший ответ. */
154
+ async selfUpdate() {
155
+ let updated;
156
+ try {
157
+ updated = await this.toolset.selfUpdate();
158
+ } catch (trouble) {
159
+ this.o.fault(trouble);
160
+ return;
161
+ }
162
+ if (updated.engine !== null && updated.shell === null) this.news.push(updatedWords(updated.engine));
163
+ await this.refresh();
164
+ }
165
+ /**
166
+ * Заменить набор, если вышло новое тело и ничего не идёт.
167
+ *
168
+ * ⚠ ОДНА ЗАМЕНА ЗА РАЗ. Два вызова, пришедшие вместе, ждут одну и ту же
169
+ * замену, а не заводят две: вторая импортировала бы то же тело второй раз.
170
+ */
171
+ refresh() {
172
+ if (this.swapping !== null) return this.swapping;
173
+ if (this.o.off === true || this.inFlight > 0 || this.toolset.busy()) return Promise.resolve();
174
+ const next = this.pointed();
175
+ if (next === null || next === this.version || this.refused.has(next)) return Promise.resolve();
176
+ this.swapping = this.swapTo(next).finally(() => {
177
+ this.swapping = null;
178
+ });
179
+ return this.swapping;
180
+ }
181
+ /** Какую версию тела называет указатель — дёшево, без сверки сумм. */
182
+ pointed() {
183
+ try {
184
+ const name = readFileSync(join(shellDir(this.o.home), "current"), "utf8").trim();
185
+ return name === "" ? null : name;
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ async swapTo(next) {
191
+ const choice = chooseBody({ home: this.o.home, ownFile: this.o.ownFile });
192
+ if (choice.what !== "body" || choice.version !== next) {
193
+ this.refused.add(next);
194
+ if (choice.what === "embedded" && choice.why !== null) this.o.log(`\u043D\u043E\u0432\u043E\u0435 \u0442\u0435\u043B\u043E \u043D\u0435 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: ${choice.why}`);
195
+ return;
196
+ }
197
+ let body;
198
+ let toolset2;
199
+ try {
200
+ body = await (this.o.load ?? importBody)(choice.entry);
201
+ if (body.SWAP_PROTOCOL !== SWAP_PROTOCOL || typeof body.toolset !== "function") {
202
+ this.refused.add(next);
203
+ this.news.push(needsRestartWords(next));
204
+ this.o.log(`\u0442\u0435\u043B\u043E ${next} \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043F\u043E \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u0434\u043E\u0433\u043E\u0432\u043E\u0440\u0443 \u0437\u0430\u043C\u0435\u043D\u044B \u2014 \u0432\u0441\u0442\u0430\u043D\u0435\u0442 \u0441 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C`);
205
+ return;
206
+ }
207
+ toolset2 = await body.toolset({ requestUpdate: () => {
208
+ void this.selfUpdate();
209
+ } });
210
+ } catch (trouble) {
211
+ this.refused.add(next);
212
+ this.o.fault(trouble);
213
+ this.o.log(`\u0442\u0435\u043B\u043E ${next} \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u043E\u0441\u044C (${describeFailure(trouble)}) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u0440\u0435\u0436\u043D\u0438\u043C`);
214
+ return;
215
+ }
216
+ if (this.inFlight > 0 || this.toolset.busy()) return;
217
+ const was = this.version;
218
+ this.toolset = toolset2;
219
+ this.version = next;
220
+ this.install(toolset2);
221
+ this.o.log(`\u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u043B \u0442\u0435\u043B\u043E ${next} \u0431\u0435\u0437 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430`);
222
+ if (was === null || compare(next, was) > 0) this.news.push(updatedWords(next));
223
+ }
224
+ install(toolset2) {
225
+ for (const one of toolset2.resources) this.o.shelf.addResource(one.resource, one.contents);
226
+ this.o.shelf.replaceTools(toolset2.tools.map((tool) => this.proxy(tool)));
227
+ }
228
+ /**
229
+ * Инструмент, который зовёт ТЕКУЩИЙ набор, а не тот, что был при регистрации.
230
+ *
231
+ * ⚠ ЗАМЕНА ПРОВЕРЯЕТСЯ И ПЕРЕД ВЫЗОВОМ, А НЕ ТОЛЬКО ПОСЛЕ. Тело мог обновить
232
+ * соседний процесс (Codex рядом с Claude Desktop), и первый же вызов после
233
+ * этого шёл бы прежним кодом.
234
+ */
235
+ proxy(tool) {
236
+ const name = tool.name;
237
+ return {
238
+ ...tool,
239
+ execute: async (args, ctx) => {
240
+ await this.refresh();
241
+ const now = this.toolset.tools.find((one) => one.name === name);
242
+ if (now === void 0) {
243
+ return toolError(`\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 ${name} \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043D\u0435\u0442 \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u043F\u0438\u0441\u043E\u043A \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u0437\u0430\u043D\u043E\u0432\u043E.`);
244
+ }
245
+ this.inFlight += 1;
246
+ try {
247
+ return this.withNews(await now.execute(args, ctx));
248
+ } finally {
249
+ this.inFlight -= 1;
250
+ if (this.inFlight === 0) void this.refresh();
251
+ }
252
+ }
253
+ };
254
+ }
255
+ /** Приложить новость модели к ответу — один раз. В данные окна не идёт. */
256
+ withNews(result) {
257
+ if (this.news.length === 0) return result;
258
+ const said = this.news.splice(0).join("\n");
259
+ if (result.type === "error") return { ...result, error: `${result.error}
260
+
261
+ ${said}` };
262
+ return typeof result.content === "string" ? { ...result, content: `${result.content}
263
+
264
+ ${said}` } : { ...result, content: [...result.content, { type: "text", text: said }] };
265
+ }
266
+ };
267
+ async function importBody(entry) {
268
+ return await import(pathToFileURL(entry).href);
269
+ }
270
+
271
+ // src/self-update.ts
272
+ var SELF_UPDATE_EVERY_MS = 6 * 60 * 60 * 1e3;
273
+ var NO_UPDATE = "NAPARNIK_NO_UPDATE";
274
+ var POINTER_MS = 15e3;
275
+ var NOTHING = { engine: null, shell: null };
276
+ async function selfUpdatePass(deps) {
277
+ const { engine, key, baseUrl } = deps;
278
+ if (engine.variable(NO_UPDATE) !== void 0) return NOTHING;
279
+ if (engine.variable(NO_AUTO) !== void 0) return NOTHING;
280
+ if (key === void 0 || key === "") return NOTHING;
281
+ if (engine.place() === null) return NOTHING;
282
+ const home = engine.home();
283
+ const engineWas = engine.freshVersion();
284
+ const shellWas = installedShell(home);
285
+ const id = startSetupJob(TITLE_UPDATE, (job) => runUpdate(job, {
286
+ engine,
287
+ key,
288
+ baseUrl,
289
+ what: "all",
290
+ pointerMs: POINTER_MS,
291
+ installMissing: deps.installMissing
292
+ }), Date.now, home);
293
+ await setupJobDone(id);
294
+ if (deps.crashes !== null) reportJobFaults(deps.crashes, id, "\u0441\u0430\u043C\u043E\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435");
295
+ const ending = setupJobEnding(id);
296
+ if (ending === null) return NOTHING;
297
+ const engineNow = engine.freshVersion();
298
+ const shellNow = installedShell(home);
299
+ return {
300
+ engine: engineNow !== null && engineNow !== engineWas ? engineNow : null,
301
+ shell: shellNow !== shellWas ? shellNow : null
302
+ };
303
+ }
140
304
 
141
305
  // src/installer-args.ts
142
306
  var KEY_ARG = "--key";
@@ -341,51 +505,9 @@ function wakefulEngine() {
341
505
  function serverAddress(state) {
342
506
  return state.ok ? state.config.baseUrl : state.baseUrl;
343
507
  }
344
- async function newPayload(engine, baseUrl, key) {
345
- if (engine.variable("NAPARNIK_NO_UPDATE") !== void 0) return null;
346
- if (engine.place() === null) return null;
347
- if (engine.pathSetByHand()) return null;
348
- if (!timeToCheck2(engine)) return null;
349
- try {
350
- const url = engine.variable("NAPARNIK_UPDATE_MANIFEST") ?? manifestUrlFor(baseUrl);
351
- const outcome = await fetchManifest(url, void 0, key);
352
- markChecked2(engine);
353
- const decision = decide(engine.freshVersion(), outcome.ok ? outcome.manifest : null);
354
- if (decision.what !== "newer_available") return null;
355
- return aboutNewPayload(
356
- engine.freshVersion(),
357
- decision.manifest.version,
358
- decision.manifest.bytes / 1024 / 1024
359
- );
360
- } catch {
361
- return null;
362
- }
363
- }
364
- async function newShell(engine, baseUrl, key) {
365
- if (engine.variable("NAPARNIK_NO_UPDATE") !== void 0) return null;
366
- const home = engine.home();
367
- const dir = shellDir(home);
368
- if (!timeToCheck(dir)) return null;
369
- try {
370
- const url = engine.variable("NAPARNIK_BUNDLE_MANIFEST") ?? bundleUrlFor(baseUrl);
371
- const outcome = await fetchShellManifest(url, void 0, key);
372
- markChecked(dir);
373
- const decision = decideShell(installedShell(home), outcome.ok ? outcome.manifest : null);
374
- if (decision.what !== "newer_available") return null;
375
- return aboutNewShell(
376
- installedShell(home),
377
- decision.manifest.version,
378
- decision.manifest.notes
379
- );
380
- } catch {
381
- return null;
382
- }
383
- }
384
- async function start() {
385
- const state = readConfig();
508
+ async function assemble(state, hooks) {
386
509
  const engineForVersion = new Engine();
387
510
  const client = apiClientFor(state, void 0, () => engineForVersion.currentVersion());
388
- const basics = state.ok ? configuredInstructions(state.config.baseUrl) : notConfiguredInstructions(state.problem, state.message);
389
511
  const { awake, engine } = wakefulEngine();
390
512
  const crashes = new CrashReporter({
391
513
  client,
@@ -393,33 +515,67 @@ async function start() {
393
515
  engineVersion: () => engine.currentVersion(),
394
516
  canSend: state.ok
395
517
  });
396
- watchProcessCrashes(crashes);
397
- const [passport, aboutUpdate, aboutShell] = await Promise.all([
398
- engine.passport(),
399
- newPayload(engine, serverAddress(state), state.ok ? state.config.apiKey : void 0),
400
- newShell(engine, serverAddress(state), state.ok ? state.config.apiKey : void 0)
401
- ]);
402
- const news = [aboutUpdate, aboutShell].filter((one) => one !== null);
518
+ const key = state.ok ? state.config.apiKey : void 0;
519
+ const baseUrl = serverAddress(state);
520
+ whenNewerReleased(() => {
521
+ hooks.requestUpdate();
522
+ });
523
+ const passport = await engine.passport();
524
+ const tools = buildAll(client, engine, passport, key, baseUrl, realPace, awake, crashes);
525
+ const toolset2 = {
526
+ tools,
527
+ resources: buildResources(),
528
+ busy: () => engine.busy() || ownSetupJobRunning(),
529
+ selfUpdate: () => selfUpdatePass({
530
+ engine,
531
+ key,
532
+ baseUrl,
533
+ crashes: state.ok ? crashes : null,
534
+ installMissing: () => installMissing(engine, key, baseUrl)
535
+ })
536
+ };
537
+ return { toolset: toolset2, engine, awake, crashes };
538
+ }
539
+ async function toolset(hooks) {
540
+ return (await assemble(readConfig(), hooks)).toolset;
541
+ }
542
+ function startedAs(home) {
543
+ const own = theFile();
544
+ const shells = shellDir(home);
545
+ return own.startsWith(`${shells}/`) || own.startsWith(`${shells}\\`) ? own.slice(shells.length + 1).split(/[\\/]/)[0] ?? VERSION : VERSION;
546
+ }
547
+ async function start() {
548
+ const state = readConfig();
549
+ const basics = state.ok ? configuredInstructions(state.config.baseUrl) : notConfiguredInstructions(state.problem, state.message);
403
550
  const server = new McpServer(void 0, {
404
- instructions: news.length === 0 ? basics : `${basics}${NEWS_SEPARATOR}${news.join(NEWS_SEPARATOR)}`,
405
- // ⚠ ПРЕДСТАВЛЯЕМСЯ СВОИМ ИМЕНЕМ, А НЕ ИМЕНЕМ ЯДРА. В Claude Desktop имя
406
- // берётся из манифеста расширения и подмена незаметна, а в Codex человек
407
- // видит ровно это — и читал «@naparnik/super-agent-core 0.1.0» вместо
551
+ instructions: basics,
552
+ // ⚠ ПРЕДСТАВЛЯЕМСЯ СВОИМ ИМЕНЕМ, А НЕ ИМЕНЕМ ЯДРА. В Codex человек видит
553
+ // ровно это — и читал «@naparnik/super-agent-core 0.1.0» вместо
408
554
  // названия расширения, которое сам поставил.
409
555
  serverInfo: { name: "@naparnik/mcp", version: VERSION }
410
556
  });
411
- const tools = buildAll(
412
- client,
413
- engine,
414
- passport,
415
- state.ok ? state.config.apiKey : void 0,
416
- serverAddress(state),
417
- realPace,
418
- awake,
419
- crashes
420
- );
421
- for (const tool of tools) server.addTool(tool);
422
- for (const r of buildResources()) server.addResource(r.resource, r.contents);
557
+ let swap = null;
558
+ const hooks = { requestUpdate: () => {
559
+ void swap?.selfUpdate();
560
+ } };
561
+ const { toolset: first, engine, awake, crashes } = await assemble(state, hooks);
562
+ watchProcessCrashes(crashes);
563
+ const home = engine.home();
564
+ swap = new BodySwap({
565
+ shelf: server,
566
+ first,
567
+ version: startedAs(home),
568
+ home,
569
+ ownFile: theFile(),
570
+ // Проба кандидата (`probeShell`) и разбор беды руками: тело не меняется.
571
+ off: engine.variable("NAPARNIK_NO_BODY") !== void 0 || engine.variable(NO_UPDATE) !== void 0,
572
+ log,
573
+ fault: (trouble) => {
574
+ void reportInstallFaults(crashes, async () => {
575
+ noteInstallFault("shell-swap", trouble);
576
+ });
577
+ }
578
+ });
423
579
  const transport = new StdioServerTransport();
424
580
  transport.onClose((error) => {
425
581
  if (error !== void 0) log(`\u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439: ${error.message}`);
@@ -429,20 +585,27 @@ async function start() {
429
585
  await server.start(transport);
430
586
  const aboutMontage = engine.place() !== null ? `\u0434\u0432\u0438\u0436\u043E\u043A \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043D\u0430\u0439\u0434\u0435\u043D: ${engine.place()?.engine}` : "\u0434\u0432\u0438\u0436\u043E\u043A \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u2014 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043E\u0431\u044A\u044F\u0441\u043D\u044F\u0442 \u044D\u0442\u043E \u0441\u043B\u043E\u0432\u0430\u043C\u0438";
431
587
  if (state.ok) {
432
- log(`\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D \u043A ${state.config.baseUrl}, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432: ${tools.length}; ${aboutMontage}`);
588
+ log(`\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D \u043A ${state.config.baseUrl}, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432: ${first.tools.length}; ${aboutMontage}`);
433
589
  } else {
434
590
  log(
435
591
  `${state.message} \u0418\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044F \u043F\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0443. \u041C\u043E\u043D\u0442\u0430\u0436 \u0431\u0435\u0437 \u043A\u043B\u044E\u0447\u0430 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0441\u044F, \u043D\u043E \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E: ${aboutMontage}.`
436
592
  );
437
593
  }
438
594
  if (state.ok) {
439
- void awake.during(() => reportInstallFaults(crashes, () => prepareEngine(engine, {
440
- key: state.config.apiKey,
441
- baseUrl: state.config.baseUrl,
442
- tell: log
443
- }))).catch((trouble) => {
444
- log(`\u0434\u0432\u0438\u0436\u043E\u043A \u0434\u043E\u043A\u0430\u0447\u0430\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${trouble instanceof Error ? trouble.message : String(trouble)}`);
595
+ const background = async () => {
596
+ await awake.during(() => reportInstallFaults(crashes, () => prepareEngine(engine, {
597
+ key: state.config.apiKey,
598
+ baseUrl: state.config.baseUrl,
599
+ tell: log
600
+ })));
601
+ await swap?.selfUpdate();
602
+ };
603
+ void background().catch((trouble) => {
604
+ log(`\u0434\u0432\u0438\u0436\u043E\u043A \u0434\u043E\u043A\u0430\u0447\u0430\u0442\u044C \u0438\u043B\u0438 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${trouble instanceof Error ? trouble.message : String(trouble)}`);
445
605
  });
606
+ setInterval(() => {
607
+ void swap?.selfUpdate();
608
+ }, SELF_UPDATE_EVERY_MS).unref();
446
609
  }
447
610
  for (const signal of ["SIGINT", "SIGTERM"]) {
448
611
  process.on(signal, () => {
@@ -478,4 +641,4 @@ if (launchedDirectly()) {
478
641
  });
479
642
  }
480
643
 
481
- export { installer, start };
644
+ export { SWAP_PROTOCOL, installer, start, toolset };