dsh-telegram-multiagent 1.5.0 → 1.5.1
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/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/src/index.js +28 -2
- package/test/stend-prichiny-otkaza.mjs +73 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 1.5.1
|
|
2
|
+
|
|
3
|
+
🔴 **`errDetail` печатал пустоту после слова «причина».** Собирал причину через `??`, а нулевое
|
|
4
|
+
слияние реагирует только на `null` и `undefined` — пустая строка для него «найденное значение»,
|
|
5
|
+
и до `code` дело не доходило. У `AggregateError`, которую кладёт `fetch`, перебрав адреса,
|
|
6
|
+
собственный `message` как раз пустой.
|
|
7
|
+
|
|
8
|
+
В живом журнале это дало **125 строк за три часа** вида `причина: ` с пустотой после двоеточия.
|
|
9
|
+
Хуже отсутствия поля: «причина:» с пустотой читается как «причину узнали, она пустая», а её
|
|
10
|
+
просто не достали из объекта.
|
|
11
|
+
|
|
12
|
+
Теперь причина собирается перечнем: класс ошибки (AggregateError и Error лечатся по-разному, а
|
|
13
|
+
по тексту неотличимы), `code`, `message`, и внутренние ошибки `AggregateError.errors`, где у неё
|
|
14
|
+
и лежат настоящие причины. Пустой перечень даёт «объект есть, полей с описанием нет» — это НЕ
|
|
15
|
+
пустая причина, а другое состояние, и слова для него другие.
|
|
16
|
+
|
|
17
|
+
Строка отказа теперь называет, ЧТО опрашивалось: в цикле три опроса подряд (a2a, ящик, telegram),
|
|
18
|
+
и без имени отказ читается как «всё сломалось», хотя стоять может одно из трёх.
|
|
19
|
+
|
|
20
|
+
Стенд `stend-prichiny-otkaza.mjs`, 5 проб, вырезает функцию ИЗ ПРЕДМЕТА. Порча возвратом `??`
|
|
21
|
+
роняет три пробы из пяти.
|
|
22
|
+
|
|
1
23
|
## 1.5.0
|
|
2
24
|
|
|
3
25
|
🔴 **`/compact-status` больше не выдаёт устаревшую опору за свежую.** Строка «Опора замера:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-telegram-multiagent",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Канал Telegram для DeepSeek Harness: один модуль на машину обслуживает несколько агентов, у каждого свой бот, свой файл токена и свой список разрешённых. Метка отправителя неподделываема, память владельца и координатора слита в одну, режим доставки переключается файлом вне кода, цели управляются из канала.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/index.js
CHANGED
|
@@ -1364,10 +1364,33 @@ export function apply(ctx, config = {}) {
|
|
|
1364
1364
|
// «причины нет», и это та же слепота, что и раньше).
|
|
1365
1365
|
// Перенесено из второй живой копии модуля 01.09.2026 ДОСЛОВНО: своя редакция
|
|
1366
1366
|
// того же лечения развела бы копии по логике при сверке.
|
|
1367
|
+
// 🔴 `??` ПРОПУСКАЕТ ПУСТУЮ СТРОКУ КАК ЗАДАННОЕ ЗНАЧЕНИЕ (замер 04.09.2026).
|
|
1368
|
+
// Было `c?.message ?? c?.code ?? String(c)`. Нулевое слияние реагирует только на
|
|
1369
|
+
// null и undefined, а `message` у AggregateError (её кладёт fetch, перебрав адреса)
|
|
1370
|
+
// бывает ПУСТОЙ СТРОКОЙ — и она «найдена», до `code` дело не доходит.
|
|
1371
|
+
// В журнале это дало 125 строк за три часа вида «причина: » с пустотой после
|
|
1372
|
+
// двоеточия. Хуже отсутствия поля: «причина:» с пустотой читается как «причину
|
|
1373
|
+
// узнали, она пустая», а её просто не достали.
|
|
1374
|
+
// Воспроизведено: errDetail(cause с message='') -> '', errDetail(AggregateError) -> ''.
|
|
1367
1375
|
function errDetail(e) {
|
|
1368
1376
|
const c = e?.cause;
|
|
1369
1377
|
if (!c) return 'не указана';
|
|
1370
|
-
|
|
1378
|
+
const chasti = [];
|
|
1379
|
+
// Класс ошибки называем всегда: AggregateError и Error лечатся по-разному, а по
|
|
1380
|
+
// тексту они неотличимы.
|
|
1381
|
+
if (c?.constructor?.name) chasti.push(c.constructor.name);
|
|
1382
|
+
if (c?.code) chasti.push(String(c.code));
|
|
1383
|
+
if (c?.message) chasti.push(String(c.message));
|
|
1384
|
+
// У AggregateError настоящие причины лежат в errors, а собственный message пуст.
|
|
1385
|
+
if (Array.isArray(c?.errors) && c.errors.length) {
|
|
1386
|
+
const vnutri = c.errors.slice(0, 3)
|
|
1387
|
+
.map((x) => [x?.code, x?.message].filter(Boolean).join(' ') || String(x))
|
|
1388
|
+
.filter(Boolean);
|
|
1389
|
+
if (vnutri.length) chasti.push('внутри: ' + vnutri.join('; '));
|
|
1390
|
+
}
|
|
1391
|
+
// Пустой перечень — это НЕ пустая причина, а «объект есть, а сказать про него
|
|
1392
|
+
// нечего». Разные состояния, разные слова.
|
|
1393
|
+
return chasti.length ? chasti.join(' ') : `объект ${typeof c}, полей с описанием нет`;
|
|
1371
1394
|
}
|
|
1372
1395
|
|
|
1373
1396
|
async function loop(run) {
|
|
@@ -1397,7 +1420,10 @@ export function apply(ctx, config = {}) {
|
|
|
1397
1420
|
}
|
|
1398
1421
|
} catch (e) {
|
|
1399
1422
|
// Сеть моргнула или Telegram ответил ошибкой — ждём и продолжаем.
|
|
1400
|
-
|
|
1423
|
+
// ЧТО именно опрашивалось — называем: в цикле три опроса подряд (a2a, ящик,
|
|
1424
|
+
// телеграм), и без имени отказ читается как «всё сломалось», хотя стоять может
|
|
1425
|
+
// одно из трёх.
|
|
1426
|
+
log(`опрос не удался (a2a+ящик+telegram): ${e?.message ?? e} | причина: ${errDetail(e)}`);
|
|
1401
1427
|
await new Promise((r) => setTimeout(r, 3000));
|
|
1402
1428
|
}
|
|
1403
1429
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Стенд разбора причины отказа опроса.
|
|
2
|
+
//
|
|
3
|
+
// Коды: 0 сошлось | 1 расхождение | 2 слепота.
|
|
4
|
+
//
|
|
5
|
+
// ЧТО ЛЕЧИТ ПРЕДМЕТ. `errDetail` собирал причину через `??`. Нулевое слияние реагирует
|
|
6
|
+
// только на null и undefined, а `message` у AggregateError (её кладёт fetch, перебрав
|
|
7
|
+
// адреса) бывает ПУСТОЙ СТРОКОЙ — она «найдена», и до `code` дело не доходит.
|
|
8
|
+
// В живом журнале это дало 125 строк за три часа вида «причина: » с пустотой.
|
|
9
|
+
// 🔴 Хуже отсутствия поля: «причина:» с пустотой читается как «причину узнали, она
|
|
10
|
+
// пустая», а её просто не достали из объекта.
|
|
11
|
+
//
|
|
12
|
+
// 🔴 ВЫРЕЗАЕМ ИЗ ПРЕДМЕТА, а не переписываем: копия стареет молча.
|
|
13
|
+
//
|
|
14
|
+
// ГДЕ НЕ ПРИМЕНЯЕТСЯ: стенд проверяет только РАЗБОР объекта ошибки. Он не проверяет,
|
|
15
|
+
// что отказ вообще случается, и не проверяет доставку строки в журнал.
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from 'node:fs'
|
|
18
|
+
|
|
19
|
+
const SRC = process.env.SVYAZ_SRC || new URL('../src/index.js', import.meta.url).pathname
|
|
20
|
+
let text
|
|
21
|
+
try { text = readFileSync(SRC, 'utf8') } catch (e) {
|
|
22
|
+
console.log(`СЛЕПОТА: предмет ${SRC} не читается: ${e?.message ?? e}`); process.exit(2)
|
|
23
|
+
}
|
|
24
|
+
const m = text.match(/function errDetail\(e\) \{[\s\S]*?\n \}/)
|
|
25
|
+
if (!m) { console.log('СЛЕПОТА: функция errDetail в предмете не найдена — вырезать нечего'); process.exit(2) }
|
|
26
|
+
let errDetail
|
|
27
|
+
try { errDetail = new Function(m[0] + '; return errDetail;')() }
|
|
28
|
+
catch (e) { console.log(`СЛЕПОТА: вырезанное не собирается: ${e?.message ?? e}`); process.exit(2) }
|
|
29
|
+
|
|
30
|
+
let ok = 0, bed = 0
|
|
31
|
+
const t = (imya, delo) => { try { delo(); ok++; console.log(` ok ${imya}`) }
|
|
32
|
+
catch (e) { bed++; console.log(` FAIL ${imya} — ${e?.message ?? e}`) } }
|
|
33
|
+
const nePusto = (v, chto) => {
|
|
34
|
+
if (typeof v !== 'string' || v.trim() === '')
|
|
35
|
+
throw new Error(`${chto}: причина пуста — «${v}»`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
t('cause отсутствует → «не указана», а не пустота', () => {
|
|
39
|
+
const r = errDetail(new Error('fetch failed'))
|
|
40
|
+
nePusto(r, 'нет cause'); if (!/не указана/.test(r)) throw new Error(`ждали «не указана», получили «${r}»`)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// 🔴 ГЛАВНАЯ: ровно тот случай, что дал 125 пустых строк в бою.
|
|
44
|
+
t('cause.message ПУСТАЯ СТРОКА → причина всё равно названа', () => {
|
|
45
|
+
const c = Object.assign(new Error(''), { code: 'ECONNRESET' })
|
|
46
|
+
const r = errDetail(Object.assign(new Error('fetch failed'), { cause: c }))
|
|
47
|
+
nePusto(r, 'пустой message')
|
|
48
|
+
if (!/ECONNRESET/.test(r)) throw new Error(`код не попал в причину: «${r}»`)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
t('AggregateError с пустым message → названа и она сама, и её внутренние', () => {
|
|
52
|
+
const vnutri = [Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' })]
|
|
53
|
+
const c = new AggregateError(vnutri, '')
|
|
54
|
+
const r = errDetail(Object.assign(new Error('fetch failed'), { cause: c }))
|
|
55
|
+
nePusto(r, 'AggregateError')
|
|
56
|
+
if (!/AggregateError/.test(r)) throw new Error(`класс ошибки не назван: «${r}»`)
|
|
57
|
+
if (!/ECONNREFUSED/.test(r)) throw new Error(`внутренние причины потеряны: «${r}»`)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
t('обычная причина с message → она и печатается', () => {
|
|
61
|
+
const c = Object.assign(new Error('getaddrinfo ENOTFOUND api.telegram.org'), { code: 'ENOTFOUND' })
|
|
62
|
+
const r = errDetail(Object.assign(new Error('fetch failed'), { cause: c }))
|
|
63
|
+
if (!/ENOTFOUND/.test(r)) throw new Error(`потеряно: «${r}»`)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Отличать «объект есть, описания нет» от «cause отсутствует» — разные состояния.
|
|
67
|
+
t('cause без единого поля → сказано, что описания нет, а не пустота', () => {
|
|
68
|
+
const r = errDetail(Object.assign(new Error('fetch failed'), { cause: Object.create(null) }))
|
|
69
|
+
nePusto(r, 'пустой объект')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
console.log(`итог: ${ok} из ${ok + bed}`)
|
|
73
|
+
process.exit(bed === 0 ? 0 : 1)
|