@besales/mcp 1.0.0-beta.4 → 1.0.0-beta.7

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 (56) hide show
  1. package/README.md +5 -3
  2. package/dist/http/api-client.d.ts +18 -0
  3. package/dist/http/api-client.js +33 -1
  4. package/dist/http/api-client.js.map +1 -1
  5. package/dist/resources/docs/platforms.md +63 -0
  6. package/dist/schemas/mcp-tools.json +847 -30
  7. package/dist/tools/definitions/agent-design.d.ts +2 -0
  8. package/dist/tools/definitions/agent-design.js +5 -0
  9. package/dist/tools/definitions/agent-design.js.map +1 -1
  10. package/dist/tools/definitions/agent-settings.d.ts +1 -0
  11. package/dist/tools/definitions/agent-settings.js +2 -0
  12. package/dist/tools/definitions/agent-settings.js.map +1 -1
  13. package/dist/tools/definitions/api-tool-request.d.ts +4 -2
  14. package/dist/tools/definitions/dialogue-nudge.d.ts +7 -0
  15. package/dist/tools/definitions/dialogue-nudge.js +139 -0
  16. package/dist/tools/definitions/dialogue-nudge.js.map +1 -0
  17. package/dist/tools/definitions/platform-setup.d.ts +1 -0
  18. package/dist/tools/definitions/task-state.d.ts +1 -1
  19. package/dist/tools/definitions/telegram-personal-connect.d.ts +12 -0
  20. package/dist/tools/definitions/telegram-personal-connect.js +61 -0
  21. package/dist/tools/definitions/telegram-personal-connect.js.map +1 -0
  22. package/dist/tools/definitions/telegram-personal-management.d.ts +1 -1
  23. package/dist/tools/definitions/telegram-personal-profile.d.ts +24 -0
  24. package/dist/tools/definitions/telegram-personal-profile.js +154 -0
  25. package/dist/tools/definitions/telegram-personal-profile.js.map +1 -0
  26. package/dist/tools/definitions/telegram-personal-proxies.d.ts +42 -0
  27. package/dist/tools/definitions/telegram-personal-proxies.js +204 -0
  28. package/dist/tools/definitions/telegram-personal-proxies.js.map +1 -0
  29. package/dist/tools/definitions/telegram-proxy-stock.d.ts +34 -0
  30. package/dist/tools/definitions/telegram-proxy-stock.js +129 -0
  31. package/dist/tools/definitions/telegram-proxy-stock.js.map +1 -0
  32. package/dist/tools/definitions.d.ts +98 -2
  33. package/dist/tools/definitions.js +11 -0
  34. package/dist/tools/definitions.js.map +1 -1
  35. package/dist/tools/follow-up-request.js +3 -0
  36. package/dist/tools/follow-up-request.js.map +1 -1
  37. package/dist/tools/operation-recovery.d.ts +1 -1
  38. package/dist/tools/operation-recovery.js +1 -1
  39. package/dist/tools/operation-recovery.js.map +1 -1
  40. package/dist/tools/registry-mapping.cases.js +235 -0
  41. package/dist/tools/registry-mapping.cases.js.map +1 -1
  42. package/dist/tools/registry-test-fixtures.js +5 -1
  43. package/dist/tools/registry-test-fixtures.js.map +1 -1
  44. package/dist/tools/registry.js +18 -4
  45. package/dist/tools/registry.js.map +1 -1
  46. package/dist/tools/result.d.ts +1 -0
  47. package/dist/tools/result.js +57 -5
  48. package/dist/tools/result.js.map +1 -1
  49. package/docs/dialogue-nudge.md +50 -0
  50. package/docs/failure-semantics.md +4 -1
  51. package/docs/help-navigation.md +2 -0
  52. package/docs/telegram-personal-management.md +80 -3
  53. package/docs/telegram-personal-proxies.md +135 -0
  54. package/package.json +1 -1
  55. package/scripts/telegram-personal-profile-mutants.mjs +96 -0
  56. package/scripts/telegram-personal-proxy-mutants.mjs +171 -0
