@axon-assistant/core 2026.8.26 → 2026.8.28

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.
@@ -1 +1 @@
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"}
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;AAMtD,OAAO,EAML,OAAO,EAER,MAAM,gBAAgB,CAAC;AAKxB,eAAO,MAAM,cAAc,QAC4C,CAAC;AAOxE,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;IAmIvB,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,SAAS;CA+BlB"}
package/dist/auth.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare class PairingService {
38
38
  expiresInSeconds: number;
39
39
  };
40
40
  /** Обменять код на токен. Код одноразовый. */
41
- complete(code: string, deviceName?: string): {
41
+ complete(code: string, deviceName?: string, platform?: DevicePlatform): {
42
42
  device: Device;
43
43
  token: string;
44
44
  } | null;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAJhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;gBAG1C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,MAAM;IAGxC,qEAAqE;IACrE,mBAAmB,IAAI,MAAM,GAAG,IAAI;IA8BpC,KAAK,CAAC,KAAK,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,cAAc,CAAC;QACzB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE;IAW9C,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAoBrF,OAAO,CAAC,KAAK;CAMd;AAcD,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAGlF"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAJhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;gBAG1C,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,MAAM;IAGxC,qEAAqE;IACrE,mBAAmB,IAAI,MAAM,GAAG,IAAI;IA8BpC,KAAK,CAAC,KAAK,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,cAAc,CAAC;QACzB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE;IAW9C,8CAA8C;IAC9C,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,cAAc,GACxB;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAoB3C,OAAO,CAAC,KAAK;CAMd;AAcD,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAGlF"}
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 = 10;
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
  /** Какие ключи вернуть. Пусто — все, к которым у устройства есть доступ. */
@@ -10161,7 +10197,7 @@ var PairingService = class {
10161
10197
  return { code: code3, expiresInSeconds: input.ttlSeconds };
10162
10198
  }
10163
10199
  /** Обменять код на токен. Код одноразовый. */
