@axon-assistant/core 2026.8.25 → 2026.8.27
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 +1 -0
- package/dist/Daemon.d.ts +13 -0
- package/dist/Daemon.d.ts.map +1 -1
- package/dist/cli.js +198 -67
- package/dist/index.js +101 -4
- package/dist/plugin-host.js +9 -1
- package/package.json +1 -1
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>;
|
package/dist/Daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Daemon.d.ts","sourceRoot":"","sources":["../src/Daemon.ts"],"names":[],"mappings":"
|
|
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;IAuHvB,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,SAAS;CA+BlB"}
|
package/dist/cli.js
CHANGED
|
@@ -680,10 +680,33 @@ var SecretStore = class {
|
|
|
680
680
|
const row = this.db.prepare(`SELECT * FROM secrets WHERE key = ?`).get(key);
|
|
681
681
|
if (!row)
|
|
682
682
|
return null;
|
|
683
|
+
return this.decrypt(row);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Расшифровать строку. Бросает, если значение не сходится с ключом.
|
|
687
|
+
*
|
|
688
|
+
* GCM проверяет подпись, поэтому чужой ключ шифрования — это не мусор на
|
|
689
|
+
* выходе, а честный отказ. Ловить его здесь нельзя: молча вернуть `null`
|
|
690
|
+
* значит превратить «ключ не читается» в «ключа нет», а лечатся они
|
|
691
|
+
* по-разному.
|
|
692
|
+
*/
|
|
693
|
+
decrypt(row) {
|
|
683
694
|
const decipher = crypto.createDecipheriv(ALGORITHM, this.masterKey(), row.iv);
|
|
684
695
|
decipher.setAuthTag(row.tag);
|
|
685
696
|
return Buffer.concat([decipher.update(row.ciphertext), decipher.final()]).toString("utf8");
|
|
686
697
|
}
|
|
698
|
+
/** Читается ли значение здешним ключом шифрования. */
|
|
699
|
+
readable(key) {
|
|
700
|
+
const row = this.db.prepare(`SELECT * FROM secrets WHERE key = ?`).get(key);
|
|
701
|
+
if (!row)
|
|
702
|
+
return false;
|
|
703
|
+
try {
|
|
704
|
+
this.decrypt(row);
|
|
705
|
+
return true;
|
|
706
|
+
} catch {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
687
710
|
delete(key) {
|
|
688
711
|
this.db.prepare(`DELETE FROM secrets WHERE key = ?`).run(key);
|
|
689
712
|
}
|
|
@@ -704,7 +727,12 @@ var SecretStore = class {
|
|
|
704
727
|
key,
|
|
705
728
|
set: true,
|
|
706
729
|
...row.hint ? { hint: row.hint } : {},
|
|
707
|
-
updatedAt: row.updated_at
|
|
730
|
+
updatedAt: row.updated_at,
|
|
731
|
+
// Наличие строки в базе — ещё не рабочий секрет: база могла приехать
|
|
732
|
+
// с другой машины, а ключ шифрования остаться там. «Ключ на месте» в
|
|
733
|
+
// таком случае врало, и человек искал причину в чём угодно, кроме
|
|
734
|
+
// переноса.
|
|
735
|
+
...this.readable(key) ? {} : { unreadable: true }
|
|
708
736
|
};
|
|
709
737
|
});
|
|
710
738
|
}
|
|
@@ -1861,7 +1889,7 @@ import fs15 from "node:fs";
|
|
|
1861
1889
|
// ../protocol/dist/primitives.js
|
|
1862
1890
|
import { z } from "zod";
|
|
1863
1891
|
var PROTOCOL_VERSION = 1;
|
|
1864
|
-
var PROTOCOL_REVISION =
|
|
1892
|
+
var PROTOCOL_REVISION = 11;
|
|
1865
1893
|
var zTimestamp = z.string().datetime();
|
|
1866
1894
|
var zId = z.string().uuid();
|
|
1867
1895
|
var zExternalId = z.string().min(1).max(256);
|
|
@@ -2907,7 +2935,15 @@ var zSecretStatus = z10.object({
|
|
|
2907
2935
|
set: z10.boolean(),
|
|
2908
2936
|
/** Последние символы значения. Присутствует, только если секрет задан. */
|
|
2909
2937
|
hint: z10.string().max(8).optional(),
|
|
2910
|
-
updatedAt: zTimestamp.optional()
|
|
2938
|
+
updatedAt: zTimestamp.optional(),
|
|
2939
|
+
/**
|
|
2940
|
+
* Значение лежит, но расшифровать его нечем.
|
|
2941
|
+
*
|
|
2942
|
+
* Так бывает после переноса: база приехала, а ключ шифрования рядом с ней —
|
|
2943
|
+
* нет (`axon backup` не кладёт его в архив без спроса). Снаружи это выглядело
|
|
2944
|
+
* как «ключ на месте», хотя ни один запрос с ним не проходит.
|
|
2945
|
+
*/
|
|
2946
|
+
unreadable: z10.boolean().optional()
|
|
2911
2947
|
});
|
|
2912
2948
|
var zSettingsGetReq = z10.object({
|
|
2913
2949
|
/** Какие ключи вернуть. Пусто — все, к которым у устройства есть доступ. */
|
|
@@ -3424,8 +3460,8 @@ function estimateTokens(text) {
|
|
|
3424
3460
|
return 0;
|
|
3425
3461
|
let cyrillic = 0;
|
|
3426
3462
|
for (const char of text) {
|
|
3427
|
-
const
|
|
3428
|
-
if (
|
|
3463
|
+
const code3 = char.codePointAt(0);
|
|
3464
|
+
if (code3 >= 1024 && code3 <= 1279)
|
|
3429
3465
|
cyrillic++;
|
|
3430
3466
|
}
|
|
3431
3467
|
const other = text.length - cyrillic;
|
|
@@ -5161,9 +5197,9 @@ var HOST_READY = "host.ready";
|
|
|
5161
5197
|
// ../core/dist/plugins/rpc.js
|
|
5162
5198
|
var RpcError = class extends Error {
|
|
5163
5199
|
code;
|
|
5164
|
-
constructor(message,
|
|
5200
|
+
constructor(message, code3 = "plugin_error") {
|
|
5165
5201
|
super(message);
|
|
5166
|
-
this.code =
|
|
5202
|
+
this.code = code3;
|
|
5167
5203
|
this.name = "RpcError";
|
|
5168
5204
|
}
|
|
5169
5205
|
};
|
|
@@ -5545,9 +5581,9 @@ var CLIENT_INFO = { name: "axon", version: "1" };
|
|
|
5545
5581
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
5546
5582
|
var McpError = class extends Error {
|
|
5547
5583
|
code;
|
|
5548
|
-
constructor(message,
|
|
5584
|
+
constructor(message, code3 = "mcp_error") {
|
|
5549
5585
|
super(message);
|
|
5550
|
-
this.code =
|
|
5586
|
+
this.code = code3;
|
|
5551
5587
|
this.name = "McpError";
|
|
5552
5588
|
}
|
|
5553
5589
|
};
|
|
@@ -5659,10 +5695,10 @@ var McpClient = class {
|
|
|
5659
5695
|
}
|
|
5660
5696
|
});
|
|
5661
5697
|
child.on("error", (error) => this.fail(new McpError(error.message, "spawn_failed")));
|
|
5662
|
-
child.on("exit", (
|
|
5698
|
+
child.on("exit", (code3, signal) => {
|
|
5663
5699
|
if (this.closed)
|
|
5664
5700
|
return;
|
|
5665
|
-
this.fail(new McpError(`\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F (${signal ??
|
|
5701
|
+
this.fail(new McpError(`\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F (${signal ?? code3})`, "exited"));
|
|
5666
5702
|
});
|
|
5667
5703
|
}
|
|
5668
5704
|
/** Разобрать поток stdout на строки: одна строка — одно сообщение JSON-RPC. */
|
|
@@ -6009,19 +6045,19 @@ var PluginProcess = class {
|
|
|
6009
6045
|
clearTimeout(timer);
|
|
6010
6046
|
reject(error);
|
|
6011
6047
|
});
|
|
6012
|
-
child.once("exit", (
|
|
6048
|
+
child.once("exit", (code3, signal) => {
|
|
6013
6049
|
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 ??
|
|
6050
|
+
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
6051
|
});
|
|
6016
6052
|
});
|
|
6017
|
-
child.once("exit", (
|
|
6053
|
+
child.once("exit", (code3, signal) => {
|
|
6018
6054
|
this.child = null;
|
|
6019
6055
|
this.ready = false;
|
|
6020
6056
|
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
6057
|
if (this.stopping)
|
|
6022
6058
|
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 ${
|
|
6024
|
-
this.log.warn({ code:
|
|
6059
|
+
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}`;
|
|
6060
|
+
this.log.warn({ code: code3, signal }, "\u043F\u043B\u0430\u0433\u0438\u043D \u0443\u043F\u0430\u043B");
|
|
6025
6061
|
this.options.onExit(reason);
|
|
6026
6062
|
});
|
|
6027
6063
|
await started;
|
|
@@ -6723,9 +6759,9 @@ var LoadedPlugin = class {
|
|
|
6723
6759
|
// ../core/dist/plugins/PluginHost.js
|
|
6724
6760
|
var PluginError = class extends Error {
|
|
6725
6761
|
code;
|
|
6726
|
-
constructor(message,
|
|
6762
|
+
constructor(message, code3 = "plugin_error") {
|
|
6727
6763
|
super(message);
|
|
6728
|
-
this.code =
|
|
6764
|
+
this.code = code3;
|
|
6729
6765
|
this.name = "PluginError";
|
|
6730
6766
|
}
|
|
6731
6767
|
};
|
|
@@ -7677,9 +7713,9 @@ function dayAt(from, offsetDays, time) {
|
|
|
7677
7713
|
var TICK_MS2 = 3e4;
|
|
7678
7714
|
var RoutineError = class extends Error {
|
|
7679
7715
|
code;
|
|
7680
|
-
constructor(message,
|
|
7716
|
+
constructor(message, code3 = "routine_error") {
|
|
7681
7717
|
super(message);
|
|
7682
|
-
this.code =
|
|
7718
|
+
this.code = code3;
|
|
7683
7719
|
this.name = "RoutineError";
|
|
7684
7720
|
}
|
|
7685
7721
|
};
|
|
@@ -9571,7 +9607,7 @@ function runCommand(input) {
|
|
|
9571
9607
|
input.signal.removeEventListener("abort", onAbort);
|
|
9572
9608
|
reject(e);
|
|
9573
9609
|
});
|
|
9574
|
-
child.on("close", (
|
|
9610
|
+
child.on("close", (code3) => {
|
|
9575
9611
|
if (settled)
|
|
9576
9612
|
return;
|
|
9577
9613
|
settled = true;
|
|
@@ -9583,7 +9619,7 @@ function runCommand(input) {
|
|
|
9583
9619
|
resolve({
|
|
9584
9620
|
stdout: decodeOutput(Buffer.concat(stdout)).trimEnd(),
|
|
9585
9621
|
stderr: decodeOutput(Buffer.concat(stderr)).trimEnd(),
|
|
9586
|
-
code:
|
|
9622
|
+
code: code3
|
|
9587
9623
|
});
|
|
9588
9624
|
});
|
|
9589
9625
|
});
|
|
@@ -10100,6 +10136,7 @@ axon plugin logs ${id}
|
|
|
10100
10136
|
}
|
|
10101
10137
|
|
|
10102
10138
|
// src/Daemon.ts
|
|
10139
|
+
import crypto4 from "node:crypto";
|
|
10103
10140
|
import http from "node:http";
|
|
10104
10141
|
import fs18 from "node:fs";
|
|
10105
10142
|
import os7 from "node:os";
|
|
@@ -10128,11 +10165,11 @@ var PairingService = class {
|
|
|
10128
10165
|
fs17.rmSync(this.bootstrapPath, { force: true });
|
|
10129
10166
|
return null;
|
|
10130
10167
|
}
|
|
10131
|
-
const
|
|
10168
|
+
const code3 = generateCode();
|
|
10132
10169
|
fs17.mkdirSync(path17.dirname(this.bootstrapPath), { recursive: true });
|
|
10133
|
-
fs17.writeFileSync(this.bootstrapPath, `${
|
|
10170
|
+
fs17.writeFileSync(this.bootstrapPath, `${code3}
|
|
10134
10171
|
`, { mode: 384 });
|
|
10135
|
-
this.pending.set(
|
|
10172
|
+
this.pending.set(code3, {
|
|
10136
10173
|
name: "\u041F\u0435\u0440\u0432\u043E\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
|
|
10137
10174
|
platform: "desktop",
|
|
10138
10175
|
// Первому устройству — всё: с него настраивают остальные.
|
|
@@ -10147,24 +10184,24 @@ var PairingService = class {
|
|
|
10147
10184
|
],
|
|
10148
10185
|
expiresAt: Number.POSITIVE_INFINITY
|
|
10149
10186
|
});
|
|
10150
|
-
return
|
|
10187
|
+
return code3;
|
|
10151
10188
|
}
|
|
10152
10189
|
begin(input) {
|
|
10153
|
-
const
|
|
10154
|
-
this.pending.set(
|
|
10190
|
+
const code3 = generateCode();
|
|
10191
|
+
this.pending.set(code3, {
|
|
10155
10192
|
name: input.name,
|
|
10156
10193
|
platform: input.platform,
|
|
10157
10194
|
scopes: input.scopes,
|
|
10158
10195
|
expiresAt: Date.now() + input.ttlSeconds * 1e3
|
|
10159
10196
|
});
|
|
10160
|
-
return { code:
|
|
10197
|
+
return { code: code3, expiresInSeconds: input.ttlSeconds };
|
|
10161
10198
|
}
|
|
10162
10199
|
/** Обменять код на токен. Код одноразовый. */
|
|
10163
|
-
complete(
|
|
10200
|
+
complete(code3, deviceName) {
|
|
10164
10201
|
this.sweep();
|
|
10165
|
-
const pending = this.pending.get(
|
|
10202
|
+
const pending = this.pending.get(code3);
|
|
10166
10203
|
if (!pending) return null;
|
|
10167
|
-
this.pending.delete(
|
|
10204
|
+
this.pending.delete(code3);
|
|
10168
10205
|
const token = generateToken();
|
|
10169
10206
|
const device = this.runtime.store.pairDevice({
|
|
10170
10207
|
name: deviceName || pending.name,
|
|
@@ -10177,20 +10214,20 @@ var PairingService = class {
|
|
|
10177
10214
|
}
|
|
10178
10215
|
sweep() {
|
|
10179
10216
|
const now = Date.now();
|
|
10180
|
-
for (const [
|
|
10181
|
-
if (pending.expiresAt < now) this.pending.delete(
|
|
10217
|
+
for (const [code3, pending] of this.pending) {
|
|
10218
|
+
if (pending.expiresAt < now) this.pending.delete(code3);
|
|
10182
10219
|
}
|
|
10183
10220
|
}
|
|
10184
10221
|
};
|
|
10185
10222
|
function generateCode() {
|
|
10186
10223
|
const alphabet = "ACDEFGHJKLMNPQRTUVWXY34679";
|
|
10187
10224
|
const bytes = crypto3.randomBytes(8);
|
|
10188
|
-
let
|
|
10225
|
+
let code3 = "";
|
|
10189
10226
|
for (let i = 0; i < 8; i++) {
|
|
10190
|
-
if (i === 4)
|
|
10191
|
-
|
|
10227
|
+
if (i === 4) code3 += "-";
|
|
10228
|
+
code3 += alphabet[bytes[i] % alphabet.length];
|
|
10192
10229
|
}
|
|
10193
|
-
return
|
|
10230
|
+
return code3;
|
|
10194
10231
|
}
|
|
10195
10232
|
function authenticate(runtime, token) {
|
|
10196
10233
|
if (!token) return null;
|
|
@@ -10247,9 +10284,9 @@ var POLL_TIMEOUT_SECONDS = 25;
|
|
|
10247
10284
|
var TelegramError = class extends Error {
|
|
10248
10285
|
code;
|
|
10249
10286
|
retryAfter;
|
|
10250
|
-
constructor(message,
|
|
10287
|
+
constructor(message, code3, retryAfter2) {
|
|
10251
10288
|
super(message);
|
|
10252
|
-
this.code =
|
|
10289
|
+
this.code = code3;
|
|
10253
10290
|
this.retryAfter = retryAfter2;
|
|
10254
10291
|
this.name = "TelegramError";
|
|
10255
10292
|
}
|
|
@@ -10384,8 +10421,8 @@ function toTelegramHtml(markdown) {
|
|
|
10384
10421
|
return out.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
10385
10422
|
}
|
|
10386
10423
|
function closeFence(body, language) {
|
|
10387
|
-
const
|
|
10388
|
-
return language ? `<pre><code class="language-${escape(language)}">${
|
|
10424
|
+
const code3 = escape(body.join("\n"));
|
|
10425
|
+
return language ? `<pre><code class="language-${escape(language)}">${code3}</code></pre>` : `<pre>${code3}</pre>`;
|
|
10389
10426
|
}
|
|
10390
10427
|
function block(line) {
|
|
10391
10428
|
const heading = /^\s{0,3}(#{1,6})\s+(.*)$/.exec(line);
|
|
@@ -10405,10 +10442,10 @@ function block(line) {
|
|
|
10405
10442
|
return inline(line);
|
|
10406
10443
|
}
|
|
10407
10444
|
function inline(text) {
|
|
10408
|
-
const
|
|
10445
|
+
const code3 = [];
|
|
10409
10446
|
let work = text.replace(/`([^`]+)`/g, (_, body) => {
|
|
10410
|
-
|
|
10411
|
-
return `\0${
|
|
10447
|
+
code3.push(`<code>${escape(body)}</code>`);
|
|
10448
|
+
return `\0${code3.length - 1}\0`;
|
|
10412
10449
|
});
|
|
10413
10450
|
work = escape(work);
|
|
10414
10451
|
work = work.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, label, href) => {
|
|
@@ -10419,7 +10456,7 @@ function inline(text) {
|
|
|
10419
10456
|
work = work.replace(/(^|[\s(])\*([^*\n]+)\*/g, "$1<i>$2</i>");
|
|
10420
10457
|
work = work.replace(/(^|[\s(])_([^_\n]+)_/g, "$1<i>$2</i>");
|
|
10421
10458
|
work = work.replace(/~~([^~]+)~~/g, "<s>$1</s>");
|
|
10422
|
-
return work.replace(/\u0000(\d+)\u0000/g, (_, index) =>
|
|
10459
|
+
return work.replace(/\u0000(\d+)\u0000/g, (_, index) => code3[Number(index)] ?? "");
|
|
10423
10460
|
}
|
|
10424
10461
|
function escape(text) {
|
|
10425
10462
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -10558,17 +10595,17 @@ var TelegramAdapter = class {
|
|
|
10558
10595
|
}
|
|
10559
10596
|
/** Привязка: `/start КОД` меняет код на устройство, как это делает десктоп. */
|
|
10560
10597
|
async onStart(message, text) {
|
|
10561
|
-
const
|
|
10598
|
+
const code3 = text.slice("/start".length).trim();
|
|
10562
10599
|
if (this.bindingOf(message.chat.id)) {
|
|
10563
10600
|
await this.api.sendMessage(message.chat.id, "\u0423\u0436\u0435 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438. \u041F\u0438\u0448\u0438\u0442\u0435.");
|
|
10564
10601
|
return;
|
|
10565
10602
|
}
|
|
10566
|
-
if (!
|
|
10603
|
+
if (!code3) {
|
|
10567
10604
|
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
10605
|
return;
|
|
10569
10606
|
}
|
|
10570
10607
|
const name = [message.from?.first_name, message.from?.last_name].filter(Boolean).join(" ");
|
|
10571
|
-
const paired = this.deps.pair(
|
|
10608
|
+
const paired = this.deps.pair(code3.toUpperCase(), `\u0422\u0435\u043B\u0435\u0433\u0440\u0430\u043C: ${name || message.chat.id}`);
|
|
10572
10609
|
if (!paired) {
|
|
10573
10610
|
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
10611
|
return;
|
|
@@ -11144,13 +11181,13 @@ var UserbotAuth = class {
|
|
|
11144
11181
|
* продолжение, и обращаться с ней как с отказом значит терять начатый вход
|
|
11145
11182
|
* там, где он идёт нормально.
|
|
11146
11183
|
*/
|
|
11147
|
-
async signIn(
|
|
11184
|
+
async signIn(code3) {
|
|
11148
11185
|
const pending = this.require();
|
|
11149
11186
|
try {
|
|
11150
11187
|
await pending.client.invoke(new Api2.auth.SignIn({
|
|
11151
11188
|
phoneNumber: pending.phone,
|
|
11152
11189
|
phoneCodeHash: pending.phoneCodeHash,
|
|
11153
|
-
phoneCode:
|
|
11190
|
+
phoneCode: code3.trim()
|
|
11154
11191
|
}));
|
|
11155
11192
|
} catch (error) {
|
|
11156
11193
|
if (!isPasswordNeeded(error))
|
|
@@ -11199,9 +11236,9 @@ function isPasswordNeeded(error) {
|
|
|
11199
11236
|
|
|
11200
11237
|
// src/commands.ts
|
|
11201
11238
|
var CommandError = class extends Error {
|
|
11202
|
-
constructor(
|
|
11239
|
+
constructor(code3, message) {
|
|
11203
11240
|
super(message);
|
|
11204
|
-
this.code =
|
|
11241
|
+
this.code = code3;
|
|
11205
11242
|
this.name = "CommandError";
|
|
11206
11243
|
}
|
|
11207
11244
|
};
|
|
@@ -11433,8 +11470,8 @@ var handlers = {
|
|
|
11433
11470
|
try {
|
|
11434
11471
|
return { plugin: await runtime.plugins.update(req.id) };
|
|
11435
11472
|
} catch (error) {
|
|
11436
|
-
const
|
|
11437
|
-
throw new CommandError(
|
|
11473
|
+
const code3 = error.code;
|
|
11474
|
+
throw new CommandError(code3 === "not_found" ? "not_found" : "bad_request", error.message);
|
|
11438
11475
|
}
|
|
11439
11476
|
},
|
|
11440
11477
|
"plugin.configure": async (req, { runtime }) => {
|
|
@@ -11555,8 +11592,8 @@ async function withPlugin(run2) {
|
|
|
11555
11592
|
try {
|
|
11556
11593
|
await run2();
|
|
11557
11594
|
} catch (error) {
|
|
11558
|
-
const
|
|
11559
|
-
throw new CommandError(
|
|
11595
|
+
const code3 = error.code;
|
|
11596
|
+
throw new CommandError(code3 === "not_found" ? "not_found" : "bad_request", error.message);
|
|
11560
11597
|
}
|
|
11561
11598
|
}
|
|
11562
11599
|
function requireConversation(runtime, id) {
|
|
@@ -11689,7 +11726,7 @@ var WsSession = class {
|
|
|
11689
11726
|
};
|
|
11690
11727
|
|
|
11691
11728
|
// src/Daemon.ts
|
|
11692
|
-
var DAEMON_VERSION = true ? "2026.8.
|
|
11729
|
+
var DAEMON_VERSION = true ? "2026.8.27" : "0.0.0-dev";
|
|
11693
11730
|
var MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
|
|
11694
11731
|
function isWildcard(host) {
|
|
11695
11732
|
return host === "0.0.0.0" || host === "::" || host === "";
|
|
@@ -11764,6 +11801,11 @@ var Daemon = class {
|
|
|
11764
11801
|
* ответ, и мог бы показать его только целиком в самом конце.
|
|
11765
11802
|
*/
|
|
11766
11803
|
signalListeners = /* @__PURE__ */ new Set();
|
|
11804
|
+
/**
|
|
11805
|
+
* Пропуск для того, кто сидит за этой машиной: им подписан запрос на новый
|
|
11806
|
+
* код подключения. Живёт в файле рядом с базой, с правами 0600.
|
|
11807
|
+
*/
|
|
11808
|
+
controlToken = crypto4.randomBytes(24).toString("hex");
|
|
11767
11809
|
/** Разрешается, когда все установленные плагины отработали запуск. */
|
|
11768
11810
|
pluginsReady = Promise.resolve();
|
|
11769
11811
|
async start() {
|
|
@@ -11785,6 +11827,7 @@ var Daemon = class {
|
|
|
11785
11827
|
reachable: isWildcard(host) ? reachableUrls(boundPort) : []
|
|
11786
11828
|
};
|
|
11787
11829
|
const bootstrapCode = this.pairing.ensureBootstrapCode();
|
|
11830
|
+
this.writeControlToken();
|
|
11788
11831
|
this.announce();
|
|
11789
11832
|
logger.info({ url: this.address.url, coreId: this.runtime.coreId }, "\u0434\u0435\u043C\u043E\u043D \u0441\u043B\u0443\u0448\u0430\u0435\u0442");
|
|
11790
11833
|
this.pluginsReady = this.runtime.startPlugins().catch((error) => {
|
|
@@ -11818,7 +11861,7 @@ var Daemon = class {
|
|
|
11818
11861
|
const adapter = new TelegramAdapter(
|
|
11819
11862
|
{
|
|
11820
11863
|
runtime: this.runtime,
|
|
11821
|
-
pair: (
|
|
11864
|
+
pair: (code3, name) => this.pairing.complete(code3, name),
|
|
11822
11865
|
resolvePermission: (requestId, allow) => void this.permissions.resolve(requestId, allow ? "allow_once" : "deny_once"),
|
|
11823
11866
|
onSignal: (listener) => {
|
|
11824
11867
|
this.signalListeners.add(listener);
|
|
@@ -11903,6 +11946,26 @@ var Daemon = class {
|
|
|
11903
11946
|
* же машине мог его найти, не завися от того, кто его поднял, адрес и pid
|
|
11904
11947
|
* кладутся в файл рядом с базой.
|
|
11905
11948
|
*/
|
|
11949
|
+
controlPath() {
|
|
11950
|
+
return path18.join(this.runtime.config.dataDir, "control.token");
|
|
11951
|
+
}
|
|
11952
|
+
/**
|
|
11953
|
+
* Пропуск переписывается на каждом запуске: старый файл, оставшийся от
|
|
11954
|
+
* убитого процесса, не должен открывать двери в новое ядро.
|
|
11955
|
+
*/
|
|
11956
|
+
writeControlToken() {
|
|
11957
|
+
fs18.mkdirSync(this.runtime.config.dataDir, { recursive: true });
|
|
11958
|
+
fs18.writeFileSync(this.controlPath(), `${this.controlToken}
|
|
11959
|
+
`, { mode: 384 });
|
|
11960
|
+
}
|
|
11961
|
+
/** Сравнение постоянного времени: пропуск подбирают по одному символу. */
|
|
11962
|
+
controlAllowed(given) {
|
|
11963
|
+
const value = Array.isArray(given) ? given[0] : given;
|
|
11964
|
+
if (!value) return false;
|
|
11965
|
+
const a = Buffer.from(value.trim());
|
|
11966
|
+
const b = Buffer.from(this.controlToken);
|
|
11967
|
+
return a.length === b.length && crypto4.timingSafeEqual(a, b);
|
|
11968
|
+
}
|
|
11906
11969
|
announce() {
|
|
11907
11970
|
const file = path18.join(this.runtime.config.dataDir, "core.json");
|
|
11908
11971
|
fs18.writeFileSync(
|
|
@@ -11952,6 +12015,17 @@ var Daemon = class {
|
|
|
11952
12015
|
// ─── HTTP ─────────────────────────────────────────────────────────────────
|
|
11953
12016
|
async onRequest(req, res) {
|
|
11954
12017
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
12018
|
+
if (url.pathname.startsWith("/v1/blobs")) {
|
|
12019
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
12020
|
+
res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
|
|
12021
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
12022
|
+
res.setHeader("Access-Control-Max-Age", "86400");
|
|
12023
|
+
if (req.method === "OPTIONS") {
|
|
12024
|
+
res.writeHead(204);
|
|
12025
|
+
res.end();
|
|
12026
|
+
return;
|
|
12027
|
+
}
|
|
12028
|
+
}
|
|
11955
12029
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
11956
12030
|
return respond(res, 200, {
|
|
11957
12031
|
ok: true,
|
|
@@ -11962,11 +12036,34 @@ var Daemon = class {
|
|
|
11962
12036
|
pendingPermissions: this.permissions.pendingCount
|
|
11963
12037
|
});
|
|
11964
12038
|
}
|
|
12039
|
+
if (req.method === "POST" && url.pathname === "/v1/control/pair") {
|
|
12040
|
+
if (!this.controlAllowed(req.headers["x-axon-control"])) {
|
|
12041
|
+
return respond(res, 403, { error: "forbidden" });
|
|
12042
|
+
}
|
|
12043
|
+
const body = await readJson(req);
|
|
12044
|
+
const chatOnly = body?.["scopes"] === "chat";
|
|
12045
|
+
const ttl = Number(body?.["ttlSeconds"] ?? 300);
|
|
12046
|
+
const issued = this.pairing.begin({
|
|
12047
|
+
name: typeof body?.["name"] === "string" ? body["name"] : "\u041D\u043E\u0432\u043E\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
|
|
12048
|
+
platform: "desktop",
|
|
12049
|
+
scopes: chatOnly ? ["chat.read", "chat.write", "tools.safe"] : [
|
|
12050
|
+
"chat.read",
|
|
12051
|
+
"chat.write",
|
|
12052
|
+
"tools.safe",
|
|
12053
|
+
"tools.sensitive",
|
|
12054
|
+
"tools.dangerous",
|
|
12055
|
+
"settings.write",
|
|
12056
|
+
"devices.manage"
|
|
12057
|
+
],
|
|
12058
|
+
ttlSeconds: Number.isFinite(ttl) && ttl > 0 ? Math.min(ttl, 3600) : 300
|
|
12059
|
+
});
|
|
12060
|
+
return respond(res, 200, issued);
|
|
12061
|
+
}
|
|
11965
12062
|
if (req.method === "POST" && url.pathname === "/v1/pair") {
|
|
11966
12063
|
const body = await readJson(req);
|
|
11967
|
-
const
|
|
12064
|
+
const code3 = typeof body?.["code"] === "string" ? body["code"] : "";
|
|
11968
12065
|
const name = typeof body?.["name"] === "string" ? body["name"] : void 0;
|
|
11969
|
-
const paired = this.pairing.complete(
|
|
12066
|
+
const paired = this.pairing.complete(code3.trim().toUpperCase(), name);
|
|
11970
12067
|
if (!paired) return respond(res, 403, { error: "invalid_code" });
|
|
11971
12068
|
return respond(res, 200, {
|
|
11972
12069
|
token: paired.token,
|
|
@@ -12093,6 +12190,8 @@ async function main() {
|
|
|
12093
12190
|
return await restore(args);
|
|
12094
12191
|
case "status":
|
|
12095
12192
|
return await status();
|
|
12193
|
+
case "code":
|
|
12194
|
+
return await code2(args);
|
|
12096
12195
|
case "stop":
|
|
12097
12196
|
return stop();
|
|
12098
12197
|
case "help":
|
|
@@ -12129,11 +12228,11 @@ async function start(args) {
|
|
|
12129
12228
|
try {
|
|
12130
12229
|
started = await daemon.start();
|
|
12131
12230
|
} catch (error) {
|
|
12132
|
-
const
|
|
12133
|
-
if (
|
|
12231
|
+
const code3 = error.code;
|
|
12232
|
+
if (code3 === "EADDRINUSE") {
|
|
12134
12233
|
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
12234
|
}
|
|
12136
|
-
if (
|
|
12235
|
+
if (code3 === "EADDRNOTAVAIL") {
|
|
12137
12236
|
return fail(
|
|
12138
12237
|
`\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
12238
|
\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 +12263,37 @@ async function start(args) {
|
|
|
12164
12263
|
process.on("SIGINT", stop2);
|
|
12165
12264
|
process.on("SIGTERM", stop2);
|
|
12166
12265
|
}
|
|
12266
|
+
async function code2(args) {
|
|
12267
|
+
const record = readRecord();
|
|
12268
|
+
if (!record || !await alive(record.url)) {
|
|
12269
|
+
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");
|
|
12270
|
+
}
|
|
12271
|
+
const file = path19.join(resolveConfig().dataDir, "control.token");
|
|
12272
|
+
let token;
|
|
12273
|
+
try {
|
|
12274
|
+
token = fs19.readFileSync(file, "utf8").trim();
|
|
12275
|
+
} catch {
|
|
12276
|
+
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`);
|
|
12277
|
+
}
|
|
12278
|
+
const chatOnly = args.includes("--chat");
|
|
12279
|
+
const ttl = Number(valueOf(args, "--ttl") ?? 300);
|
|
12280
|
+
const response = await fetch(`${record.url}/v1/control/pair`, {
|
|
12281
|
+
method: "POST",
|
|
12282
|
+
headers: { "Content-Type": "application/json", "x-axon-control": token },
|
|
12283
|
+
body: JSON.stringify({ scopes: chatOnly ? "chat" : "full", ttlSeconds: ttl })
|
|
12284
|
+
}).catch(() => null);
|
|
12285
|
+
if (!response?.ok) {
|
|
12286
|
+
return fail(
|
|
12287
|
+
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"
|
|
12288
|
+
);
|
|
12289
|
+
}
|
|
12290
|
+
const issued = await response.json();
|
|
12291
|
+
console.log(issued.code);
|
|
12292
|
+
console.log("");
|
|
12293
|
+
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`);
|
|
12294
|
+
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"}`);
|
|
12295
|
+
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}`);
|
|
12296
|
+
}
|
|
12167
12297
|
function logPath() {
|
|
12168
12298
|
return path19.join(resolveConfig().dataDir, "axon.log");
|
|
12169
12299
|
}
|
|
@@ -12190,11 +12320,11 @@ async function detach(args) {
|
|
|
12190
12320
|
console.log(` \u0432\u044B\u0432\u043E\u0434: ${file}`);
|
|
12191
12321
|
console.log(" \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C: axon stop");
|
|
12192
12322
|
const codeFile = path19.join(config.dataDir, "bootstrap.code");
|
|
12193
|
-
const
|
|
12194
|
-
if (
|
|
12323
|
+
const code3 = fs19.existsSync(codeFile) ? fs19.readFileSync(codeFile, "utf8").trim() : "";
|
|
12324
|
+
if (code3) {
|
|
12195
12325
|
console.log("");
|
|
12196
12326
|
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(` ${
|
|
12327
|
+
console.log(` ${code3}`);
|
|
12198
12328
|
console.log("");
|
|
12199
12329
|
}
|
|
12200
12330
|
}
|
|
@@ -12460,6 +12590,7 @@ function usage() {
|
|
|
12460
12590
|
--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
12591
|
--foreground, -f \u043D\u0435 \u0443\u0445\u043E\u0434\u0438\u0442\u044C \u0432 \u0444\u043E\u043D (systemd, docker)
|
|
12462
12592
|
axon status \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E \u043B\u0438 \u044F\u0434\u0440\u043E \u0438 \u0433\u0434\u0435
|
|
12593
|
+
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
12594
|
axon stop \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u044F\u0434\u0440\u043E
|
|
12464
12595
|
axon secret list \u043A\u0430\u043A\u0438\u0435 \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0437\u0430\u0434\u0430\u043D\u044B
|
|
12465
12596
|
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";
|
|
@@ -680,10 +681,33 @@ var SecretStore = class {
|
|
|
680
681
|
const row = this.db.prepare(`SELECT * FROM secrets WHERE key = ?`).get(key);
|
|
681
682
|
if (!row)
|
|
682
683
|
return null;
|
|
684
|
+
return this.decrypt(row);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Расшифровать строку. Бросает, если значение не сходится с ключом.
|
|
688
|
+
*
|
|
689
|
+
* GCM проверяет подпись, поэтому чужой ключ шифрования — это не мусор на
|
|
690
|
+
* выходе, а честный отказ. Ловить его здесь нельзя: молча вернуть `null`
|
|
691
|
+
* значит превратить «ключ не читается» в «ключа нет», а лечатся они
|
|
692
|
+
* по-разному.
|
|
693
|
+
*/
|
|
694
|
+
decrypt(row) {
|
|
683
695
|
const decipher = crypto.createDecipheriv(ALGORITHM, this.masterKey(), row.iv);
|
|
684
696
|
decipher.setAuthTag(row.tag);
|
|
685
697
|
return Buffer.concat([decipher.update(row.ciphertext), decipher.final()]).toString("utf8");
|
|
686
698
|
}
|
|
699
|
+
/** Читается ли значение здешним ключом шифрования. */
|
|
700
|
+
readable(key) {
|
|
701
|
+
const row = this.db.prepare(`SELECT * FROM secrets WHERE key = ?`).get(key);
|
|
702
|
+
if (!row)
|
|
703
|
+
return false;
|
|
704
|
+
try {
|
|
705
|
+
this.decrypt(row);
|
|
706
|
+
return true;
|
|
707
|
+
} catch {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
687
711
|
delete(key) {
|
|
688
712
|
this.db.prepare(`DELETE FROM secrets WHERE key = ?`).run(key);
|
|
689
713
|
}
|
|
@@ -704,7 +728,12 @@ var SecretStore = class {
|
|
|
704
728
|
key,
|
|
705
729
|
set: true,
|
|
706
730
|
...row.hint ? { hint: row.hint } : {},
|
|
707
|
-
updatedAt: row.updated_at
|
|
731
|
+
updatedAt: row.updated_at,
|
|
732
|
+
// Наличие строки в базе — ещё не рабочий секрет: база могла приехать
|
|
733
|
+
// с другой машины, а ключ шифрования остаться там. «Ключ на месте» в
|
|
734
|
+
// таком случае врало, и человек искал причину в чём угодно, кроме
|
|
735
|
+
// переноса.
|
|
736
|
+
...this.readable(key) ? {} : { unreadable: true }
|
|
708
737
|
};
|
|
709
738
|
});
|
|
710
739
|
}
|
|
@@ -1724,7 +1753,7 @@ import fs14 from "node:fs";
|
|
|
1724
1753
|
// ../protocol/dist/primitives.js
|
|
1725
1754
|
import { z } from "zod";
|
|
1726
1755
|
var PROTOCOL_VERSION = 1;
|
|
1727
|
-
var PROTOCOL_REVISION =
|
|
1756
|
+
var PROTOCOL_REVISION = 11;
|
|
1728
1757
|
var zTimestamp = z.string().datetime();
|
|
1729
1758
|
var zId = z.string().uuid();
|
|
1730
1759
|
var zExternalId = z.string().min(1).max(256);
|
|
@@ -2698,7 +2727,15 @@ var zSecretStatus = z10.object({
|
|
|
2698
2727
|
set: z10.boolean(),
|
|
2699
2728
|
/** Последние символы значения. Присутствует, только если секрет задан. */
|
|
2700
2729
|
hint: z10.string().max(8).optional(),
|
|
2701
|
-
updatedAt: zTimestamp.optional()
|
|
2730
|
+
updatedAt: zTimestamp.optional(),
|
|
2731
|
+
/**
|
|
2732
|
+
* Значение лежит, но расшифровать его нечем.
|
|
2733
|
+
*
|
|
2734
|
+
* Так бывает после переноса: база приехала, а ключ шифрования рядом с ней —
|
|
2735
|
+
* нет (`axon backup` не кладёт его в архив без спроса). Снаружи это выглядело
|
|
2736
|
+
* как «ключ на месте», хотя ни один запрос с ним не проходит.
|
|
2737
|
+
*/
|
|
2738
|
+
unreadable: z10.boolean().optional()
|
|
2702
2739
|
});
|
|
2703
2740
|
var zSettingsGetReq = z10.object({
|
|
2704
2741
|
/** Какие ключи вернуть. Пусто — все, к которым у устройства есть доступ. */
|
|
@@ -11352,7 +11389,7 @@ var WsSession = class {
|
|
|
11352
11389
|
};
|
|
11353
11390
|
|
|
11354
11391
|
// src/Daemon.ts
|
|
11355
|
-
var DAEMON_VERSION = true ? "2026.8.
|
|
11392
|
+
var DAEMON_VERSION = true ? "2026.8.27" : "0.0.0-dev";
|
|
11356
11393
|
var MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
|
|
11357
11394
|
function isWildcard(host) {
|
|
11358
11395
|
return host === "0.0.0.0" || host === "::" || host === "";
|
|
@@ -11427,6 +11464,11 @@ var Daemon = class {
|
|
|
11427
11464
|
* ответ, и мог бы показать его только целиком в самом конце.
|
|
11428
11465
|
*/
|
|
11429
11466
|
signalListeners = /* @__PURE__ */ new Set();
|
|
11467
|
+
/**
|
|
11468
|
+
* Пропуск для того, кто сидит за этой машиной: им подписан запрос на новый
|
|
11469
|
+
* код подключения. Живёт в файле рядом с базой, с правами 0600.
|
|
11470
|
+
*/
|
|
11471
|
+
controlToken = crypto4.randomBytes(24).toString("hex");
|
|
11430
11472
|
/** Разрешается, когда все установленные плагины отработали запуск. */
|
|
11431
11473
|
pluginsReady = Promise.resolve();
|
|
11432
11474
|
async start() {
|
|
@@ -11448,6 +11490,7 @@ var Daemon = class {
|
|
|
11448
11490
|
reachable: isWildcard(host) ? reachableUrls(boundPort) : []
|
|
11449
11491
|
};
|
|
11450
11492
|
const bootstrapCode = this.pairing.ensureBootstrapCode();
|
|
11493
|
+
this.writeControlToken();
|
|
11451
11494
|
this.announce();
|
|
11452
11495
|
logger.info({ url: this.address.url, coreId: this.runtime.coreId }, "\u0434\u0435\u043C\u043E\u043D \u0441\u043B\u0443\u0448\u0430\u0435\u0442");
|
|
11453
11496
|
this.pluginsReady = this.runtime.startPlugins().catch((error) => {
|
|
@@ -11566,6 +11609,26 @@ var Daemon = class {
|
|
|
11566
11609
|
* же машине мог его найти, не завися от того, кто его поднял, адрес и pid
|
|
11567
11610
|
* кладутся в файл рядом с базой.
|
|
11568
11611
|
*/
|
|
11612
|
+
controlPath() {
|
|
11613
|
+
return path16.join(this.runtime.config.dataDir, "control.token");
|
|
11614
|
+
}
|
|
11615
|
+
/**
|
|
11616
|
+
* Пропуск переписывается на каждом запуске: старый файл, оставшийся от
|
|
11617
|
+
* убитого процесса, не должен открывать двери в новое ядро.
|
|
11618
|
+
*/
|
|
11619
|
+
writeControlToken() {
|
|
11620
|
+
fs16.mkdirSync(this.runtime.config.dataDir, { recursive: true });
|
|
11621
|
+
fs16.writeFileSync(this.controlPath(), `${this.controlToken}
|
|
11622
|
+
`, { mode: 384 });
|
|
11623
|
+
}
|
|
11624
|
+
/** Сравнение постоянного времени: пропуск подбирают по одному символу. */
|
|
11625
|
+
controlAllowed(given) {
|
|
11626
|
+
const value = Array.isArray(given) ? given[0] : given;
|
|
11627
|
+
if (!value) return false;
|
|
11628
|
+
const a = Buffer.from(value.trim());
|
|
11629
|
+
const b = Buffer.from(this.controlToken);
|
|
11630
|
+
return a.length === b.length && crypto4.timingSafeEqual(a, b);
|
|
11631
|
+
}
|
|
11569
11632
|
announce() {
|
|
11570
11633
|
const file = path16.join(this.runtime.config.dataDir, "core.json");
|
|
11571
11634
|
fs16.writeFileSync(
|
|
@@ -11615,6 +11678,17 @@ var Daemon = class {
|
|
|
11615
11678
|
// ─── HTTP ─────────────────────────────────────────────────────────────────
|
|
11616
11679
|
async onRequest(req, res) {
|
|
11617
11680
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
11681
|
+
if (url.pathname.startsWith("/v1/blobs")) {
|
|
11682
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
11683
|
+
res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
|
|
11684
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
11685
|
+
res.setHeader("Access-Control-Max-Age", "86400");
|
|
11686
|
+
if (req.method === "OPTIONS") {
|
|
11687
|
+
res.writeHead(204);
|
|
11688
|
+
res.end();
|
|
11689
|
+
return;
|
|
11690
|
+
}
|
|
11691
|
+
}
|
|
11618
11692
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
11619
11693
|
return respond(res, 200, {
|
|
11620
11694
|
ok: true,
|
|
@@ -11625,6 +11699,29 @@ var Daemon = class {
|
|
|
11625
11699
|
pendingPermissions: this.permissions.pendingCount
|
|
11626
11700
|
});
|
|
11627
11701
|
}
|
|
11702
|
+
if (req.method === "POST" && url.pathname === "/v1/control/pair") {
|
|
11703
|
+
if (!this.controlAllowed(req.headers["x-axon-control"])) {
|
|
11704
|
+
return respond(res, 403, { error: "forbidden" });
|
|
11705
|
+
}
|
|
11706
|
+
const body = await readJson(req);
|
|
11707
|
+
const chatOnly = body?.["scopes"] === "chat";
|
|
11708
|
+
const ttl = Number(body?.["ttlSeconds"] ?? 300);
|
|
11709
|
+
const issued = this.pairing.begin({
|
|
11710
|
+
name: typeof body?.["name"] === "string" ? body["name"] : "\u041D\u043E\u0432\u043E\u0435 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E",
|
|
11711
|
+
platform: "desktop",
|
|
11712
|
+
scopes: chatOnly ? ["chat.read", "chat.write", "tools.safe"] : [
|
|
11713
|
+
"chat.read",
|
|
11714
|
+
"chat.write",
|
|
11715
|
+
"tools.safe",
|
|
11716
|
+
"tools.sensitive",
|
|
11717
|
+
"tools.dangerous",
|
|
11718
|
+
"settings.write",
|
|
11719
|
+
"devices.manage"
|
|
11720
|
+
],
|
|
11721
|
+
ttlSeconds: Number.isFinite(ttl) && ttl > 0 ? Math.min(ttl, 3600) : 300
|
|
11722
|
+
});
|
|
11723
|
+
return respond(res, 200, issued);
|
|
11724
|
+
}
|
|
11628
11725
|
if (req.method === "POST" && url.pathname === "/v1/pair") {
|
|
11629
11726
|
const body = await readJson(req);
|
|
11630
11727
|
const code = typeof body?.["code"] === "string" ? body["code"] : "";
|
package/dist/plugin-host.js
CHANGED
|
@@ -986,7 +986,15 @@ var zSecretStatus = z10.object({
|
|
|
986
986
|
set: z10.boolean(),
|
|
987
987
|
/** Последние символы значения. Присутствует, только если секрет задан. */
|
|
988
988
|
hint: z10.string().max(8).optional(),
|
|
989
|
-
updatedAt: zTimestamp.optional()
|
|
989
|
+
updatedAt: zTimestamp.optional(),
|
|
990
|
+
/**
|
|
991
|
+
* Значение лежит, но расшифровать его нечем.
|
|
992
|
+
*
|
|
993
|
+
* Так бывает после переноса: база приехала, а ключ шифрования рядом с ней —
|
|
994
|
+
* нет (`axon backup` не кладёт его в архив без спроса). Снаружи это выглядело
|
|
995
|
+
* как «ключ на месте», хотя ни один запрос с ним не проходит.
|
|
996
|
+
*/
|
|
997
|
+
unreadable: z10.boolean().optional()
|
|
990
998
|
});
|
|
991
999
|
var zSettingsGetReq = z10.object({
|
|
992
1000
|
/** Какие ключи вернуть. Пусто — все, к которым у устройства есть доступ. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axon-assistant/core",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.27",
|
|
4
4
|
"comment:version": "Заглушка. Настоящий номер проставляется из git-тега при публикации: см. scripts/release-npm.mjs. Версия, которую правят руками, рано или поздно расходится с тем, что опубликовано.",
|
|
5
5
|
"description": "Ядро Axon: персональный AI-агент, который живёт на вашей машине",
|
|
6
6
|
"keywords": [
|