@@ -0,0 +1,96 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ const suite = 'src/tools/definitions/telegram-personal-profile.spec.ts';
7
+ const tool = 'src/tools/definitions/telegram-personal-profile.ts';
8
+ const published = 'src/schemas/mcp-tools.json';
9
+ const temporary = mkdtempSync(join(tmpdir(), 'telegram-profile-mutants-'));
10
+ function run(pattern, name) {
11
+ const output = join(temporary, name + '.json');
12
+ const result = spawnSync(process.execPath, [
13
+ 'node_modules/vitest/vitest.mjs', 'run', suite,
14
+ '--maxWorkers=1', '--reporter=json', '--outputFile=' + output,
15
+ ...(pattern ? ['--testNamePattern', pattern] : []),
16
+ ], { encoding: 'utf8', timeout: 180000 });
17
+ const report = JSON.parse(readFileSync(output, 'utf8'));
18
+ return { result, report };
19
+ }
20
+ // Единственное вхождение якоря: иначе мутация могла бы тихо попасть не туда.
21
+ function replaceOnce(text, anchor, replacement) {
22
+ if (text.split(anchor).length !== 2) throw new Error('MUTATION_ANCHOR_NOT_UNIQUE');
23
+ return text.replace(anchor, replacement);
24
+ }
25
+ try {
26
+ const baseline = run(undefined, 'baseline');
27
+ if (baseline.result.status !== 0 || baseline.report.numPassedTests === 0) {
28
+ throw new Error('BASELINE_FAILED\n' + baseline.result.stdout + baseline.result.stderr);
29
+ }
30
+ console.log('BASELINE PASS ' + baseline.report.numPassedTests);
31
+ for (const entry of [
32
+ {
33
+ // Главный мутант пачки: оба режима ников разрешены одновременно в Zod-схеме инструмента.
34
+ name: 'username-modes-both-allowed-runtime',
35
+ file: tool,
36
+ pattern: 'оба режима сразу отвергаются до HTTP',
37
+ mutate: (text) =>
38
+ replaceOnce(
39
+ text,
40
+ " if (input.username_base !== undefined) {\n refuse(\n 'username_base',",
41
+ " if (input.username_base !== undefined && false) {\n refuse(\n 'username_base',",
42
+ ),
43
+ },
44
+ {
45
+ // То же в опубликованной схеме: снят запрет «usernames вместе с username_base».
46
+ name: 'username-modes-both-allowed-published',
47
+ file: published,
48
+ pattern: 'оба режима сразу отвергаются до HTTP',
49
+ mutate: (text) => {
50
+ const registry = JSON.parse(text);
51
+ const schema = registry.tools.find(
52
+ (item) => item.name === 'besales_platform_telegram_personal_profile_bulk',
53
+ ).inputSchema;
54
+ const before = schema.allOf.length;
55
+ schema.allOf = schema.allOf.filter(
56
+ (rule) => !(rule.not?.required?.includes('usernames') && rule.not.required.includes('username_base')),
57
+ );
58
+ if (schema.allOf.length !== before - 1) throw new Error('MUTATION_ANCHOR_NOT_UNIQUE');
59
+ return JSON.stringify(registry, null, 2) + '\n';
60
+ },
61
+ },
62
+ {
63
+ // Ключ идемпотентности уходит под общим именем, которого профильный маршрут ядра не читает.
64
+ name: 'idempotency-key-default-header',
65
+ file: tool,
66
+ pattern: 'идёт заголовком x-idempotency-key',
67
+ mutate: (text) =>
68
+ replaceOnce(text, " idempotencyKeyHeader: 'X-Idempotency-Key',\n", ''),
69
+ },
70
+ ]) {
71
+ const original = readFileSync(entry.file, 'utf8');
72
+ try {
73
+ writeFileSync(entry.file, entry.mutate(original));
74
+ const { result, report } = run(entry.pattern, entry.name);
75
+ // Убитым считается только провал утверждения внутри названного теста: AssertionError
76
+ // или отказ vitest expect(...).rejects («promise resolved … instead of rejecting»).
77
+ // Ошибка загрузки набора не даёт assertionResults и остаётся INFRA_FAILURE.
78
+ const failures = report.testResults.flatMap((item) => item.assertionResults).filter((test) =>
79
+ test.status === 'failed' && test.fullName.includes(entry.pattern) &&
80
+ test.failureMessages.some(
81
+ (message) =>
82
+ message.includes('AssertionError') ||
83
+ /^Error: promise resolved .* instead of rejecting/su.test(message),
84
+ ),
85
+ );
86
+ if (result.status === 0 || result.signal || failures.length === 0) {
87
+ throw new Error('SURVIVED_OR_INFRA_FAILURE:' + entry.name + '\n' + result.stdout + result.stderr);
88
+ }
89
+ console.log('KILLED ' + entry.name + ' ' + JSON.stringify(failures.map((test) => test.fullName)));
90
+ } finally {
91
+ writeFileSync(entry.file, original);
92
+ }
93
+ }
94
+ } finally {
95
+ rmSync(temporary, { recursive: true, force: true });
96
+ }
@@ -0,0 +1,171 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ const suite = 'src/tools/definitions/telegram-personal-proxies.spec.ts';
7
+ const tool = 'src/tools/definitions/telegram-personal-proxies.ts';
8
+ const temporary = mkdtempSync(join(tmpdir(), 'telegram-proxy-mutants-'));
9
+ function run(pattern, name, suitePath = suite) {
10
+ const output = join(temporary, name + '.json');
11
+ const result = spawnSync(process.execPath, [
12
+ 'node_modules/vitest/vitest.mjs', 'run', suitePath,
13
+ '--maxWorkers=1', '--reporter=json', '--outputFile=' + output,
14
+ ...(pattern ? ['--testNamePattern', pattern] : []),
15
+ ], { encoding: 'utf8', timeout: 180000 });
16
+ const report = JSON.parse(readFileSync(output, 'utf8'));
17
+ return { result, report };
18
+ }
19
+ function replaceOnce(text, anchor, replacement) {
20
+ if (text.split(anchor).length !== 2) throw new Error('MUTATION_ANCHOR_NOT_UNIQUE');
21
+ return text.replace(anchor, replacement);
22
+ }
23
+ try {
24
+ const baseline = run(undefined, 'baseline');
25
+ if (baseline.result.status !== 0 || baseline.report.numPassedTests === 0) {
26
+ throw new Error('BASELINE_FAILED\n' + baseline.result.stdout + baseline.result.stderr);
27
+ }
28
+ console.log('BASELINE PASS ' + baseline.report.numPassedTests);
29
+ for (const [suffix, description] of [
30
+ ['proxy_delete', 'Явное согласие человека на удаление прокси.'],
31
+ ['account_proxy_change', 'Явное согласие человека: номер переподключится.'],
32
+ ]) {
33
+ for (const target of ['runtime', 'published']) {
34
+ const file = target === 'runtime' ? tool : 'src/schemas/mcp-tools.json';
35
+ const name = suffix + '-without-confirmation-' + target;
36
+ const pattern = suffix + ': без явного true отказ до HTTP';
37
+ const original = readFileSync(file, 'utf8');
38
+ try {
39
+ let mutated;
40
+ if (target === 'runtime') {
41
+ const anchor = `explicit_confirmation: z.literal(true).describe('${description}')`;
42
+ mutated = replaceOnce(original, anchor,
43
+ `explicit_confirmation: z.literal(true).optional().describe('${description}')`);
44
+ } else {
45
+ const registry = JSON.parse(original);
46
+ const schema = registry.tools.find((entry) =>
47
+ entry.name === 'besales_platform_telegram_personal_' + suffix,
48
+ ).inputSchema;
49
+ if (!schema.required.includes('explicit_confirmation')) throw new Error('MUTATION_ANCHOR_NOT_UNIQUE');
50
+ schema.required = schema.required.filter((field) => field !== 'explicit_confirmation');
51
+ mutated = JSON.stringify(registry, null, 2) + '\n';
52
+ }
53
+ writeFileSync(file, mutated);
54
+ const { result, report } = run(pattern, name);
55
+ // Убит только утверждением именованного теста; ошибка загрузки — INFRA_FAILURE.
56
+ const failures = report.testResults.flatMap((entry) => entry.assertionResults).filter((test) =>
57
+ test.status === 'failed' && test.fullName.includes(pattern) &&
58
+ test.failureMessages.some((message) =>
59
+ message.includes('AssertionError') ||
60
+ /^Error: promise resolved .* instead of rejecting/su.test(message),
61
+ ),
62
+ );
63
+ if (result.status === 0 || result.signal || failures.length === 0) {
64
+ throw new Error('SURVIVED_OR_INFRA_FAILURE:' + name + '\n' + result.stdout + result.stderr);
65
+ }
66
+ console.log('KILLED ' + name + ' ' + JSON.stringify(failures.map((test) => test.fullName)));
67
+ } finally {
68
+ writeFileSync(file, original);
69
+ }
70
+ }
71
+ }
72
+ const stockSuite = 'src/tools/definitions/telegram-proxy-stock.spec.ts';
73
+ const stockFile = 'src/tools/definitions/telegram-proxy-stock.ts';
74
+ const connectFile = 'src/tools/definitions/telegram-personal-connect.ts';
75
+ const apiClientFile = 'src/http/api-client.ts';
76
+ const resultFile = 'src/tools/result.ts';
77
+ const publishedFile = 'src/schemas/mcp-tools.json';
78
+ const stockBaseline = run(undefined, 'stock-baseline', stockSuite);
79
+ if (stockBaseline.result.status !== 0 || stockBaseline.report.numPassedTests === 0) {
80
+ throw new Error('BASELINE_FAILED\n' + stockBaseline.result.stdout + stockBaseline.result.stderr);
81
+ }
82
+ console.log('STOCK BASELINE PASS ' + stockBaseline.report.numPassedTests);
83
+ function publishedMutation(suffix, change) {
84
+ return (original) => {
85
+ const registry = JSON.parse(original);
86
+ const schema = registry.tools.find((entry) => entry.name === 'besales_platform_telegram_personal_' + suffix).inputSchema;
87
+ change(schema);
88
+ return JSON.stringify(registry, null, 2) + '\n';
89
+ };
90
+ }
91
+ function inTool(original, suffix, anchor, replacement) {
92
+ const start = original.indexOf("name: 'besales_platform_telegram_personal_" + suffix + "'");
93
+ if (start < 0) throw new Error('MUTATION_ANCHOR_NOT_UNIQUE');
94
+ const next = original.indexOf(' defineTool({', start);
95
+ const end = next < 0 ? original.length : next;
96
+ return original.slice(0, start) + replaceOnce(original.slice(start, end), anchor, replacement) + original.slice(end);
97
+ }
98
+ const mutations = [
99
+ ['stock-loose-schema', stockFile, 'proxy_stock_import_link: строгая схема отвергает секреты до HTTP',
100
+ (original) => inTool(original, 'proxy_stock_import_link', 'schema: z.object(workspace).strict()', 'schema: z.object(workspace).passthrough()')],
101
+ ['stock-delete-without-confirmation-runtime', stockFile, 'proxy_stock_delete: без явного true отказ до HTTP',
102
+ (original) => replaceOnce(original, "explicit_confirmation: z.literal(true).describe('Явное согласие человека на удаление прокси из запаса.')", "explicit_confirmation: z.literal(true).optional().describe('Явное согласие человека на удаление прокси из запаса.')")],
103
+ ['stock-delete-without-confirmation-published', publishedFile, 'proxy_stock_delete: без явного true отказ до HTTP',
104
+ publishedMutation('proxy_stock_delete', (schema) => { schema.required = schema.required.filter((field) => field !== 'explicit_confirmation'); })],
105
+ ...['proxy_take_from_stock', 'proxy_return_to_stock'].map((suffix) => [
106
+ suffix + '-changed-key', tool, suffix + ': повтор сохраняет настоящий заголовок и тело',
107
+ (original) => inTool(original, suffix, 'idempotencyKey: input.action_id', "idempotencyKey: '00000000-0000-4000-8000-000000000099'"),
108
+ ]),
109
+ ...['proxy_take_from_stock', 'proxy_return_to_stock'].map((suffix) => [
110
+ suffix + '-wrong-key-header', tool, suffix + ': повтор сохраняет настоящий заголовок и тело',
111
+ (original) => inTool(original, suffix, "idempotencyKeyHeader: 'X-Idempotency-Key'", "idempotencyKeyHeader: 'Idempotency-Key'"),
112
+ ]),
113
+ ['stock-details-at-top-level', apiClientFile, 'PROXY_STOCK_NOT_ENOUGH: доступность из details видна в тексте',
114
+ (original) => replaceOnce(original, "...proxyStockErrorDetails(asRecord(source?.['details']), code)", '...proxyStockErrorDetails(source, code)')],
115
+ ['stock-available-dropped', apiClientFile, 'PROXY_STOCK_NOT_ENOUGH: доступность из details видна в тексте',
116
+ (original) => replaceOnce(original, '? { available } : {}', '? {} : {}')],
117
+ ['stock-items-dropped', apiClientFile, 'PROXY_STOCK_REFUSED: причины из details видны по каждому id',
118
+ (original) => replaceOnce(original, 'return { items };', 'return {};')],
119
+ ['stock-unknown-reason-exposed', apiClientFile, 'PROXY_STOCK_REFUSED: причины из details видны по каждому id',
120
+ (original) => replaceOnce(original, "['NOT_FOUND', 'NOT_READY', 'BUSY', 'ADDRESS_IN_USE'].includes(reason)", 'true')],
121
+ ['stock-visible-reasons-dropped', resultFile, 'PROXY_STOCK_REFUSED: причины из details видны по каждому id',
122
+ (original) => replaceOnce(original, 'for (const item of details.items ?? []) parts.push(`${item.proxyId}: ${item.reason}.`);', '')],
123
+ ['stock-requested-dropped', apiClientFile, 'отказы подключения: available, requested и files читаются из details закрытым набором',
124
+ (original) => replaceOnce(original, '? { requested } : {}', '? {} : {}')],
125
+ ['stock-files-dropped', apiClientFile, 'отказы подключения: available, requested и files читаются из details закрытым набором',
126
+ (original) => replaceOnce(original, 'return { files };', 'return {};')],
127
+ ['stock-route-removed', 'src/schemas/mcp-v2-route-map.json', 'содержит ровно десять новых маршрутов',
128
+ (original) => JSON.stringify(JSON.parse(original).filter((entry) => entry.tool !== 'besales_platform_telegram_personal_connect_link'), null, 2) + '\n'],
129
+ ['stock-link-truncatable', stockFile, 'proxy_stock_import_link: ссылка neverTruncate',
130
+ (original) => replaceOnce(original, 'const resultPolicy = { neverTruncate: true, maxResultBytes: 16 * 1024 }', 'const resultPolicy = { neverTruncate: false, maxResultBytes: 16 * 1024 }')],
131
+ ['connect-link-truncatable', connectFile, 'connect_link: ссылка neverTruncate',
132
+ (original) => replaceOnce(original, 'resultPolicy: { neverTruncate: true, maxResultBytes: 16 * 1024 }', 'resultPolicy: { neverTruncate: false, maxResultBytes: 16 * 1024 }')],
133
+ ['plan-two-forms-runtime', connectFile, 'план: строгая форма',
134
+ (original) => replaceOnce(original, "z.object({ country_code: z.string().regex(/^[A-Z]{2}$/u) }).strict()", "z.object({ country_code: z.string().regex(/^[A-Z]{2}$/u) }).passthrough()")],
135
+ ['plan-two-forms-published', publishedFile, 'план: строгая форма',
136
+ publishedMutation('connect_link', (schema) => {
137
+ const plan = schema.properties.proxy_plan;
138
+ plan.anyOf = plan.oneOf;
139
+ delete plan.oneOf;
140
+ for (const form of plan.anyOf) delete form.additionalProperties;
141
+ })],
142
+ ['plan-without-channel-runtime', connectFile, 'план: строгая форма',
143
+ (original) => replaceOnce(original, 'channel_id: uuidSchema,', 'channel_id: uuidSchema.optional(),')],
144
+ ['plan-without-channel-published', publishedFile, 'план: строгая форма',
145
+ publishedMutation('connect_link', (schema) => { schema.required = []; })],
146
+ ['take-without-selection-runtime', tool, 'take: ровно одна форма',
147
+ (original) => replaceOnce(original, ': input.count === undefined)', ': false)')],
148
+ ['take-without-selection-published', publishedFile, 'take: ровно одна форма',
149
+ publishedMutation('proxy_take_from_stock', (schema) => { delete schema.oneOf; })],
150
+ ];
151
+ for (const [name, file, pattern, mutate] of mutations) {
152
+ const original = readFileSync(file, 'utf8');
153
+ try {
154
+ writeFileSync(file, mutate(original));
155
+ const { result, report } = run(pattern, name, stockSuite);
156
+ const failures = report.testResults.flatMap((entry) => entry.assertionResults).filter((test) =>
157
+ test.status === 'failed' && test.fullName.includes(pattern) &&
158
+ test.failureMessages.some((message) => message.includes('AssertionError') || /^Error: promise resolved .* instead of rejecting/su.test(message)),
159
+ );
160
+ if (result.status === 0 || result.signal || failures.length === 0) {
161
+ throw new Error('SURVIVED_OR_INFRA_FAILURE:' + name + '\n' + result.stdout + result.stderr);
162
+ }
163
+ console.log('KILLED ' + name + ' ' + JSON.stringify(failures.map((test) => test.fullName)));
164
+ } finally {
165
+ writeFileSync(file, original);
166
+ }
167
+ }
168
+
169
+ } finally {
170
+ rmSync(temporary, { recursive: true, force: true });
171
+ }