@axon-assistant/core 2026.8.25 → 2026.8.26

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/README.md CHANGED
@@ -32,6 +32,7 @@ axon start --host 0.0.0.0 # ещё и для устройств из се
32
32
  использования.
33
33
 
34
34
  ```bash
35
+ axon code # новый код подключения устройства
35
36
  axon status # запущено ли и где
36
37
  axon secret set provider.anthropic.apiKey sk-… # ключ провайдера
37
38
  axon devices # подключённые устройства
package/dist/Daemon.d.ts CHANGED
@@ -57,6 +57,11 @@ export declare class Daemon {
57
57
  * ответ, и мог бы показать его только целиком в самом конце.
58
58
  */
59
59
  private readonly signalListeners;
60
+ /**
61
+ * Пропуск для того, кто сидит за этой машиной: им подписан запрос на новый
62
+ * код подключения. Живёт в файле рядом с базой, с правами 0600.
63
+ */
64
+ private readonly controlToken;
60
65
  /** Разрешается, когда все установленные плагины отработали запуск. */
61
66
  pluginsReady: Promise<void>;
62
67
  constructor(options?: DaemonOptions);
@@ -116,6 +121,14 @@ export declare class Daemon {
116
121
  * же машине мог его найти, не завися от того, кто его поднял, адрес и pid
117
122
  * кладутся в файл рядом с базой.
118
123
  */
124
+ private controlPath;
125
+ /**
126
+ * Пропуск переписывается на каждом запуске: старый файл, оставшийся от
127
+ * убитого процесса, не должен открывать двери в новое ядро.
128
+ */
129
+ private writeControlToken;
130
+ /** Сравнение постоянного времени: пропуск подбирают по одному символу. */
131
+ private controlAllowed;
119
132
  private announce;
120
133
  private withdraw;
121
134
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"Daemon.d.ts","sourceRoot":"","sources":["../src/Daemon.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAKtD,OAAO,EAML,OAAO,EAER,MAAM,gBAAgB,CAAC;AAKxB,eAAO,MAAM,cAAc,QAC4C,CAAC;AAIxE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,IAAI,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAoCD;;;;;;;;;;GAUG;AACH,qBAAa,MAAM;IAyBL,OAAO,CAAC,QAAQ,CAAC,OAAO;IAxBpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,QAAQ,CAAgC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,OAAO,CAAC,IAAI,CAA4B;IACxC;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuC;IACvE,sEAAsE;IACtE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAqB;gBAEnB,OAAO,GAAE,aAAkB;IAsClD,KAAK,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC;QAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAoDhF;;;;;;;OAOG;YACW,YAAY;IAmC1B;;;;;OAKG;YACW,WAAW;IAkBzB;;;;;OAKG;IACG,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;IAoC3E,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrC,cAAc;;;;IAOd;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,QAAQ;IAIV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,IAAI,GAAG,IAAI,MAAM,CAGhB;IAED,4EAA4E;IAC5E,OAAO,CAAC,WAAW;YAWL,SAAS;IAsDvB,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,SAAS;CA+BlB"}
1
+ {"version":3,"file":"Daemon.d.ts","sourceRoot":"","sources":["../src/Daemon.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAKtD,OAAO,EAML,OAAO,EAER,MAAM,gBAAgB,CAAC;AAKxB,eAAO,MAAM,cAAc,QAC4C,CAAC;AAIxE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,IAAI,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAoCD;;;;;;;;;;GAUG;AACH,qBAAa,MAAM;IA8BL,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7BpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,QAAQ,CAAgC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,OAAO,CAAC,IAAI,CAA4B;IACxC;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuC;IACvE;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0C;IACvE,sEAAsE;IACtE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAqB;gBAEnB,OAAO,GAAE,aAAkB;IAsClD,KAAK,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC;QAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAqDhF;;;;;;;OAOG;YACW,YAAY;IAmC1B;;;;;OAKG;YACW,WAAW;IAkBzB;;;;;OAKG;IACG,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;IAoC3E,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrC,cAAc;;;;IAOd;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAInB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAKzB,0EAA0E;IAC1E,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,QAAQ;IAIV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,IAAI,GAAG,IAAI,MAAM,CAGhB;IAED,4EAA4E;IAC5E,OAAO,CAAC,WAAW;YAWL,SAAS;IAgGvB,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,SAAS;CA+BlB"}
package/dist/cli.js CHANGED
@@ -3424,8 +3424,8 @@ function estimateTokens(text) {
3424
3424
  return 0;
3425
3425
  let cyrillic = 0;
3426
3426
  for (const char of text) {
3427
- const code2 = char.codePointAt(0);
3428
- if (code2 >= 1024 && code2 <= 1279)
3427
+ const code3 = char.codePointAt(0);
3428
+ if (code3 >= 1024 && code3 <= 1279)
3429
3429
  cyrillic++;
3430
3430
  }
3431
3431
  const other = text.length - cyrillic;
@@ -5161,9 +5161,9 @@ var HOST_READY = "host.ready";
5161
5161
  // ../core/dist/plugins/rpc.js
5162
5162
  var RpcError = class extends Error {
5163
5163
  code;
5164
- constructor(message, code2 = "plugin_error") {
5164
+ constructor(message, code3 = "plugin_error") {
5165
5165
  super(message);
5166
- this.code = code2;
5166
+ this.code = code3;
5167
5167
  this.name = "RpcError";
5168
5168
  }
5169
5169
  };
@@ -5545,9 +5545,9 @@ var CLIENT_INFO = { name: "axon", version: "1" };
5545
5545
  var DEFAULT_TIMEOUT_MS = 6e4;
5546
5546
  var McpError = class extends Error {
5547
5547
  code;
5548
- constructor(message, code2 = "mcp_error") {
5548
+ constructor(message, code3 = "mcp_error") {
5549
5549
  super(message);
5550
- this.code = code2;
5550
+ this.code = code3;
5551
5551
  this.name = "McpError";
5552
5552
  }
5553
5553
  };
@@ -5659,10 +5659,10 @@ var McpClient = class {
5659
5659
  }
5660
5660
  });
5661
5661
  child.on("error", (error) => this.fail(new McpError(error.message, "spawn_failed")));
5662
- child.on("exit", (code2, signal) => {
5662
+ child.on("exit", (code3, signal) => {
5663
5663
  if (this.closed)
5664
5664
  return;
5665
- this.fail(new McpError(`\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F (${signal ?? code2})`, "exited"));
5665
+ this.fail(new McpError(`\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F (${signal ?? code3})`, "exited"));
5666
5666
  });
5667
5667
  }
5668
5668
  /** Разобрать поток stdout на строки: одна строка — одно сообщение JSON-RPC. */
@@ -6009,19 +6009,19 @@ var PluginProcess = class {
6009
6009
  clearTimeout(timer);
6010
6010
  reject(error);
6011
6011
  });
6012
- child.once("exit", (code2, signal) => {
6012
+ child.once("exit", (code3, signal) => {
6013
6013
  clearTimeout(timer);
6014
- reject(new Error(`\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0434\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 (${signal ?? code2})`));
6014
+ reject(new Error(`\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0434\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 (${signal ?? code3})`));
6015
6015
  });
6016
6016
  });
6017
- child.once("exit", (code2, signal) => {
6017
+ child.once("exit", (code3, signal) => {
6018
6018
  this.child = null;
6019
6019
  this.ready = false;
6020
6020
  this.peer.dispose(new RpcError("\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u043B\u0430\u0433\u0438\u043D\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D", "not_running"));
6021
6021
  if (this.stopping)
6022
6022
  return;
6023
- const reason = signal ? `\u0443\u0431\u0438\u0442 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C ${signal}` : `\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043A\u043E\u0434\u043E\u043C ${code2}`;
6024
- this.log.warn({ code: code2, signal }, "\u043F\u043B\u0430\u0433\u0438\u043D \u0443\u043F\u0430\u043B");
6023
+ const reason = signal ? `\u0443\u0431\u0438\u0442 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C ${signal}` : `\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043A\u043E\u0434\u043E\u043C ${code3}`;
6024
+ this.log.warn({ code: code3, signal }, "\u043F\u043B\u0430\u0433\u0438\u043D \u0443\u043F\u0430\u043B");
6025
6025
  this.options.onExit(reason);
6026
6026
  });
6027
6027
  await started;
@@ -6723,9 +6723,9 @@ var LoadedPlugin = class {
6723
6723
  // ../core/dist/plugins/PluginHost.js
6724
6724
  var PluginError = class extends Error {
6725
6725
  code;
6726
- constructor(message, code2 = "plugin_error") {
6726
+ constructor(message, code3 = "plugin_error") {
6727
6727
  super(message);
6728
- this.code = code2;
6728
+ this.code = code3;
6729
6729
  this.name = "PluginError";
6730
6730
  }
6731
6731
  };
@@ -7677,9 +7677,9 @@ function dayAt(from, offsetDays, time) {
7677
7677
  var TICK_MS2 = 3e4;
7678
7678
  var RoutineError = class extends Error {
7679
7679
  code;
7680
- constructor(message, code2 = "routine_error") {
7680
+ constructor(message, code3 = "routine_error") {
7681
7681
  super(message);
7682
- this.code = code2;
7682
+ this.code = code3;
7683
7683
  this.name = "RoutineError";
7684
7684
  }
7685
7685
  };
@@ -9571,7 +9571,7 @@ function runCommand(input) {
9571
9571
  input.signal.removeEventListener("abort", onAbort);
9572
9572
  reject(e);
9573
9573
  });
9574
- child.on("close", (code2) => {
9574
+ child.on("close", (code3) => {
9575
9575
  if (settled)
9576
9576
  return;
9577
9577
  settled = true;
@@ -9583,7 +9583,7 @@ function runCommand(input) {
9583
9583
  resolve({
9584
9584
  stdout: decodeOutput(Buffer.concat(stdout)).trimEnd(),
9585
9585
  stderr: decodeOutput(Buffer.concat(stderr)).trimEnd(),
9586
- code: code2
9586
+ code: code3
9587
9587
  });
9588
9588
  });
9589
9589
  });
@@ -10100,6 +10100,7 @@ axon plugin logs ${id}
10100
10100
  }
10101
10101
 
10102
10102
  // src/Daemon.ts
10103
+ import crypto4 from "node:crypto";
10103
10104
  import http from "node:http";
10104
10105
  import fs18 from "node:fs";
10105
10106
  import os7 from "node:os";
@@ -10128,11 +10129,11 @@ var PairingService = class {
10128
10129
  fs17.rmSync(this.bootstrapPath, { force: true });
10129
10130
  return null;
10130
10131
  }
10131
- const code2 = generateCode();
10132
+ const code3 = generateCode();
10132
10133
  fs17.mkdirSync(path17.dirname(this.bootstrapPath), { recursive: true });
10133
- fs17.writeFileSync(this.bootstrapPath, `${code2}
10134
+ fs17.writeFileSync(this.bootstrapPath, `${code3}
10134
10135
  `, { mode: 384 });
