@quecue/dev-notify 0.2.4 → 0.2.5

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.en.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  English | [简体中文](CHANGELOG.md)
4
4
 
5
+ ## 0.2.5 — 2026-11-07
6
+
7
+ - Distinguish Telegram `getMe` failures during reply-button preparation, bridge unreadiness, and notification-delivery failures. Retain bounded API error codes and report safe delivery categories and API-acknowledged part counts, without exposing underlying exceptions or server bodies. Do not infer exact send error codes discarded by the communication library.
8
+ - Add isolated regressions for Telegram failure with successful buttonless Feishu delivery, bridge unreadiness with ordinary delivery on both channels, and recovery with buttons on both channels. No new retries, replays, or authorization.
9
+ - Cover explicit channel rejection, unknown exceptions, and successful API delivery followed by local message-mapping failure without incorrectly claiming non-delivery or resending. Windows x64 / Node 24 totals 247 tests: 246 passed, 0 failed, and 1 opt-in native credential test skipped; not macOS real-machine acceptance.
10
+ - This release improves diagnostics; it does not establish or fix the specific cause of historical missing notifications on any particular device.
11
+
5
12
  ## 0.2.4 — 2026-11-07
6
13
 
7
14
  - Recover valid bot locks belonging to confirmed exited processes so a crashed reply bridge does not permanently remove Reply to Claude buttons. Live processes, reused PIDs, unknown permissions, and damaged locks are not preempted; concurrent recovery is serialized.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  简体中文 | [English](CHANGELOG.en.md)
4
4
 
5
+ ## 0.2.5 — 2026-11-07
6
+
7
+ - 区分回复按钮的 Telegram `getMe` 身份查询失败、桥接未就绪与通知投递失败;保留有界 API 错误码,投递失败显示安全分类及 API 确认分段数,不输出底层异常或服务端正文。通讯库未保留的具体发送错误码不会猜测。
8
+ - 补充 Telegram 失败而飞书成功且无按钮、桥接未就绪仍发送双渠道普通通知、恢复后双渠道带按钮的隔离回归;不新增重试、补发或授权。
9
+ - 补充渠道明确拒绝、未知异常和 API 成功但本地消息映射失败的回归,避免误报未送达或重复发送。Windows x64/Node 24 共 247 项测试:246 通过、0 失败、1 项显式原生凭据测试跳过;不代表 macOS 实机验收。
10
+ - 本版改进故障诊断,不代表已确认或修复某台设备历史漏发的具体根因。
11
+
5
12
  ## 0.2.4 — 2026-11-07
6
13
 
7
14
  - 修复回复桥接异常退出后残留 Bot 锁导致通知不再出现“回复 Claude”按钮:仅回收格式有效且已确认进程退出的锁;活进程、PID 复用、权限未知或损坏锁均不抢占,并串行化并发恢复。
@@ -6,6 +6,7 @@ import { t } from './i18n.js';
6
6
  import { shouldNotify } from './notifications.js';
7
7
  import { transcriptEndTime } from './hook-time.js';
8
8
  import { findSessionName } from './reply-platform.js';
9
+ import { ReplyPreparationError, safeDeliveryReason, safeFailureReason } from './notification-diagnostics.js';
9
10
  import { lifecycleHook, interactionHook, attachReply, mapReplies, notificationCoverage } from './reply-hooks.js';
10
11
  export const MAX_HOOK_BYTES = 8 * 1024 * 1024;
