@oneentry/mcp-platform-server 0.1.1 → 0.1.3

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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -61
  3. package/dist/api/audit.d.ts +0 -11
  4. package/dist/api/audit.js +0 -10
  5. package/dist/api/auth.d.ts +0 -18
  6. package/dist/api/auth.js +0 -20
  7. package/dist/api/build-catalog.d.ts +0 -14
  8. package/dist/api/build-catalog.js +7 -77
  9. package/dist/api/catalog.d.ts +0 -26
  10. package/dist/api/catalog.js +3 -30
  11. package/dist/api/client.d.ts +0 -24
  12. package/dist/api/client.js +6 -28
  13. package/dist/api/normalize-schema.d.ts +0 -11
  14. package/dist/api/normalize-schema.js +0 -18
  15. package/dist/api/policy.d.ts +0 -23
  16. package/dist/api/policy.js +0 -0
  17. package/dist/api/shape.d.ts +0 -13
  18. package/dist/api/shape.js +0 -21
  19. package/dist/api/swagger-source.d.ts +1 -12
  20. package/dist/api/swagger-source.js +2 -20
  21. package/dist/api/types.d.ts +0 -34
  22. package/dist/api/types.js +0 -1
  23. package/dist/bin/cli.d.ts +0 -1
  24. package/dist/bin/cli.js +1 -5
  25. package/dist/config/config.d.ts +0 -54
  26. package/dist/config/config.js +0 -60
  27. package/dist/index.d.ts +0 -5
  28. package/dist/index.js +0 -5
  29. package/dist/knowledge/chunk.d.ts +0 -15
  30. package/dist/knowledge/chunk.js +0 -16
  31. package/dist/knowledge/github.d.ts +0 -25
  32. package/dist/knowledge/github.js +0 -40
  33. package/dist/knowledge/loader.d.ts +0 -28
  34. package/dist/knowledge/loader.js +0 -26
  35. package/dist/knowledge/search.d.ts +0 -22
  36. package/dist/knowledge/search.js +0 -34
  37. package/dist/knowledge/tar.d.ts +0 -15
  38. package/dist/knowledge/tar.js +0 -33
  39. package/dist/knowledge/types.d.ts +0 -35
  40. package/dist/knowledge/types.js +0 -1
  41. package/dist/server.d.ts +0 -12
  42. package/dist/server.js +10 -14
  43. package/dist/session.d.ts +0 -23
  44. package/dist/session.js +0 -18
  45. package/dist/tools/api-call.d.ts +0 -2
  46. package/dist/tools/api-call.js +0 -23
  47. package/dist/tools/api-discovery.d.ts +0 -2
  48. package/dist/tools/api-discovery.js +2 -5
  49. package/dist/tools/docs.d.ts +0 -2
  50. package/dist/tools/docs.js +1 -5
  51. package/dist/tools/guide.d.ts +0 -7
  52. package/dist/tools/guide.js +2 -9
  53. package/dist/tools/result.d.ts +0 -8
  54. package/dist/tools/result.js +0 -7
  55. package/dist/tools/whoami.d.ts +0 -2
  56. package/dist/tools/whoami.js +1 -3
  57. package/dist/transports/http.d.ts +0 -6
  58. package/dist/transports/http.js +0 -26
  59. package/dist/transports/stdio.d.ts +0 -6
  60. package/dist/transports/stdio.js +0 -6
  61. package/knowledge/operating-rules.md +70 -125
  62. package/package.json +18 -5
  63. package/dist/api/audit.d.ts.map +0 -1
  64. package/dist/api/audit.js.map +0 -1
  65. package/dist/api/auth.d.ts.map +0 -1
  66. package/dist/api/auth.js.map +0 -1
  67. package/dist/api/build-catalog.d.ts.map +0 -1
  68. package/dist/api/build-catalog.js.map +0 -1
  69. package/dist/api/catalog.d.ts.map +0 -1
  70. package/dist/api/catalog.js.map +0 -1
  71. package/dist/api/client.d.ts.map +0 -1
  72. package/dist/api/client.js.map +0 -1
  73. package/dist/api/normalize-schema.d.ts.map +0 -1
  74. package/dist/api/normalize-schema.js.map +0 -1
  75. package/dist/api/policy.d.ts.map +0 -1
  76. package/dist/api/policy.js.map +0 -1
  77. package/dist/api/shape.d.ts.map +0 -1
  78. package/dist/api/shape.js.map +0 -1
  79. package/dist/api/swagger-source.d.ts.map +0 -1
  80. package/dist/api/swagger-source.js.map +0 -1
  81. package/dist/api/types.d.ts.map +0 -1
  82. package/dist/api/types.js.map +0 -1
  83. package/dist/bin/cli.d.ts.map +0 -1
  84. package/dist/bin/cli.js.map +0 -1
  85. package/dist/config/config.d.ts.map +0 -1
  86. package/dist/config/config.js.map +0 -1
  87. package/dist/index.d.ts.map +0 -1
  88. package/dist/index.js.map +0 -1
  89. package/dist/knowledge/chunk.d.ts.map +0 -1
  90. package/dist/knowledge/chunk.js.map +0 -1
  91. package/dist/knowledge/github.d.ts.map +0 -1
  92. package/dist/knowledge/github.js.map +0 -1
  93. package/dist/knowledge/loader.d.ts.map +0 -1
  94. package/dist/knowledge/loader.js.map +0 -1
  95. package/dist/knowledge/search.d.ts.map +0 -1
  96. package/dist/knowledge/search.js.map +0 -1
  97. package/dist/knowledge/tar.d.ts.map +0 -1
  98. package/dist/knowledge/tar.js.map +0 -1
  99. package/dist/knowledge/types.d.ts.map +0 -1
  100. package/dist/knowledge/types.js.map +0 -1
  101. package/dist/server.d.ts.map +0 -1
  102. package/dist/server.js.map +0 -1
  103. package/dist/session.d.ts.map +0 -1
  104. package/dist/session.js.map +0 -1
  105. package/dist/tools/api-call.d.ts.map +0 -1
  106. package/dist/tools/api-call.js.map +0 -1
  107. package/dist/tools/api-discovery.d.ts.map +0 -1
  108. package/dist/tools/api-discovery.js.map +0 -1
  109. package/dist/tools/docs.d.ts.map +0 -1
  110. package/dist/tools/docs.js.map +0 -1
  111. package/dist/tools/guide.d.ts.map +0 -1
  112. package/dist/tools/guide.js.map +0 -1
  113. package/dist/tools/result.d.ts.map +0 -1
  114. package/dist/tools/result.js.map +0 -1
  115. package/dist/tools/whoami.d.ts.map +0 -1
  116. package/dist/tools/whoami.js.map +0 -1
  117. package/dist/transports/http.d.ts.map +0 -1
  118. package/dist/transports/http.js.map +0 -1
  119. package/dist/transports/stdio.d.ts.map +0 -1
  120. package/dist/transports/stdio.js.map +0 -1