10164
- complete(code3, deviceName) {
10200
+ complete(code3, deviceName, platform) {
10165
10201
  this.sweep();
10166
10202
  const pending = this.pending.get(code3);
10167
10203
  if (!pending) return null;
@@ -10169,7 +10205,7 @@ var PairingService = class {
10169
10205
  const token = generateToken();
10170
10206
  const device = this.runtime.store.pairDevice({
10171
10207
  name: deviceName || pending.name,
10172
- platform: pending.platform,
10208
+ platform: platform ?? pending.platform,
10173
10209
  scopes: pending.scopes,
10174
10210
  tokenHash: hashToken(token)
10175
10211
  });
@@ -11690,7 +11726,8 @@ var WsSession = class {
11690
11726
  };
11691
11727
 
11692
11728
  // src/Daemon.ts
11693
- var DAEMON_VERSION = true ? "2026.8.26" : "0.0.0-dev";
11729
+ var DAEMON_VERSION = true ? "2026.8.28" : "0.0.0-dev";
11730
+ var CORS_PATHS = /* @__PURE__ */ new Set(["/health", "/v1/pair"]);
11694
11731
  var MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
11695
11732
  function isWildcard(host) {
11696
11733
  return host === "0.0.0.0" || host === "::" || host === "";
@@ -11979,6 +12016,17 @@ var Daemon = class {
11979
12016
  // ─── HTTP ─────────────────────────────────────────────────────────────────
11980
12017
  async onRequest(req, res) {
11981
12018
  const url = new URL(req.url ?? "/", "http://localhost");
12019
+ if (url.pathname.startsWith("/v1/blobs") || CORS_PATHS.has(url.pathname)) {
12020
+ res.setHeader("Access-Control-Allow-Origin", "*");
12021
+ res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
12022
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
12023
+ res.setHeader("Access-Control-Max-Age", "86400");
12024
+ if (req.method === "OPTIONS") {
12025
+ res.writeHead(204);
12026
+ res.end();
12027
+ return;
12028
+ }
12029
+ }
11982
12030
  if (req.method === "GET" && url.pathname === "/health") {
11983
12031
  return respond(res, 200, {
11984
12032
  ok: true,
@@ -12016,7 +12064,12 @@ var Daemon = class {
12016
12064
  const body = await readJson(req);
12017
12065
  const code3 = typeof body?.["code"] === "string" ? body["code"] : "";
12018
12066
  const name = typeof body?.["name"] === "string" ? body["name"] : void 0;
12019
- const paired = this.pairing.complete(code3.trim().toUpperCase(), name);
12067
+ const platform = zDevicePlatform.safeParse(body?.["platform"]);
12068
+ const paired = this.pairing.complete(
12069
+ code3.trim().toUpperCase(),
12070
+ name,
12071
+ platform.success ? platform.data : void 0
12072
+ );
12020
12073
  if (!paired) return respond(res, 403, { error: "invalid_code" });
12021
12074
  return respond(res, 200, {
12022
12075
  token: paired.token,
package/dist/index.js CHANGED
@@ -681,10 +681,33 @@ var SecretStore = class {
681
681
  const row = this.db.prepare(`SELECT * FROM secrets WHERE key = ?`).get(key);
682
682
  if (!row)
683
683
  return null;
684
+ return this.decrypt(row);
685
+ }
686
+ /**
687
+ * Расшифровать строку. Бросает, если значение не сходится с ключом.
688
+ *
689
+ * GCM проверяет подпись, поэтому чужой ключ шифрования — это не мусор на
690
+ * выходе, а честный отказ. Ловить его здесь нельзя: молча вернуть `null`
691
+ * значит превратить «ключ не читается» в «ключа нет», а лечатся они
692
+ * по-разному.
693
+ */
694
+ decrypt(row) {
684
695
  const decipher = crypto.createDecipheriv(ALGORITHM, this.masterKey(), row.iv);
685
696
  decipher.setAuthTag(row.tag);
686
697
  return Buffer.concat([decipher.update(row.ciphertext), decipher.final()]).toString("utf8");
687
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
+ }
688
711
  delete(key) {
689
712
  this.db.prepare(`DELETE FROM secrets WHERE key = ?`).run(key);
690
713
  }
@@ -705,7 +728,12 @@ var SecretStore = class {
705
728
  key,
706
729
  set: true,
707
730
  ...row.hint ? { hint: row.hint } : {},
708
- updatedAt: row.updated_at
731
+ updatedAt: row.updated_at,
732
+ // Наличие строки в базе — ещё не рабочий секрет: база могла приехать
733
+ // с другой машины, а ключ шифрования остаться там. «Ключ на месте» в
734
+ // таком случае врало, и человек искал причину в чём угодно, кроме
735
+ // переноса.
736
+ ...this.readable(key) ? {} : { unreadable: true }
709
737
  };
710
738
  });
711
739
  }
@@ -1725,7 +1753,7 @@ import fs14 from "node:fs";
1725
1753
  // ../protocol/dist/primitives.js
1726
1754
  import { z } from "zod";
1727
1755
  var PROTOCOL_VERSION = 1;
1728
- var PROTOCOL_REVISION = 10;
1756
+ var PROTOCOL_REVISION = 11;
1729
1757
  var zTimestamp = z.string().datetime();
1730
1758
  var zId = z.string().uuid();
1731
1759
  var zExternalId = z.string().min(1).max(256);
@@ -2699,7 +2727,15 @@ var zSecretStatus = z10.object({
2699
2727
  set: z10.boolean(),
2700
2728
  /** Последние символы значения. Присутствует, только если секрет задан. */
2701
2729
  hint: z10.string().max(8).optional(),
2702
- updatedAt: zTimestamp.optional()
2730
+ updatedAt: zTimestamp.optional(),
2731
+ /**
2732
+ * Значение лежит, но расшифровать его нечем.
2733
+ *
2734
+ * Так бывает после переноса: база приехала, а ключ шифрования рядом с ней —
2735
+ * нет (`axon backup` не кладёт его в архив без спроса). Снаружи это выглядело
2736
+ * как «ключ на месте», хотя ни один запрос с ним не проходит.
2737
+ */
2738
+ unreadable: z10.boolean().optional()
2703
2739
  });
2704
2740
  var zSettingsGetReq = z10.object({
2705
2741
  /** Какие ключи вернуть. Пусто — все, к которым у устройства есть доступ. */
@@ -9824,7 +9860,7 @@ var PairingService = class {
9824
9860
  return { code, expiresInSeconds: input.ttlSeconds };
9825
9861
  }
9826
9862
  /** Обменять код на токен. Код одноразовый. */
9827
- complete(code, deviceName) {
9863
+ complete(code, deviceName, platform) {
9828
9864
  this.sweep();
9829
9865
  const pending = this.pending.get(code);
9830
9866
  if (!pending) return null;
@@ -9832,7 +9868,7 @@ var PairingService = class {
9832
9868
  const token = generateToken();
9833
9869
  const device = this.runtime.store.pairDevice({
9834
9870
  name: deviceName || pending.name,
9835
- platform: pending.platform,
9871
+ platform: platform ?? pending.platform,
9836
9872
  scopes: pending.scopes,
9837
9873
  tokenHash: hashToken(token)
9838
9874
  });
@@ -11353,7 +11389,8 @@ var WsSession = class {
11353
11389
  };
11354
11390
 
11355
11391
  // src/Daemon.ts
11356
- var DAEMON_VERSION = true ? "2026.8.26" : "0.0.0-dev";
11392
+ var DAEMON_VERSION = true ? "2026.8.28" : "0.0.0-dev";
11393
+ var CORS_PATHS = /* @__PURE__ */ new Set(["/health", "/v1/pair"]);
11357
11394
  var MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
11358
11395
  function isWildcard(host) {
11359
11396
  return host === "0.0.0.0" || host === "::" || host === "";
@@ -11642,6 +11679,17 @@ var Daemon = class {
11642
11679
  // ─── HTTP ─────────────────────────────────────────────────────────────────
11643
11680
  async onRequest(req, res) {
11644
11681
  const url = new URL(req.url ?? "/", "http://localhost");
11682
+ if (url.pathname.startsWith("/v1/blobs") || CORS_PATHS.has(url.pathname)) {
11683
+ res.setHeader("Access-Control-Allow-Origin", "*");
11684
+ res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
11685
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
11686
+ res.setHeader("Access-Control-Max-Age", "86400");
11687
+ if (req.method === "OPTIONS") {
11688
+ res.writeHead(204);
11689
+ res.end();
11690
+ return;
11691
+ }
11692
+ }
11645
11693
  if (req.method === "GET" && url.pathname === "/health") {
11646
11694
  return respond(res, 200, {
11647
11695
  ok: true,
@@ -11679,7 +11727,12 @@ var Daemon = class {
11679
11727
  const body = await readJson(req);
11680
11728
  const code = typeof body?.["code"] === "string" ? body["code"] : "";
11681
11729
  const name = typeof body?.["name"] === "string" ? body["name"] : void 0;
11682
- const paired = this.pairing.complete(code.trim().toUpperCase(), name);
11730
+ const platform = zDevicePlatform.safeParse(body?.["platform"]);
11731
+ const paired = this.pairing.complete(
11732
+ code.trim().toUpperCase(),
11733
+ name,
11734
+ platform.success ? platform.data : void 0
11735
+ );
11683
11736
  if (!paired) return respond(res, 403, { error: "invalid_code" });
11684
11737
  return respond(res, 200, {
11685
11738
  token: paired.token,
@@ -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.26",
3
+ "version": "2026.8.28",
4
4
  "comment:version": "Заглушка. Настоящий номер проставляется из git-тега при публикации: см. scripts/release-npm.mjs. Версия, которую правят руками, рано или поздно расходится с тем, что опубликовано.",
5
5
  "description": "Ядро Axon: персональный AI-агент, который живёт на вашей машине",
6
6
  "keywords": [