11
12
  export async function readHookInput(input = process.stdin, limit = MAX_HOOK_BYTES) {
@@ -139,8 +140,8 @@ export async function dispatchClaudeHook(bytes, configFile, context) {
139
140
  try {
140
141
  binding = await attachReply(notification, data, configFile, options);
141
142
  }
142
- catch {
143
- context.err(t('hook.replyNotReady'));
143
+ catch (error) {
144
+ context.err(error instanceof ReplyPreparationError ? error.message : t('hook.replyNotReady'));
144
145
  }
145
146
  let covered = { telegram: [], feishu: false };
146
147
  try {
@@ -168,10 +169,12 @@ export async function dispatchClaudeHook(bytes, configFile, context) {
168
169
  mapReplies(plan, binding);
169
170
  const result = await context.send(plan, config);
170
171
  if (!result.ok)
171
- context.err(t('hook.sendIncomplete', { channel }));
172
+ context.err(t('hook.sendIncompleteDetail', {
173
+ channel, sent: result.sent, planned: result.planned, reason: safeDeliveryReason(result.error),
174
+ }));
172
175
  }
173
- catch {
174
- context.err(t('hook.sendFailed', { channel }));
176
+ catch (error) {
177
+ context.err(t('hook.sendFailedDetail', { channel, reason: safeFailureReason(error) }));
175
178
  }
176
179
  }
177
180
  if (!channels)
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ import { startBridge, stopBridge, bridgeStatus, runBridge } from './reply-bridge
16
16
  import { updatePackage } from './update.js';
17
17
  import { postUpdate } from './maintenance.js';
18
18
  import { localeName, parseLocale, resolveLocale, setLocale, t } from './i18n.js';
19
- export const VERSION = '0.2.4';
19
+ export const VERSION = '0.2.5';
20
20
  const helpText = () => t('cli.help', { version: VERSION });
21
21
  function rawLang(argv) {
22
22
  for (let i = 0; i < argv.length; i++) {
package/dist/i18n.js CHANGED
@@ -305,7 +305,16 @@ const zh = {
305
305
  'hook.notificationFallback': 'Claude Code 正在等待你的确认或输入,请回到终端处理。',
306
306
  'hook.interactionFailed': 'Claude Code 的回复状态或交互处理失败。没有自动选择答案或批准权限,请在本机处理。',
307
307
  'hook.sessionNameFailed': '读取不到 Claude Code 会话名称,通知底部将省略主题。',
308
- 'hook.replyNotReady': '远程回复还没有就绪,仍会尝试发送普通通知。',
308
+ 'hook.replyNotReady': '回复按钮准备失败(会话、授权、本地状态或桥接检查);仍会尝试向各渠道发送普通通知,不代表 Telegram 已送达。',
309
+ 'hook.replyIdentityFailed': '回复按钮准备失败:Telegram getMe 身份查询失败({reason});仍会尝试发送不带按钮的普通通知。',
310
+ 'hook.sendIncompleteDetail': '{channel}:通知投递或发送后处理未全部完成(API 确认 {sent}/{planned} 段;{reason})。整条消息不会自动重发。',
311
+ 'hook.sendFailedDetail': '{channel}:通知发送流程失败({reason})。整条消息不会自动重发。',
312
+ 'diagnostic.network': '网络请求失败或响应无效(可能为超时或连接异常,无法进一步区分)',
313
+ 'diagnostic.rejected': '渠道明确拒绝请求(当前接口未保留具体错误码)',
314
+ 'diagnostic.unknown': '原因未分类;为避免泄密,不显示底层错误',
315
+ 'diagnostic.telegramCode': 'Telegram API 错误码 {code}',
316
+ 'diagnostic.telegramUnknown': 'Telegram API 返回失败,错误码未知',
317
+ 'diagnostic.httpCode': 'HTTP 状态码 {code}',
309
318
  'hook.coverageFailed': '没有核验到选择题通知的覆盖情况,将保留普通权限提醒。',
310
319
  'hook.sendIncomplete': '{channel}:Claude Code 通知没有完整发送。整条消息不会自动重发。',
311
320
  'hook.sendFailed': '{channel}:Claude Code 通知发送失败。请检查配置、系统凭据和网络;整条消息不会自动重发。',
@@ -803,7 +812,16 @@ secrets are never written in plaintext.`,
803
812
  'hook.notificationFallback': 'Claude Code is waiting for your confirmation or input. Return to the terminal to handle it.',
804
813
  'hook.interactionFailed': 'Claude Code reply state or interaction handling failed. No answer or permission was selected automatically; handle it locally.',
805
814
  'hook.sessionNameFailed': 'Could not read the Claude Code session name; the topic will be omitted from the notification footer.',
806
- 'hook.replyNotReady': 'Remote replies are not ready yet; a normal notification will still be attempted.',
815
+ 'hook.replyNotReady': 'Reply button preparation failed during session, authorization, local-state, or bridge checks; normal notifications will still be attempted on each channel. This does not mean Telegram delivery succeeded.',
816
+ 'hook.replyIdentityFailed': 'Reply button preparation failed: Telegram getMe identity lookup failed ({reason}); normal notifications without buttons will still be attempted.',
817
+ 'hook.sendIncompleteDetail': '{channel}: delivery or post-send processing did not fully complete (API acknowledged {sent}/{planned} parts; {reason}). The message will not be resent automatically.',
818
+ 'hook.sendFailedDetail': '{channel}: notification sending failed ({reason}). The message will not be resent automatically.',
819
+ 'diagnostic.network': 'Network request failed or response was invalid (possibly a timeout or connection failure; cannot distinguish further)',
820
+ 'diagnostic.rejected': 'The channel explicitly rejected the request (the current interface does not retain its exact error code)',
821
+ 'diagnostic.unknown': 'Unclassified reason; underlying error withheld to protect secrets',
822
+ 'diagnostic.telegramCode': 'Telegram API error code {code}',
823
+ 'diagnostic.telegramUnknown': 'Telegram API failed with an unknown error code',
824
+ 'diagnostic.httpCode': 'HTTP status {code}',
807
825
  'hook.coverageFailed': 'Could not verify question-notification coverage; the general permission reminder will be kept.',
808
826
  'hook.sendIncomplete': '{channel}: the Claude Code notification was not fully sent. The message will not be resent automatically.',
809
827
  'hook.sendFailed': '{channel}: the Claude Code notification failed to send. Check configuration, system credentials, and network; the message will not be resent automatically.',
@@ -0,0 +1,41 @@
1
+ import { HttpError, TelegramApiError, TransportError } from '@quecue/notify-core';
2
+ import { ConfigError } from './config.js';
3
+ import { t } from './i18n.js';
4
+ // 仅保留类型和有界状态码;不输出底层异常、URL、Token 或服务端正文。
5
+ export class TelegramRequestError extends ConfigError {
6
+ code;
7
+ constructor(code) {
8
+ super(t(code === 409 ? 'reply.telegramConflict' : 'reply.telegramRequestFailed'));
9
+ this.code = code;
10
+ }
11
+ }
12
+ export function safeFailureReason(error) {
13
+ if (error instanceof TelegramRequestError || error instanceof TelegramApiError) {
14
+ const code = error.code;
15
+ return Number.isSafeInteger(code) && code >= 100 && code <= 599
16
+ ? t('diagnostic.telegramCode', { code }) : t('diagnostic.telegramUnknown');
17
+ }
18
+ if (error instanceof HttpError) {
19
+ const code = error.status;
20
+ return Number.isSafeInteger(code) && code >= 100 && code <= 599
21
+ ? t('diagnostic.httpCode', { code }) : t('diagnostic.network');
22
+ }
23
+ if (error instanceof TransportError)
24
+ return t('diagnostic.network');
25
+ return t('diagnostic.unknown');
26
+ }
27
+ export class ReplyPreparationError extends ConfigError {
28
+ constructor(error) {
29
+ super(t('hook.replyIdentityFailed', { reason: safeFailureReason(error) }));
30
+ }
31
+ }
32
+ // 公共库目前只返回安全文本而非错误类型。仅匹配本地已知值,不转发任意字符串。
33
+ export function safeDeliveryReason(error) {
34
+ for (const key of ['channels.network', 'channels.noResponse', 'channels.responseTooLarge', 'channels.notJsonObject']) {
35
+ if (error === t(key))
36
+ return t('diagnostic.network');
37
+ }
38
+ if (error === t('channels.rejected'))
39
+ return t('diagnostic.rejected');
40
+ return t('diagnostic.unknown');
41
+ }
@@ -6,6 +6,7 @@ import { findSession } from './reply-platform.js';
6
6
  import { textHash } from './reply-engine.js';
7
7
  import { telegramApi, replyHint } from './reply-telegram.js';
8
8
  import { bridgeStatus, startBridge } from './reply-bridge.js';
9
+ import { ReplyPreparationError } from './notification-diagnostics.js';
9
10
  import { createInteraction, questionsContent, showInteraction, waitInteraction } from './reply-interactions.js';
10
11
  import { makePlan, deliver } from './channels.js';
11
12
  import { completeToolCall, endSessionInteractions, finishInteraction, matchingCall, registerToolCall } from './reply-outcomes.js';
@@ -115,7 +116,14 @@ export async function attachReply(message, data, file, options = {}) {
115
116
  options.err?.(t('reply.bridgeNotReadyButton'));
116
117
  return null;
117
118
  }
118
- const api = apiFor(access, options), bot = await api('getMe', {});
119
+ const api = apiFor(access, options);
120
+ let bot;
121
+ try {
122
+ bot = await api('getMe', {});
123
+ }
124
+ catch (error) {
125
+ throw new ReplyPreparationError(error);
126
+ }
119
127
  if (typeof bot?.username !== 'string' || !/^[A-Za-z0-9_]{5,64}$/.test(bot.username))
120
128
  throw new ConfigError(t('reply.botUsernameInvalid'));
121
129
  const token = randomBytes(16).toString('hex');
@@ -2,6 +2,7 @@ import { createTelegramApi, TelegramApiError } from '@quecue/notify-core';
2
2
  import { requestJson } from './channels.js';
3
3
  import { ConfigError } from './config.js';
4
4
  import { t } from './i18n.js';
5
+ import { TelegramRequestError } from './notification-diagnostics.js';
5
6
  export function telegramApi(token, request = requestJson) {
6
7
  const call = createTelegramApi(token, { request });
7
8
  return async (method, payload) => {
@@ -12,9 +13,7 @@ export function telegramApi(token, request = requestJson) {
12
13
  }
13
14
  catch (error) {
14
15
  if (error instanceof TelegramApiError) {
15
- if (error.code === 409)
16
- throw new ConfigError(t('reply.telegramConflict'));
17
- throw new ConfigError(t('reply.telegramRequestFailed'));
16
+ throw new TelegramRequestError(error.code);
18
17
  }
19
18
  throw error;
20
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quecue/dev-notify",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "跨平台开发任务通知工具,支持 Telegram 与飞书",
5
5
  "license": "MIT",
6
6
  "repository": {