@@ -1,7 +1,6 @@
1
1
  import type { Config } from '../config/config.js';
2
2
  import { type PermissionMap } from './build-catalog.js';
3
3
  import type { Catalog, Operation } from './types.js';
4
- /** Найденная операция с оценкой релевантности. */
5
4
  export interface OperationHit {
6
5
  opId: string;
7
6
  method: string;
@@ -12,40 +11,16 @@ export interface OperationHit {
12
11
  risk: Operation['risk'];
13
12
  score: number;
14
13
  }
15
- /**
16
- * Индекс каталога операций.
17
- * Поиск здесь примитивный — подсчёт совпадений термов по склеенной строке.
18
- * Это осознанно: путей 478, они короткие и хорошо самоописаны,
19
- * полнотекстовый движок дал бы ту же выдачу при большем весе.
20
- */
21
14
  export declare class OperationCatalog {
22
15
  private byOpId;
23
16
  catalog: Catalog;
24
17
  constructor(catalog: Catalog);
25
- /** Карта прав — единственное, что осталось предсобранным: её нет в swagger. */
26
18
  static permissionMap(): PermissionMap;
27
- /**
28
- * Строит каталог из swagger подключённого стенда.
29
- * Недоступный стенд не мешает серверу подняться: каталог получается пустым,
30
- * а причина уходит в `warnings` и оттуда в `cms_guide` и `cms_whoami`.
31
- * Пустой каталог безопасен — вызвать по нему нечего, — но молчать о нём нельзя.
32
- */
33
19
  static resolve(config: Config, bearer?: string): Promise<OperationCatalog>;
34
- /** Каталог не удалось собрать при старте — стенд был недоступен или закрыт авторизацией. */
35
20
  get isEmpty(): boolean;
36
- /**
37
- * Заменяет содержимое каталога, собранного вхолостую.
38
- * Нужно remote-режиму: процесс поднимается без учётных данных, и если стенд
39
- * отдаёт swagger только авторизованным, единственный шанс получить каталог —
40
- * первая сессия с токеном. Каталог общий на процесс, поэтому и подмена одна.
41
- */
42
21
  adopt(other: OperationCatalog): void;
43
22
  get(opId: string): Operation | undefined;
44
23
  operations(): readonly Operation[];
45
- /**
46
- * Подсказывает похожие `opId` — нужна для внятной ошибки, когда модель
47
- * угадала имя операции вместо того, чтобы взять его из поиска.
48
- */
49
24
  suggest(opId: string, limit?: number): string[];
50
25
  search(params: {
51
26
  query: string;
@@ -55,4 +30,3 @@ export declare class OperationCatalog {
55
30
  limit?: number;
56
31
  }): OperationHit[];
57
32
  }
58
- //# sourceMappingURL=catalog.d.ts.map
@@ -3,12 +3,6 @@ import { resolve } from 'node:path';
3
3
  import { dataDir } from '../knowledge/loader.js';
4
4
  import { buildCatalog } from './build-catalog.js';
5
5
  import { fetchSwagger } from './swagger-source.js';
6
- /**
7
- * Индекс каталога операций.
8
- * Поиск здесь примитивный — подсчёт совпадений термов по склеенной строке.
9
- * Это осознанно: путей 478, они короткие и хорошо самоописаны,
10
- * полнотекстовый движок дал бы ту же выдачу при большем весе.
11
- */
12
6
  export class OperationCatalog {
13
7
  byOpId;
14
8
  catalog;
@@ -16,22 +10,14 @@ export class OperationCatalog {
16
10
  if (catalog.version !== 1) {
17
11
  throw new Error(`Unsupported catalog version ${String(catalog.version)}`);
18
12
  }
19
- /** Каталог собирается из документа, полученного по сети: поля нормализуем на входе. */
20
13
  this.catalog = { ...catalog, warnings: catalog.warnings ?? [] };
21
14
  this.byOpId = new Map(catalog.operations.map((o) => [o.opId, o]));
22
15
  }
23
- /** Карта прав — единственное, что осталось предсобранным: её нет в swagger. */
24
16
  static permissionMap() {
25
17
  const file = resolve(dataDir(), 'permissions.json');
26
18
  const parsed = JSON.parse(readFileSync(file, 'utf8'));
27
19
  return { byOpId: parsed.byOpId, permissions: parsed.permissions };
28
20
  }
29
- /**
30
- * Строит каталог из swagger подключённого стенда.
31
- * Недоступный стенд не мешает серверу подняться: каталог получается пустым,
32
- * а причина уходит в `warnings` и оттуда в `cms_guide` и `cms_whoami`.
33
- * Пустой каталог безопасен — вызвать по нему нечего, — но молчать о нём нельзя.
34
- */
35
21
  static async resolve(config, bearer) {
36
22
  const builtAt = new Date().toISOString();
37
23
  try {
@@ -51,7 +37,7 @@ export class OperationCatalog {
51
37
  ...catalog,
52
38
  warnings: [
53
39
  ...catalog.warnings,
54
- `The stand at ${config.baseUrl} was unreachable; this catalog came from the ` +
40
+ `The instance at ${config.baseUrl} was unreachable; this catalog came from the ` +
55
41
  'on-disk cache and may not match the API that is actually running.',
56
42
  ],
57
43
  }
@@ -65,24 +51,17 @@ export class OperationCatalog {
65
51
  swaggerHash: '',
66
52
  permissions: [],
67
53
  warnings: [
68
- `The operation catalog is EMPTY: ${config.baseUrl} did not serve its swagger ` +
54
+ `The operation catalog is EMPTY: ${config.baseUrl} did not serve its API document ` +
69
55
  `(${error instanceof Error ? error.message : String(error)}). ` +
70
- 'No API operation can be searched or called until the stand is reachable.',
56
+ 'No API operation can be searched or called until the instance is reachable.',
71
57
  ],
72
58
  operations: [],
73
59
  });
74
60
  }
75
61
  }
76
- /** Каталог не удалось собрать при старте — стенд был недоступен или закрыт авторизацией. */
77
62
  get isEmpty() {
78
63
  return this.catalog.operations.length === 0;
79
64
  }
80
- /**
81
- * Заменяет содержимое каталога, собранного вхолостую.
82
- * Нужно remote-режиму: процесс поднимается без учётных данных, и если стенд
83
- * отдаёт swagger только авторизованным, единственный шанс получить каталог —
84
- * первая сессия с токеном. Каталог общий на процесс, поэтому и подмена одна.
85
- */
86
65
  adopt(other) {
87
66
  this.catalog = other.catalog;
88
67
  this.byOpId = new Map(other.catalog.operations.map((o) => [o.opId, o]));
@@ -93,10 +72,6 @@ export class OperationCatalog {
93
72
  operations() {
94
73
  return this.catalog.operations;
95
74
  }
96
- /**
97
- * Подсказывает похожие `opId` — нужна для внятной ошибки, когда модель
98
- * угадала имя операции вместо того, чтобы взять его из поиска.
99
- */
100
75
  suggest(opId, limit = 5) {
101
76
  const needle = opId.toLowerCase();
102
77
  return this.catalog.operations
@@ -151,7 +126,6 @@ export class OperationCatalog {
151
126
  return hits.slice(0, params.limit ?? 15);
152
127
  }
153
128
  }
154
- /** Доля общих триграмм — грубая, но достаточная мера похожести имён операций. */
155
129
  const overlap = (a, b) => {
156
130
  const grams = (value) => {
157
131
  const out = new Set();
@@ -170,4 +144,3 @@ const overlap = (a, b) => {
170
144
  }
171
145
  return right.size === 0 ? 0 : shared / right.size;
172
146
  };
173
- //# sourceMappingURL=catalog.js.map
@@ -1,18 +1,15 @@
1
1
  import type { TokenStore } from './auth.js';
2
2
  import type { Operation } from './types.js';
3
- /** Аргументы вызова операции, как их присылает модель. */
4
3
  export interface CallArgs {
5
4
  path?: Record<string, string | number>;
6
5
  query?: Record<string, string | number | boolean>;
7
6
  body?: unknown;
8
7
  }
9
- /** Нормализованный неуспех вызова. */
10
8
  export interface NormalizedError {
11
9
  status: number;
12
10
  message: string;
13
11
  hint?: string;
14
12
  }
15
- /** Результат вызова: либо тело ответа, либо нормализованная ошибка. */
16
13
  export type CallResult = {
17
14
  ok: true;
18
15
  status: number;
@@ -21,29 +18,10 @@ export type CallResult = {
21
18
  ok: false;
22
19
  error: NormalizedError;
23
20
  };
24
- /** Ошибка на стороне вызывающего — до отправки запроса. */
25
21
  export declare class RequestBuildError extends Error {
26
22
  }
27
- /**
28
- * Возвращает тело в том виде, в каком его надо сериализовать в запрос.
29
- * Часть MCP-клиентов передаёт объектный аргумент уже сериализованным в строку;
30
- * без этой нормализации `JSON.stringify` завернул бы его второй раз, и на сервер
31
- * ушла бы JSON-строка вместо объекта — любая запись падала бы с невнятным 400.
32
- * Разворачивается только строка, которая целиком является объектом или массивом:
33
- * скалярная строка — законное тело, и трогать её нельзя.
34
- */
35
23
  export declare const normalizeBody: (body: unknown) => unknown;
36
- /**
37
- * Подставляет path-параметры и собирает query-строку.
38
- * Отсутствующий обязательный path-параметр — ошибка здесь, а не 404 от сервера:
39
- * иначе агент получает загадочный ответ вместо указания, чего не хватает.
40
- */
41
24
  export declare const buildUrl: (baseUrl: string, operation: Operation, args: CallArgs) => string;
42
- /**
43
- * HTTP-клиент Admin API.
44
- * Единственный ретрай — на 401 после обновления токена: любой другой повтор
45
- * на изменяющей операции рискует создать дубль, поэтому его здесь нет.
46
- */
47
25
  export declare class AdminApiClient {
48
26
  private readonly baseUrl;
49
27
  private readonly tokens;
@@ -54,7 +32,5 @@ export declare class AdminApiClient {
54
32
  timeoutMs: number;
55
33
  });
56
34
  call(operation: Operation, args: CallArgs): Promise<CallResult>;
57
- /** Читает набор прав админа. Пустой массив означает «не удалось выяснить». */
58
35
  fetchPermissions(adminId: number): Promise<string[]>;
59
36
  }
60
- //# sourceMappingURL=client.d.ts.map
@@ -1,14 +1,5 @@
1
- /** Ошибка на стороне вызывающего — до отправки запроса. */
2
1
  export class RequestBuildError extends Error {
3
2
  }
4
- /**
5
- * Возвращает тело в том виде, в каком его надо сериализовать в запрос.
6
- * Часть MCP-клиентов передаёт объектный аргумент уже сериализованным в строку;
7
- * без этой нормализации `JSON.stringify` завернул бы его второй раз, и на сервер
8
- * ушла бы JSON-строка вместо объекта — любая запись падала бы с невнятным 400.
9
- * Разворачивается только строка, которая целиком является объектом или массивом:
10
- * скалярная строка — законное тело, и трогать её нельзя.
11
- */
12
3
  export const normalizeBody = (body) => {
13
4
  if (typeof body !== 'string') {
14
5
  return body;
@@ -25,11 +16,6 @@ export const normalizeBody = (body) => {
25
16
  return body;
26
17
  }
27
18
  };
28
- /**
29
- * Подставляет path-параметры и собирает query-строку.
30
- * Отсутствующий обязательный path-параметр — ошибка здесь, а не 404 от сервера:
31
- * иначе агент получает загадочный ответ вместо указания, чего не хватает.
32
- */
33
19
  export const buildUrl = (baseUrl, operation, args) => {
34
20
  let path = operation.path;
35
21
  const provided = args.path ?? {};
@@ -47,7 +33,7 @@ export const buildUrl = (baseUrl, operation, args) => {
47
33
  const leftover = /\{([^}]+)\}/.exec(path);
48
34
  if (leftover) {
49
35
  throw new RequestBuildError(`Path parameter "${leftover[1] ?? ''}" of ${operation.path} was not provided and is not ` +
50
- 'declared in the catalog — the swagger snapshot may be stale.');
36
+ 'declared in the catalog — the cached API document may be stale.');
51
37
  }
52
38
  const search = new URLSearchParams();
53
39
  for (const [key, value] of Object.entries(args.query ?? {})) {
@@ -58,23 +44,22 @@ export const buildUrl = (baseUrl, operation, args) => {
58
44
  const suffix = search.size > 0 ? `?${search.toString()}` : '';
59
45
  return `${baseUrl}${path}${suffix}`;
60
46
  };
61
- /** Подбирает подсказку по коду ответа и метаданным операции. */
62
47
  const hintFor = (status, operation) => {
63
48
  if (status === 400 || status === 422) {
64
49
  const loose = operation.body?.schema['x-loose'] === true;
65
50
  return (`Validation rejected the payload. ${loose ? 'This body has loosely typed fields — copy the shape from the example in cms_api_describe. ' : ''}` +
66
- `Read ${operation.docLinks.slice(0, 2).join(', ')} with cms_docs_read before retrying.`);
51
+ 'Search the knowledge base with cms_docs_search before retrying.');
67
52
  }
68
53
  if (status === 403) {
69
54
  return operation.permission
70
55
  ? `The admin lacks "${operation.permission}". Ask for the grant; retrying will not help.`
71
- : 'Forbidden. Check the admin\'s permissions and the module visibility rules (back/docs/user-permissions).';
56
+ : 'Forbidden. Check the admin\'s permissions and the module visibility rules.';
72
57
  }
73
58
  if (status === 404) {
74
- return 'Not found. The id may belong to another stand — prefer marker-based operations where they exist.';
59
+ return 'Not found. The id may belong to another instance — prefer marker-based operations where they exist.';
75
60
  }
76
61
  if (status >= 500) {
77
- return 'Server-side failure. Check the dnk-back logs; do not retry blindly.';
62
+ return 'Server-side failure on the instance. Report it to your operator; do not retry blindly.';
78
63
  }
79
64
  return undefined;
80
65
  };
@@ -105,11 +90,6 @@ const extractMessage = (body, fallback) => {
105
90
  }
106
91
  return fallback;
107
92
  };
108
- /**
109
- * HTTP-клиент Admin API.
110
- * Единственный ретрай — на 401 после обновления токена: любой другой повтор
111
- * на изменяющей операции рискует создать дубль, поэтому его здесь нет.
112
- */
113
93
  export class AdminApiClient {
114
94
  baseUrl;
115
95
  tokens;
@@ -148,7 +128,7 @@ export class AdminApiClient {
148
128
  error: {
149
129
  status: 0,
150
130
  message: `Request to ${url} failed: ${error instanceof Error ? error.message : String(error)}`,
151
- hint: 'Is the stand reachable? In local mode dnk-back must be running with API_TYPE=/api/admin.',
131
+ hint: 'Is the instance reachable at the configured base URL? It must expose the Admin API under /api/admin.',
152
132
  },
153
133
  };
154
134
  }
@@ -166,7 +146,6 @@ export class AdminApiClient {
166
146
  }
167
147
  return { ok: true, status: response.status, body };
168
148
  }
169
- /** Читает набор прав админа. Пустой массив означает «не удалось выяснить». */
170
149
  async fetchPermissions(adminId) {
171
150
  try {
172
151
  const token = await this.tokens.accessToken();
@@ -193,4 +172,3 @@ export class AdminApiClient {
193
172
  }
194
173
  }
195
174
  }
196
- //# sourceMappingURL=client.js.map
@@ -1,14 +1,3 @@
1
1
  import type { JsonSchema } from './types.js';
2
- /**
3
- * Разворачивает `$ref` и нормализует типы за один проход.
4
- * Циклы (а они есть: сущности ссылаются друг на друга) обрываются заглушкой,
5
- * иначе разворачивание не завершится; глубина ограничена, чтобы схема осталась читаемой.
6
- */
7
2
  export declare const normalizeSchema: (node: unknown, components: Record<string, unknown>, depth?: number, refStack?: readonly string[]) => JsonSchema;
8
- /**
9
- * Урезает слишком большую схему до верхнего уровня свойств.
10
- * Схема тела запроса уходит прямо в контекст модели, и развёрнутый DTO
11
- * на 40 КБ вытеснит из него саму задачу.
12
- */
13
3
  export declare const capSchema: (schema: JsonSchema) => JsonSchema;
14
- //# sourceMappingURL=normalize-schema.d.ts.map
@@ -1,10 +1,4 @@
1
- /** Валидные типы JSON Schema. Всё остальное в этом swagger — TypeScript-выражения. */
2
1
  const VALID_TYPES = new Set(['object', 'array', 'string', 'number', 'integer', 'boolean', 'null']);
3
- /**
4
- * Приведение TypeScript-подобных типов из swagger к JSON Schema.
5
- * Проект генерирует схемы из декораторов NestJS, и `type` часто содержит
6
- * TS-выражение; таблица покрывает частые случаи, остальное помечается x-loose.
7
- */
8
2
  const TYPE_MAP = {
9
3
  date: { type: 'string', format: 'date-time' },
10
4
  Date: { type: 'string', format: 'date-time' },
@@ -30,11 +24,6 @@ const trimDescription = (value) => {
30
24
  return flat.length > MAX_DESCRIPTION ? `${flat.slice(0, MAX_DESCRIPTION)}…` : flat;
31
25
  };
32
26
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
33
- /**
34
- * Разворачивает `$ref` и нормализует типы за один проход.
35
- * Циклы (а они есть: сущности ссылаются друг на друга) обрываются заглушкой,
36
- * иначе разворачивание не завершится; глубина ограничена, чтобы схема осталась читаемой.
37
- */
38
27
  export const normalizeSchema = (node, components, depth = 0, refStack = []) => {
39
28
  if (!isRecord(node)) {
40
29
  return {};
@@ -51,7 +40,6 @@ export const normalizeSchema = (node, components, depth = 0, refStack = []) => {
51
40
  }
52
41
  return normalizeSchema(target, components, depth, [...refStack, name]);
53
42
  }
54
- /** allOf/oneOf/anyOf в этом swagger встречаются только как обёртки — берём первый член. */
55
43
  for (const key of ['allOf', 'oneOf', 'anyOf']) {
56
44
  const variants = node[key];
57
45
  if (Array.isArray(variants) && variants.length > 0) {
@@ -123,11 +111,6 @@ export const normalizeSchema = (node, components, depth = 0, refStack = []) => {
123
111
  return out;
124
112
  };
125
113
  const MAX_SCHEMA_BYTES = 6_144;
126
- /**
127
- * Урезает слишком большую схему до верхнего уровня свойств.
128
- * Схема тела запроса уходит прямо в контекст модели, и развёрнутый DTO
129
- * на 40 КБ вытеснит из него саму задачу.
130
- */
131
114
  export const capSchema = (schema) => {
132
115
  if (Buffer.byteLength(JSON.stringify(schema), 'utf8') <= MAX_SCHEMA_BYTES) {
133
116
  return schema;
@@ -148,4 +131,3 @@ export const capSchema = (schema) => {
148
131
  }
149
132
  return { ...schema, properties: shallow, 'x-truncated': true };
150
133
  };
151
- //# sourceMappingURL=normalize-schema.js.map
@@ -1,7 +1,6 @@
1
1
  import type { AllowLevel } from '../config/config.js';
2
2
  import type { AdminIdentity } from './auth.js';
3
3
  import type { Operation } from './types.js';
4
- /** Результат проверки политики перед выполнением операции. */
5
4
  export type PolicyDecision = {
6
5
  kind: 'allow';
7
6
  } | {
@@ -11,42 +10,20 @@ export type PolicyDecision = {
11
10
  kind: 'needsConfirm';
12
11
  reason: string;
13
12
  };
14
- /**
15
- * Одноразовые токены подтверждения для необратимых операций.
16
- * Токен привязан к хэшу (opId + аргументы), поэтому подтверждение одного удаления
17
- * нельзя переиспользовать для другого — иначе двухшаговость была бы декоративной.
18
- */
19
13
  export declare class ConfirmStore {
20
14
  private readonly issued;
21
15
  private static hash;
22
16
  issue(opId: string, args: unknown, now?: number): string;
23
- /**
24
- * Проверяет токен, не гася его.
25
- * Нужна, чтобы отказ по другой причине (нет права, не тот уровень allow)
26
- * не сжигал подтверждение, которое человек уже дал.
27
- */
28
17
  verify(token: string, opId: string, args: unknown, now?: number): boolean;
29
- /** Проверяет и немедленно гасит токен. Повторное использование невозможно. */
30
18
  consume(token: string, opId: string, args: unknown, now?: number): boolean;
31
19
  private sweep;
32
20
  }
33
- /**
34
- * Проверяет только уровень `--allow`.
35
- * Вынесено отдельно, чтобы вызывающий мог отказать до аутентификации:
36
- * обещание «no request was sent» не должно нарушаться самим логином.
37
- */
38
21
  export declare const checkLevel: (operation: Operation, allow: AllowLevel) => Extract<PolicyDecision, {
39
22
  kind: "deny";
40
23
  }> | undefined;
41
- /**
42
- * Решает, можно ли выполнить операцию.
43
- * Порядок проверок значим: уровень доступа — до прав, права — до подтверждения,
44
- * чтобы read-only сервер вообще не выдавал токенов подтверждения.
45
- */
46
24
  export declare const decide: (params: {
47
25
  operation: Operation;
48
26
  allow: AllowLevel;
49
27
  identity?: AdminIdentity;
50
28
  confirmValid: boolean;
51
29
  }) => PolicyDecision;
52
- //# sourceMappingURL=policy.d.ts.map
Binary file
@@ -1,19 +1,6 @@
1
- /** Результат подготовки ответа к отдаче модели. */
2
1
  export interface ShapedResponse {
3
2
  body: unknown;
4
3
  truncated: boolean;
5
4
  }
6
- /**
7
- * Приводит ответ Admin API к размеру, пригодному для контекста модели.
8
- * Массивы урезаются по элементам (а не обрывом JSON), поэтому результат
9
- * остаётся валидным и самоописанным: агент видит `_truncated` и сужает запрос.
10
- */
11
5
  export declare const shapeResponse: (raw: unknown, maxBytes: number) => ShapedResponse;
12
- /**
13
- * Сжимает текущее состояние цели до размера, который не топит ответ dry-run.
14
- * Целиком отдаётся только небольшая цель: для создания в коллекции «цель» — это весь
15
- * список существующих объектов, а он бывает в сотни килобайт и вытесняет собой
16
- * единственно нужные части плана — политику и собранный запрос.
17
- */
18
6
  export declare const summarizeTarget: (target: unknown) => unknown;
19
- //# sourceMappingURL=shape.d.ts.map
package/dist/api/shape.js CHANGED
@@ -1,12 +1,9 @@
1
- /** Поля, значения которых бесполезны модели и раздувают ответ. */
2
1
  const BLOB_KEYS = new Set(['base64', 'buffer', 'content', 'fileContent', 'data64', 'blob']);
3
2
  const BLOB_VALUE_LIMIT = 512;
4
3
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
5
- /** Похоже ли строковое значение на base64-блоб, который незачем показывать. */
6
4
  const looksLikeBlob = (key, value) => typeof value === 'string' &&
7
5
  value.length > BLOB_VALUE_LIMIT &&
8
6
  (BLOB_KEYS.has(key) || /^data:|^[A-Za-z0-9+/=]{512,}$/.test(value));
9
- /** Рекурсивно заменяет блобы заглушкой с указанием исходного размера. */
10
7
  const stripBlobs = (value, key = '') => {
11
8
  if (looksLikeBlob(key, value)) {
12
9
  return `[stripped ${String(value.length)} chars]`;
@@ -23,18 +20,12 @@ const stripBlobs = (value, key = '') => {
23
20
  }
24
21
  return value;
25
22
  };
26
- /**
27
- * Приводит ответ Admin API к размеру, пригодному для контекста модели.
28
- * Массивы урезаются по элементам (а не обрывом JSON), поэтому результат
29
- * остаётся валидным и самоописанным: агент видит `_truncated` и сужает запрос.
30
- */
31
23
  export const shapeResponse = (raw, maxBytes) => {
32
24
  const cleaned = stripBlobs(raw);
33
25
  const size = (value) => Buffer.byteLength(JSON.stringify(value) ?? '', 'utf8');
34
26
  if (size(cleaned) <= maxBytes) {
35
27
  return { body: cleaned, truncated: false };
36
28
  }
37
- /** Массив верхнего уровня: оставляем префикс элементов. */
38
29
  if (Array.isArray(cleaned)) {
39
30
  const kept = takePrefix(cleaned, maxBytes);
40
31
  return {
@@ -49,7 +40,6 @@ export const shapeResponse = (raw, maxBytes) => {
49
40
  truncated: true,
50
41
  };
51
42
  }
52
- /** Пагинированный объект: урезаем самый большой массив внутри него. */
53
43
  if (isRecord(cleaned)) {
54
44
  const arrayKey = Object.entries(cleaned)
55
45
  .filter(([, value]) => Array.isArray(value))
@@ -83,11 +73,8 @@ export const shapeResponse = (raw, maxBytes) => {
83
73
  truncated: true,
84
74
  };
85
75
  };
86
- /** Бюджет на показ текущего состояния цели в плане dry-run и в запросе подтверждения. */
87
76
  const TARGET_LIMIT = 4_000;
88
- /** Сколько идентификаторов списка показывать в сводке. */
89
77
  const TARGET_ID_SAMPLE = 20;
90
- /** Собирает опознавательные значения элементов списка: id, если он есть, иначе сам элемент. */
91
78
  const identify = (items) => items.slice(0, TARGET_ID_SAMPLE).map((item) => {
92
79
  if (!isRecord(item)) {
93
80
  return item;
@@ -95,12 +82,6 @@ const identify = (items) => items.slice(0, TARGET_ID_SAMPLE).map((item) => {
95
82
  const id = item['id'] ?? item['identifier'] ?? item['marker'];
96
83
  return id ?? Object.keys(item).slice(0, 3);
97
84
  });
98
- /**
99
- * Сжимает текущее состояние цели до размера, который не топит ответ dry-run.
100
- * Целиком отдаётся только небольшая цель: для создания в коллекции «цель» — это весь
101
- * список существующих объектов, а он бывает в сотни килобайт и вытесняет собой
102
- * единственно нужные части плана — политику и собранный запрос.
103
- */
104
85
  export const summarizeTarget = (target) => {
105
86
  if (target === undefined || target === null) {
106
87
  return null;
@@ -134,7 +115,6 @@ export const summarizeTarget = (target) => {
134
115
  }
135
116
  return { _summary: { kind: 'value', preview: String(target).slice(0, 200), hint } };
136
117
  };
137
- /** Берёт максимальный префикс массива, укладывающийся в бюджет байтов. */
138
118
  const takePrefix = (items, maxBytes) => {
139
119
  const kept = [];
140
120
  let used = 2;
@@ -148,4 +128,3 @@ const takePrefix = (items, maxBytes) => {
148
128
  }
149
129
  return kept;
150
130
  };
151
- //# sourceMappingURL=shape.js.map
@@ -1,22 +1,11 @@
1
- /** Откуда и как забирать swagger. */
2
1
  export interface SwaggerSource {
3
- /** Базовый URL Admin API, уже нормализованный (с `/api/admin`, без слэша на конце). */
4
2
  baseUrl: string;
5
3
  cacheDir: string;
6
4
  timeoutMs: number;
7
- /** Bearer-токен: нужен, если стенд закрывает этот эндпоинт авторизацией. */
8
5
  bearer?: string;
9
6
  }
10
- /** Полученный документ и то, откуда он взялся. */
11
7
  export interface FetchedSwagger {
12
8
  raw: string;
13
- origin: 'stand' | 'cache';
9
+ origin: 'instance' | 'cache';
14
10
  }
15
- /**
16
- * Забирает swagger со стенда, с откатом на кэш.
17
- * Эндпоинт публичный на большинстве стендов, поэтому первый запрос идёт без
18
- * авторизации: в remote-режиме на момент старта процесса учётных данных может
19
- * не быть вовсе. Токен добавляется только если стенд ответил 401/403.
20
- */
21
11
  export declare const fetchSwagger: (source: SwaggerSource) => Promise<FetchedSwagger>;
22
- //# sourceMappingURL=swagger-source.d.ts.map
@@ -1,14 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- /** Путь, по которому стенд отдаёт swagger админского API. */
5
4
  const SWAGGER_PATH = '/workflows/nodes/admin-api/swagger.json';
6
- /** Язык описаний. Английский — тот же, на котором написаны summary в каталоге. */
7
5
  const LANG_CODE = 'en_US';
8
- /**
9
- * Имя файла кэша. Ключом служит baseUrl: каталог операций одного стенда
10
- * нельзя показывать за каталог другого, даже если оба «тот же самый» CMS.
11
- */
12
6
  const cacheFile = (source) => join(source.cacheDir, 'swagger', `${createHash('sha256').update(source.baseUrl).digest('hex').slice(0, 16)}.json`);
13
7
  const readCache = (source) => {
14
8
  try {
@@ -23,12 +17,6 @@ const writeCache = (source, raw) => {
23
17
  mkdirSync(join(source.cacheDir, 'swagger'), { recursive: true });
24
18
  writeFileSync(file, raw);
25
19
  };
26
- /**
27
- * Забирает swagger со стенда, с откатом на кэш.
28
- * Эндпоинт публичный на большинстве стендов, поэтому первый запрос идёт без
29
- * авторизации: в remote-режиме на момент старта процесса учётных данных может
30
- * не быть вовсе. Токен добавляется только если стенд ответил 401/403.
31
- */
32
20
  export const fetchSwagger = async (source) => {
33
21
  const url = `${source.baseUrl}${SWAGGER_PATH}?langCode=${LANG_CODE}`;
34
22
  const request = async (bearer) => fetch(url, {
@@ -46,15 +34,10 @@ export const fetchSwagger = async (source) => {
46
34
  if (!response.ok) {
47
35
  throw new Error(`${source.baseUrl} answered ${String(response.status)} for ${SWAGGER_PATH}` +
48
36
  (response.status === 401 || response.status === 403
49
- ? ' — the stand requires credentials to read its swagger'
37
+ ? ' — the instance requires credentials to read its API document'
50
38
  : ''));
51
39
  }
52
40
  const raw = await response.text();
53
- /**
54
- * Проверяем, что это вообще OpenAPI, до записи в кэш. Стенд за прокси легко
55
- * отвечает страницей входа или конвертом ошибки с кодом 200; закэшировать
56
- * такое — значит подсунуть агенту пустой каталог до следующей чистки кэша.
57
- */
58
41
  const parsed = JSON.parse(raw);
59
42
  if (typeof parsed !== 'object' ||
60
43
  parsed === null ||
@@ -63,7 +46,7 @@ export const fetchSwagger = async (source) => {
63
46
  throw new Error(`${source.baseUrl}${SWAGGER_PATH} did not answer with an OpenAPI document`);
64
47
  }
65
48
  writeCache(source, raw);
66
- return { raw, origin: 'stand' };
49
+ return { raw, origin: 'instance' };
67
50
  }
68
51
  catch (error) {
69
52
  const cached = readCache(source);
@@ -73,4 +56,3 @@ export const fetchSwagger = async (source) => {
73
56
  throw error instanceof Error ? error : new Error(String(error));
74
57
  }
75
58
  };
76
- //# sourceMappingURL=swagger-source.js.map