10135
- this.pending.set(code2, {
10136
+ this.pending.set(code3, {
10136
10137
  name: "\u041F\u0435\u0440\u0432\u043E\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
10137
10138
  platform: "desktop",
10138
10139
  // Первому устройству — всё: с него настраивают остальные.
@@ -10147,24 +10148,24 @@ var PairingService = class {
10147
10148
  ],
10148
10149
  expiresAt: Number.POSITIVE_INFINITY
10149
10150
  });
10150
- return code2;
10151
+ return code3;
10151
10152
  }
10152
10153
  begin(input) {
10153
- const code2 = generateCode();
10154
- this.pending.set(code2, {
10154
+ const code3 = generateCode();
10155
+ this.pending.set(code3, {
10155
10156
  name: input.name,
10156
10157
  platform: input.platform,
10157
10158
  scopes: input.scopes,
10158
10159
  expiresAt: Date.now() + input.ttlSeconds * 1e3
10159
10160
  });
10160
- return { code: code2, expiresInSeconds: input.ttlSeconds };
10161
+ return { code: code3, expiresInSeconds: input.ttlSeconds };
10161
10162
  }
10162
10163
  /** Обменять код на токен. Код одноразовый. */
10163
- complete(code2, deviceName) {
10164
+ complete(code3, deviceName) {
10164
10165
  this.sweep();
10165
- const pending = this.pending.get(code2);
10166
+ const pending = this.pending.get(code3);
10166
10167
  if (!pending) return null;
10167
- this.pending.delete(code2);
10168
+ this.pending.delete(code3);
10168
10169
  const token = generateToken();
10169
10170
  const device = this.runtime.store.pairDevice({
10170
10171
  name: deviceName || pending.name,
@@ -10177,20 +10178,20 @@ var PairingService = class {
10177
10178
  }
10178
10179
  sweep() {
10179
10180
  const now = Date.now();
10180
- for (const [code2, pending] of this.pending) {
10181
- if (pending.expiresAt < now) this.pending.delete(code2);
10181
+ for (const [code3, pending] of this.pending) {
10182
+ if (pending.expiresAt < now) this.pending.delete(code3);
10182
10183
  }
10183
10184
  }
10184
10185
  };
10185
10186
  function generateCode() {
10186
10187
  const alphabet = "ACDEFGHJKLMNPQRTUVWXY34679";
10187
10188
  const bytes = crypto3.randomBytes(8);
10188
- let code2 = "";
10189
+ let code3 = "";
10189
10190
  for (let i = 0; i < 8; i++) {
10190
- if (i === 4) code2 += "-";
10191
- code2 += alphabet[bytes[i] % alphabet.length];
10191
+ if (i === 4) code3 += "-";
10192
+ code3 += alphabet[bytes[i] % alphabet.length];
10192
10193
  }
10193
- return code2;
10194
+ return code3;
10194
10195
  }
10195
10196
  function authenticate(runtime, token) {
10196
10197
  if (!token) return null;
@@ -10247,9 +10248,9 @@ var POLL_TIMEOUT_SECONDS = 25;
10247
10248
  var TelegramError = class extends Error {
10248
10249
  code;
10249
10250
  retryAfter;
10250
- constructor(message, code2, retryAfter2) {
10251
+ constructor(message, code3, retryAfter2) {
10251
10252
  super(message);
10252
- this.code = code2;
10253
+ this.code = code3;
10253
10254
  this.retryAfter = retryAfter2;
10254
10255
  this.name = "TelegramError";
10255
10256
  }
@@ -10384,8 +10385,8 @@ function toTelegramHtml(markdown) {
10384
10385
  return out.join("\n").replace(/\n{3,}/g, "\n\n").trim();
10385
10386
  }
10386
10387
  function closeFence(body, language) {
10387
- const code2 = escape(body.join("\n"));
10388
- return language ? `<pre><code class="language-${escape(language)}">${code2}</code></pre>` : `<pre>${code2}</pre>`;
10388
+ const code3 = escape(body.join("\n"));
10389
+ return language ? `<pre><code class="language-${escape(language)}">${code3}</code></pre>` : `<pre>${code3}</pre>`;
10389
10390
  }
10390
10391
  function block(line) {
10391
10392
  const heading = /^\s{0,3}(#{1,6})\s+(.*)$/.exec(line);
@@ -10405,10 +10406,10 @@ function block(line) {
10405
10406
  return inline(line);
10406
10407
  }
10407
10408
  function inline(text) {
10408
- const code2 = [];
10409
+ const code3 = [];
10409
10410
  let work = text.replace(/`([^`]+)`/g, (_, body) => {
10410
- code2.push(`<code>${escape(body)}</code>`);
10411
- return `\0${code2.length - 1}\0`;
10411
+ code3.push(`<code>${escape(body)}</code>`);
10412
+ return `\0${code3.length - 1}\0`;
10412
10413
  });
10413
10414
  work = escape(work);
10414
10415
  work = work.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, label, href) => {
@@ -10419,7 +10420,7 @@ function inline(text) {
10419
10420
  work = work.replace(/(^|[\s(])\*([^*\n]+)\*/g, "$1<i>$2</i>");
10420
10421
  work = work.replace(/(^|[\s(])_([^_\n]+)_/g, "$1<i>$2</i>");
10421
10422
  work = work.replace(/~~([^~]+)~~/g, "<s>$1</s>");
10422
- return work.replace(/\u0000(\d+)\u0000/g, (_, index) => code2[Number(index)] ?? "");
10423
+ return work.replace(/\u0000(\d+)\u0000/g, (_, index) => code3[Number(index)] ?? "");
10423
10424
  }
10424
10425
  function escape(text) {
10425
10426
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -10558,17 +10559,17 @@ var TelegramAdapter = class {
10558
10559
  }
10559
10560
  /** Привязка: `/start КОД` меняет код на устройство, как это делает десктоп. */
10560
10561
  async onStart(message, text) {
10561
- const code2 = text.slice("/start".length).trim();
10562
+ const code3 = text.slice("/start".length).trim();
10562
10563
  if (this.bindingOf(message.chat.id)) {
10563
10564
  await this.api.sendMessage(message.chat.id, "\u0423\u0436\u0435 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438. \u041F\u0438\u0448\u0438\u0442\u0435.");
10564
10565
  return;
10565
10566
  }
10566
- if (!code2) {
10567
+ if (!code3) {
10567
10568
  await this.api.sendMessage(message.chat.id, "\u041D\u0443\u0436\u0435\u043D \u043A\u043E\u0434 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F.\n\n\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 Axon \u2192 \xAB\u0423\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430\xBB \u2192 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043A\u043E\u0434, \u043F\u043E\u0442\u043E\u043C \u0441\u044E\u0434\u0430: <code>/start \u041A\u041E\u0414</code>", { parseMode: "HTML" });
10568
10569
  return;
10569
10570
  }
10570
10571
  const name = [message.from?.first_name, message.from?.last_name].filter(Boolean).join(" ");
10571
- const paired = this.deps.pair(code2.toUpperCase(), `\u0422\u0435\u043B\u0435\u0433\u0440\u0430\u043C: ${name || message.chat.id}`);
10572
+ const paired = this.deps.pair(code3.toUpperCase(), `\u0422\u0435\u043B\u0435\u0433\u0440\u0430\u043C: ${name || message.chat.id}`);
10572
10573
  if (!paired) {
10573
10574
  await this.api.sendMessage(message.chat.id, "\u041A\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u043E\u0448\u0451\u043B \u2014 \u0438\u0441\u0442\u0451\u043A \u0438\u043B\u0438 \u0443\u0436\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D.");
10574
10575
  return;
@@ -11144,13 +11145,13 @@ var UserbotAuth = class {
11144
11145
  * продолжение, и обращаться с ней как с отказом значит терять начатый вход
11145
11146
  * там, где он идёт нормально.
11146
11147
  */
11147
- async signIn(code2) {
11148
+ async signIn(code3) {
11148
11149
  const pending = this.require();
11149
11150
  try {
11150
11151
  await pending.client.invoke(new Api2.auth.SignIn({
11151
11152
  phoneNumber: pending.phone,
11152
11153
  phoneCodeHash: pending.phoneCodeHash,
11153
- phoneCode: code2.trim()
11154
+ phoneCode: code3.trim()
11154
11155
  }));
11155
11156
  } catch (error) {
11156
11157
  if (!isPasswordNeeded(error))
@@ -11199,9 +11200,9 @@ function isPasswordNeeded(error) {
11199
11200
 
11200
11201
  // src/commands.ts
11201
11202
  var CommandError = class extends Error {
11202
- constructor(code2, message) {
11203
+ constructor(code3, message) {
11203
11204
  super(message);
11204
- this.code = code2;
11205
+ this.code = code3;
11205
11206
  this.name = "CommandError";
11206
11207
  }
11207
11208
  };
@@ -11433,8 +11434,8 @@ var handlers = {
11433
11434
  try {
11434
11435
  return { plugin: await runtime.plugins.update(req.id) };
11435
11436
  } catch (error) {
11436
- const code2 = error.code;
11437
- throw new CommandError(code2 === "not_found" ? "not_found" : "bad_request", error.message);
11437
+ const code3 = error.code;
11438
+ throw new CommandError(code3 === "not_found" ? "not_found" : "bad_request", error.message);
11438
11439
  }
11439
11440
  },
11440
11441
  "plugin.configure": async (req, { runtime }) => {
@@ -11555,8 +11556,8 @@ async function withPlugin(run2) {
11555
11556
  try {
11556
11557
  await run2();
11557
11558
  } catch (error) {
11558
- const code2 = error.code;
11559
- throw new CommandError(code2 === "not_found" ? "not_found" : "bad_request", error.message);
11559
+ const code3 = error.code;
11560
+ throw new CommandError(code3 === "not_found" ? "not_found" : "bad_request", error.message);
11560
11561
  }
11561
11562
  }
11562
11563
  function requireConversation(runtime, id) {
@@ -11689,7 +11690,7 @@ var WsSession = class {
11689
11690
  };
11690
11691
 
11691
11692
  // src/Daemon.ts
11692
- var DAEMON_VERSION = true ? "2026.8.25" : "0.0.0-dev";
11693
+ var DAEMON_VERSION = true ? "2026.8.26" : "0.0.0-dev";
11693
11694
  var MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
11694
11695
  function isWildcard(host) {
11695
11696
  return host === "0.0.0.0" || host === "::" || host === "";
@@ -11764,6 +11765,11 @@ var Daemon = class {
11764
11765
  * ответ, и мог бы показать его только целиком в самом конце.
11765
11766
  */
11766
11767
  signalListeners = /* @__PURE__ */ new Set();
11768
+ /**
11769
+ * Пропуск для того, кто сидит за этой машиной: им подписан запрос на новый
11770
+ * код подключения. Живёт в файле рядом с базой, с правами 0600.
11771
+ */
11772
+ controlToken = crypto4.randomBytes(24).toString("hex");
11767
11773
  /** Разрешается, когда все установленные плагины отработали запуск. */
11768
11774
  pluginsReady = Promise.resolve();
11769
11775
  async start() {
@@ -11785,6 +11791,7 @@ var Daemon = class {
11785
11791
  reachable: isWildcard(host) ? reachableUrls(boundPort) : []
11786
11792
  };
11787
11793
  const bootstrapCode = this.pairing.ensureBootstrapCode();
11794
+ this.writeControlToken();
11788
11795
  this.announce();
11789
11796
  logger.info({ url: this.address.url, coreId: this.runtime.coreId }, "\u0434\u0435\u043C\u043E\u043D \u0441\u043B\u0443\u0448\u0430\u0435\u0442");
11790
11797
  this.pluginsReady = this.runtime.startPlugins().catch((error) => {
@@ -11818,7 +11825,7 @@ var Daemon = class {
11818
11825
  const adapter = new TelegramAdapter(
11819
11826
  {
11820
11827
  runtime: this.runtime,
11821
- pair: (code2, name) => this.pairing.complete(code2, name),
11828
+ pair: (code3, name) => this.pairing.complete(code3, name),
11822
11829
  resolvePermission: (requestId, allow) => void this.permissions.resolve(requestId, allow ? "allow_once" : "deny_once"),
11823
11830
  onSignal: (listener) => {
11824
11831
  this.signalListeners.add(listener);
@@ -11903,6 +11910,26 @@ var Daemon = class {
11903
11910
  * же машине мог его найти, не завися от того, кто его поднял, адрес и pid
11904
11911
  * кладутся в файл рядом с базой.
11905
11912
  */
11913
+ controlPath() {
11914
+ return path18.join(this.runtime.config.dataDir, "control.token");
11915
+ }
11916
+ /**
11917
+ * Пропуск переписывается на каждом запуске: старый файл, оставшийся от
11918
+ * убитого процесса, не должен открывать двери в новое ядро.
11919
+ */
11920
+ writeControlToken() {
11921
+ fs18.mkdirSync(this.runtime.config.dataDir, { recursive: true });
11922
+ fs18.writeFileSync(this.controlPath(), `${this.controlToken}
11923
+ `, { mode: 384 });
11924
+ }
11925
+ /** Сравнение постоянного времени: пропуск подбирают по одному символу. */
11926
+ controlAllowed(given) {
11927
+ const value = Array.isArray(given) ? given[0] : given;
11928
+ if (!value) return false;
11929
+ const a = Buffer.from(value.trim());
11930
+ const b = Buffer.from(this.controlToken);
11931
+ return a.length === b.length && crypto4.timingSafeEqual(a, b);
11932
+ }
11906
11933
  announce() {
11907
11934
  const file = path18.join(this.runtime.config.dataDir, "core.json");
11908
11935
  fs18.writeFileSync(
@@ -11962,11 +11989,34 @@ var Daemon = class {
11962
11989
  pendingPermissions: this.permissions.pendingCount
11963
11990
  });
11964
11991
  }
11992
+ if (req.method === "POST" && url.pathname === "/v1/control/pair") {
11993
+ if (!this.controlAllowed(req.headers["x-axon-control"])) {
11994
+ return respond(res, 403, { error: "forbidden" });
11995
+ }
11996
+ const body = await readJson(req);
11997
+ const chatOnly = body?.["scopes"] === "chat";
11998
+ const ttl = Number(body?.["ttlSeconds"] ?? 300);
11999
+ const issued = this.pairing.begin({
12000
+ name: typeof body?.["name"] === "string" ? body["name"] : "\u041D\u043E\u0432\u043E\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
12001
+ platform: "desktop",
12002
+ scopes: chatOnly ? ["chat.read", "chat.write", "tools.safe"] : [
12003
+ "chat.read",
12004
+ "chat.write",
12005
+ "tools.safe",
12006
+ "tools.sensitive",
12007
+ "tools.dangerous",
12008
+ "settings.write",
12009
+ "devices.manage"
12010
+ ],
12011
+ ttlSeconds: Number.isFinite(ttl) && ttl > 0 ? Math.min(ttl, 3600) : 300
12012
+ });
12013
+ return respond(res, 200, issued);
12014
+ }
11965
12015
  if (req.method === "POST" && url.pathname === "/v1/pair") {
11966
12016
  const body = await readJson(req);
11967
- const code2 = typeof body?.["code"] === "string" ? body["code"] : "";
12017
+ const code3 = typeof body?.["code"] === "string" ? body["code"] : "";
11968
12018
  const name = typeof body?.["name"] === "string" ? body["name"] : void 0;
11969
- const paired = this.pairing.complete(code2.trim().toUpperCase(), name);
12019
+ const paired = this.pairing.complete(code3.trim().toUpperCase(), name);
11970
12020
  if (!paired) return respond(res, 403, { error: "invalid_code" });
11971
12021
  return respond(res, 200, {
11972
12022
  token: paired.token,
@@ -12093,6 +12143,8 @@ async function main() {
12093
12143
  return await restore(args);
12094
12144
  case "status":
12095
12145
  return await status();
12146
+ case "code":
12147
+ return await code2(args);
12096
12148
  case "stop":
12097
12149
  return stop();
12098
12150
  case "help":
@@ -12129,11 +12181,11 @@ async function start(args) {
12129
12181
  try {
12130
12182
  started = await daemon.start();
12131
12183
  } catch (error) {
12132
- const code2 = error.code;
12133
- if (code2 === "EADDRINUSE") {
12184
+ const code3 = error.code;
12185
+ if (code3 === "EADDRINUSE") {
12134
12186
  return fail(`\u041F\u043E\u0440\u0442 ${port} \u0437\u0430\u043D\u044F\u0442. \u041C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C, \u044F\u0434\u0440\u043E \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442: axon status`);
12135
12187
  }
12136
- if (code2 === "EADDRNOTAVAIL") {
12188
+ if (code3 === "EADDRNOTAVAIL") {
12137
12189
  return fail(
12138
12190
  `\u0410\u0434\u0440\u0435\u0441 ${host} \u043D\u0435 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435.
12139
12191
  \u0427\u0442\u043E\u0431\u044B \u043F\u0443\u0441\u043A\u0430\u0442\u044C \u0434\u0440\u0443\u0433\u0438\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430, \u0441\u043B\u0443\u0448\u0430\u044E\u0442 \u0432\u0441\u0435 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044B: axon start --host 0.0.0.0`
@@ -12164,6 +12216,37 @@ async function start(args) {
12164
12216
  process.on("SIGINT", stop2);
12165
12217
  process.on("SIGTERM", stop2);
12166
12218
  }
12219
+ async function code2(args) {
12220
+ const record = readRecord();
12221
+ if (!record || !await alive(record.url)) {
12222
+ return fail("\u042F\u0434\u0440\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E \u2014 \u043A\u043E\u0434 \u0432\u044B\u0434\u0430\u0451\u0442 \u043E\u043D\u043E \u0441\u0430\u043C\u043E. \u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435: axon start");
12223
+ }
12224
+ const file = path19.join(resolveConfig().dataDir, "control.token");
12225
+ let token;
12226
+ try {
12227
+ token = fs19.readFileSync(file, "utf8").trim();
12228
+ } catch {
12229
+ return fail(`\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A ${file} \u2014 \u043A\u043E\u0434 \u043C\u043E\u0436\u0435\u0442 \u0432\u044B\u0434\u0430\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0445\u043E\u0437\u044F\u0438\u043D \u044F\u0434\u0440\u0430`);
12230
+ }
12231
+ const chatOnly = args.includes("--chat");
12232
+ const ttl = Number(valueOf(args, "--ttl") ?? 300);
12233
+ const response = await fetch(`${record.url}/v1/control/pair`, {
12234
+ method: "POST",
12235
+ headers: { "Content-Type": "application/json", "x-axon-control": token },
12236
+ body: JSON.stringify({ scopes: chatOnly ? "chat" : "full", ttlSeconds: ttl })
12237
+ }).catch(() => null);
12238
+ if (!response?.ok) {
12239
+ return fail(
12240
+ response?.status === 403 ? "\u042F\u0434\u0440\u043E \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B\u043E \u043F\u0440\u043E\u043F\u0443\u0441\u043A. \u041F\u043E\u0445\u043E\u0436\u0435, \u043E\u043D\u043E \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B\u043E\u0441\u044C \u2014 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437" : "\u042F\u0434\u0440\u043E \u043D\u0435 \u043E\u0442\u0434\u0430\u043B\u043E \u043A\u043E\u0434"
12241
+ );
12242
+ }
12243
+ const issued = await response.json();
12244
+ console.log(issued.code);
12245
+ console.log("");
12246
+ console.log(` \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 ${Math.round(issued.expiresInSeconds / 60)} \u043C\u0438\u043D, \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u044B\u0439`);
12247
+ console.log(` \u043F\u0440\u0430\u0432\u0430: ${chatOnly ? "\u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0430" : "\u043F\u043E\u043B\u043D\u044B\u0435"}`);
12248
+ console.log(` \u0430\u0434\u0440\u0435\u0441 \u0434\u043B\u044F \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F: ${(record.reachable ?? [])[0] ?? record.url}`);
12249
+ }
12167
12250
  function logPath() {
12168
12251
  return path19.join(resolveConfig().dataDir, "axon.log");
12169
12252
  }
@@ -12190,11 +12273,11 @@ async function detach(args) {
12190
12273
  console.log(` \u0432\u044B\u0432\u043E\u0434: ${file}`);
12191
12274
  console.log(" \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C: axon stop");
12192
12275
  const codeFile = path19.join(config.dataDir, "bootstrap.code");
12193
- const code2 = fs19.existsSync(codeFile) ? fs19.readFileSync(codeFile, "utf8").trim() : "";
12194
- if (code2) {
12276
+ const code3 = fs19.existsSync(codeFile) ? fs19.readFileSync(codeFile, "utf8").trim() : "";
12277
+ if (code3) {
12195
12278
  console.log("");
12196
12279
  console.log(" \u0423\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442. \u041A\u043E\u0434 \u0434\u043B\u044F \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F:");
12197
- console.log(` ${code2}`);
12280
+ console.log(` ${code3}`);
12198
12281
  console.log("");
12199
12282
  }
12200
12283
  }
@@ -12460,6 +12543,7 @@ function usage() {
12460
12543
  --host 0.0.0.0 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043B\u044F \u0434\u0440\u0443\u0433\u0438\u0445 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432
12461
12544
  --foreground, -f \u043D\u0435 \u0443\u0445\u043E\u0434\u0438\u0442\u044C \u0432 \u0444\u043E\u043D (systemd, docker)
12462
12545
  axon status \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E \u043B\u0438 \u044F\u0434\u0440\u043E \u0438 \u0433\u0434\u0435
12546
+ axon code [--chat] [--ttl 300] \u043D\u043E\u0432\u044B\u0439 \u043A\u043E\u0434 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0430
12463
12547
  axon stop \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u044F\u0434\u0440\u043E
12464
12548
  axon secret list \u043A\u0430\u043A\u0438\u0435 \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0437\u0430\u0434\u0430\u043D\u044B
12465
12549
  axon secret get <\u043A\u043B\u044E\u0447> \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442 \u0446\u0435\u043B\u0438\u043A\u043E\u043C
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/Daemon.ts
2
+ import crypto4 from "node:crypto";
2
3
  import http from "node:http";
3
4
  import fs16 from "node:fs";
4
5
  import os7 from "node:os";
@@ -11352,7 +11353,7 @@ var WsSession = class {
11352
11353
  };
11353
11354
 
11354
11355
  // src/Daemon.ts
11355
- var DAEMON_VERSION = true ? "2026.8.25" : "0.0.0-dev";
11356
+ var DAEMON_VERSION = true ? "2026.8.26" : "0.0.0-dev";
11356
11357
  var MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
11357
11358
  function isWildcard(host) {
11358
11359
  return host === "0.0.0.0" || host === "::" || host === "";
@@ -11427,6 +11428,11 @@ var Daemon = class {
11427
11428
  * ответ, и мог бы показать его только целиком в самом конце.
11428
11429
  */
11429
11430
  signalListeners = /* @__PURE__ */ new Set();
11431
+ /**
11432
+ * Пропуск для того, кто сидит за этой машиной: им подписан запрос на новый
11433
+ * код подключения. Живёт в файле рядом с базой, с правами 0600.
11434
+ */
11435
+ controlToken = crypto4.randomBytes(24).toString("hex");
11430
11436
  /** Разрешается, когда все установленные плагины отработали запуск. */
11431
11437
  pluginsReady = Promise.resolve();
11432
11438
  async start() {
@@ -11448,6 +11454,7 @@ var Daemon = class {
11448
11454
  reachable: isWildcard(host) ? reachableUrls(boundPort) : []
11449
11455
  };
11450
11456
  const bootstrapCode = this.pairing.ensureBootstrapCode();
11457
+ this.writeControlToken();
11451
11458
  this.announce();
11452
11459
  logger.info({ url: this.address.url, coreId: this.runtime.coreId }, "\u0434\u0435\u043C\u043E\u043D \u0441\u043B\u0443\u0448\u0430\u0435\u0442");
11453
11460
  this.pluginsReady = this.runtime.startPlugins().catch((error) => {
@@ -11566,6 +11573,26 @@ var Daemon = class {
11566
11573
  * же машине мог его найти, не завися от того, кто его поднял, адрес и pid
11567
11574
  * кладутся в файл рядом с базой.
11568
11575
  */
11576
+ controlPath() {
11577
+ return path16.join(this.runtime.config.dataDir, "control.token");
11578
+ }
11579
+ /**
11580
+ * Пропуск переписывается на каждом запуске: старый файл, оставшийся от
11581
+ * убитого процесса, не должен открывать двери в новое ядро.
11582
+ */
11583
+ writeControlToken() {
11584
+ fs16.mkdirSync(this.runtime.config.dataDir, { recursive: true });
11585
+ fs16.writeFileSync(this.controlPath(), `${this.controlToken}
11586
+ `, { mode: 384 });
11587
+ }
11588
+ /** Сравнение постоянного времени: пропуск подбирают по одному символу. */
11589
+ controlAllowed(given) {
11590
+ const value = Array.isArray(given) ? given[0] : given;
11591
+ if (!value) return false;
11592
+ const a = Buffer.from(value.trim());
11593
+ const b = Buffer.from(this.controlToken);
11594
+ return a.length === b.length && crypto4.timingSafeEqual(a, b);
11595
+ }
11569
11596
  announce() {
11570
11597
  const file = path16.join(this.runtime.config.dataDir, "core.json");
11571
11598
  fs16.writeFileSync(
@@ -11625,6 +11652,29 @@ var Daemon = class {
11625
11652
  pendingPermissions: this.permissions.pendingCount
11626
11653
  });
11627
11654
  }
11655
+ if (req.method === "POST" && url.pathname === "/v1/control/pair") {
11656
+ if (!this.controlAllowed(req.headers["x-axon-control"])) {
11657
+ return respond(res, 403, { error: "forbidden" });
11658
+ }
11659
+ const body = await readJson(req);
11660
+ const chatOnly = body?.["scopes"] === "chat";
11661
+ const ttl = Number(body?.["ttlSeconds"] ?? 300);
11662
+ const issued = this.pairing.begin({
11663
+ name: typeof body?.["name"] === "string" ? body["name"] : "\u041D\u043E\u0432\u043E\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
11664
+ platform: "desktop",
11665
+ scopes: chatOnly ? ["chat.read", "chat.write", "tools.safe"] : [
11666
+ "chat.read",
11667
+ "chat.write",
11668
+ "tools.safe",
11669
+ "tools.sensitive",
11670
+ "tools.dangerous",
11671
+ "settings.write",
11672
+ "devices.manage"
11673
+ ],
11674
+ ttlSeconds: Number.isFinite(ttl) && ttl > 0 ? Math.min(ttl, 3600) : 300
11675
+ });
11676
+ return respond(res, 200, issued);
11677
+ }
11628
11678
  if (req.method === "POST" && url.pathname === "/v1/pair") {
11629
11679
  const body = await readJson(req);
11630
11680
  const code = typeof body?.["code"] === "string" ? body["code"] : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axon-assistant/core",
3
- "version": "2026.8.25",
3
+ "version": "2026.8.26",
4
4
  "comment:version": "Заглушка. Настоящий номер проставляется из git-тега при публикации: см. scripts/release-npm.mjs. Версия, которую правят руками, рано или поздно расходится с тем, что опубликовано.",
5
5
  "description": "Ядро Axon: персональный AI-агент, который живёт на вашей машине",
6
6
  "keywords": [