pixivflow 2.27.0 → 2.28.0

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.
@@ -171,6 +171,11 @@ class HttpMultipartDelivery {
171
171
  work_type: t.workType,
172
172
  status: t.status,
173
173
  work_id: t.workId ?? null,
174
+ terminal_reason_code: t.terminal_reason_code ?? null,
175
+ reason: t.reason ?? null,
176
+ stage: t.stage ?? null,
177
+ retryable: t.retryable ?? null,
178
+ operator_hint: t.operator_hint ?? null,
174
179
  })),
175
180
  }
176
181
  : { text: request.text, idempotency_key: request.idempotencyKey };
@@ -142,6 +142,10 @@ export interface DeliveryNotificationRequest {
142
142
  terminal_reason_code?: string | null;
143
143
  /** User-facing business reason message for the failure. */
144
144
  reason?: string | null;
145
+ stage?: string | null;
146
+ /** Whether a later/manual attempt may succeed; terminal does not mean auto-retry pending. */
147
+ retryable?: boolean | null;
148
+ operator_hint?: string | null;
145
149
  }>;
146
150
  };
147
151
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NotificationPolicy = void 0;
4
4
  const DeliveryService_1 = require("../delivery/DeliveryService");
5
+ const TargetOutcome_1 = require("../scheduler/TargetOutcome");
5
6
  const SlotCoordinator_1 = require("../scheduler/SlotCoordinator");
6
7
  const logger_1 = require("../logger");
7
8
  /**
@@ -130,15 +131,22 @@ class NotificationPolicy {
130
131
  },
131
132
  }
132
133
  : {}),
133
- targets: rows.map((r) => ({
134
- targetId: r.targetId,
135
- workType: r.workType,
136
- status: r.status,
137
- workId: r.workId,
138
- error: r.error,
139
- terminal_reason_code: r.terminal_reason_code ?? null,
140
- reason: r.reason ?? null,
141
- })),
134
+ targets: rows.map((r) => {
135
+ const code = r.terminal_reason_code;
136
+ const operational = code ? (0, TargetOutcome_1.operationalReasonForCode)(code, r.reason) : null;
137
+ return {
138
+ targetId: r.targetId,
139
+ workType: r.workType,
140
+ status: r.status,
141
+ workId: r.workId,
142
+ error: r.error,
143
+ terminal_reason_code: code ?? null,
144
+ reason: operational?.message ?? r.reason ?? null,
145
+ stage: operational?.stage ?? null,
146
+ retryable: operational?.retryable ?? null,
147
+ operator_hint: operational?.operatorHint ?? null,
148
+ };
149
+ }),
142
150
  });
143
151
  }
144
152
  }
@@ -179,6 +187,8 @@ class NotificationPolicy {
179
187
  ...cell, label: cell.targetId,
180
188
  workType: targets.find((target) => target.id === cell.targetId)?.type ?? 'unknown',
181
189
  error: cell.error ?? null,
190
+ terminal_reason_code: cell.terminalReasonCode ?? null,
191
+ reason: cell.terminalReasonMessage ?? null,
182
192
  })));
183
193
  }
184
194
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow",
4
- "version": "2.27.0",
4
+ "version": "2.28.0",
5
5
  "private": true
6
6
  }
@@ -277,6 +277,19 @@ export interface TerminalReason {
277
277
  /** Business-language message an operator/reviewer reads directly. */
278
278
  message: string;
279
279
  }
280
+ /** Operator-facing recovery semantics derived from the stable reason code.
281
+ *
282
+ * These fields are deliberately derived instead of persisted as a second
283
+ * failure record. The durable SSOT remains the cell's terminal reason code;
284
+ * wording and policy can evolve without rewriting historical slot rows.
285
+ */
286
+ export interface OperationalReason extends TerminalReason {
287
+ stage: 'acquisition' | 'download' | 'delivery' | 'execution' | 'configuration';
288
+ /** A later/manual attempt may succeed; this does not mean auto-retry is pending. */
289
+ retryable: boolean;
290
+ operatorHint: string;
291
+ }
292
+ export declare function operationalReasonForCode(code: string, message?: string | null): OperationalReason | null;
280
293
  /** User-facing business messages — never stack traces, paths, SQL or tokens. */
281
294
  export declare const TERMINAL_REASON_MESSAGES: Record<TerminalReasonCode, string>;
282
295
  /**
@@ -11,6 +11,7 @@ exports.hasTransientFailure = hasTransientFailure;
11
11
  exports.mergeScanSummaries = mergeScanSummaries;
12
12
  exports.classifyCandidateFailure = classifyCandidateFailure;
13
13
  exports.classifyJobLevelOutage = classifyJobLevelOutage;
14
+ exports.operationalReasonForCode = operationalReasonForCode;
14
15
  exports.terminalReasonFor = terminalReasonFor;
15
16
  /**
16
17
  * A scan that attempted nothing. The real pipeline always reports its own scan;
@@ -226,6 +227,34 @@ function classifyJobLevelOutage(error) {
226
227
  const failure = classifyCandidateFailure(error, '');
227
228
  return failure.scope === 'job' ? failure.outage : null;
228
229
  }
230
+ const OPERATIONAL_REASON_POLICY = {
231
+ no_candidate: { stage: 'acquisition', retryable: true, operatorHint: '可等待下次任务,或使用“放宽条件重试”。' },
232
+ duplicate_exhausted: { stage: 'acquisition', retryable: true, operatorHint: '候选均已处理,可等待新作品或放宽条件重试。' },
233
+ filter_exhausted: { stage: 'acquisition', retryable: true, operatorHint: '检查筛选条件,或使用“放宽条件重试”。' },
234
+ download_timeout: { stage: 'download', retryable: true, operatorHint: '可稍后重试;若持续发生,请检查 Pixiv 连接。' },
235
+ download_failed: { stage: 'download', retryable: true, operatorHint: '可稍后重试;若持续发生,请检查网络与存储空间。' },
236
+ metadata_failed: { stage: 'acquisition', retryable: true, operatorHint: '可稍后重试;若只影响单个作品,请检查作品是否已删除或转为私密。' },
237
+ rate_limited: { stage: 'acquisition', retryable: true, operatorHint: '等待限流窗口结束后再重试。' },
238
+ auth_failed: { stage: 'acquisition', retryable: false, operatorHint: '检查并刷新对应 Pixiv 账号的认证状态。' },
239
+ remote_http_error: { stage: 'acquisition', retryable: true, operatorHint: '可稍后重试;若持续发生,请检查 Pixiv 服务状态。' },
240
+ delivery_failed: { stage: 'delivery', retryable: true, operatorHint: '检查 TelePost 可达性与投稿接口状态后重试。' },
241
+ telepost_rejected: { stage: 'delivery', retryable: false, operatorHint: '检查投稿内容和 TelePost 接口契约。' },
242
+ telegram_failed: { stage: 'delivery', retryable: true, operatorHint: '检查 Telegram 可达性与 Bot 权限后重试。' },
243
+ network_error: { stage: 'execution', retryable: true, operatorHint: '检查执行端网络;恢复后可重试。' },
244
+ execution_timeout: { stage: 'execution', retryable: true, operatorHint: '检查执行耗时和资源使用后重试。' },
245
+ configuration_error: { stage: 'configuration', retryable: false, operatorHint: '修正配置并完成校验后再重试。' },
246
+ internal_error: { stage: 'execution', retryable: false, operatorHint: '查看对应执行记录;若重复发生,请提交诊断信息。' },
247
+ };
248
+ function operationalReasonForCode(code, message) {
249
+ if (!(code in OPERATIONAL_REASON_POLICY))
250
+ return null;
251
+ const knownCode = code;
252
+ return {
253
+ code: knownCode,
254
+ message: message?.trim() || exports.TERMINAL_REASON_MESSAGES[knownCode],
255
+ ...OPERATIONAL_REASON_POLICY[knownCode],
256
+ };
257
+ }
229
258
  /** User-facing business messages — never stack traces, paths, SQL or tokens. */
230
259
  exports.TERMINAL_REASON_MESSAGES = {
231
260
  no_candidate: '没有找到合适的新作品',
@@ -317,6 +346,9 @@ function classifyFailedReason(message, scan) {
317
346
  ? { code: 'download_timeout', message: exports.TERMINAL_REASON_MESSAGES.download_timeout }
318
347
  : { code: 'execution_timeout', message: exports.TERMINAL_REASON_MESSAGES.execution_timeout };
319
348
  }
349
+ if (/\b(?:download|image fetch|image write)\b.{0,80}\b(?:fail|failed|error|unable)\b|\b(?:failed|unable)\b.{0,40}\b(?:download|fetch image|write image)\b/i.test(text)) {
350
+ return { code: 'download_failed', message: exports.TERMINAL_REASON_MESSAGES.download_failed };
351
+ }
320
352
  if (/502|503|504|bad gateway|service unavailable|server error|5\d\d/i.test(text)) {
321
353
  return { code: 'remote_http_error', message: exports.TERMINAL_REASON_MESSAGES.remote_http_error };
322
354
  }
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BUILD = void 0;
4
4
  // GENERATED by scripts/write-version.js — do not edit manually.
5
- exports.BUILD = { version: '2.27.0', commit: '491a2a8f7abd' };
5
+ exports.BUILD = { version: '2.28.0', commit: 'baec8d0803d6' };
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow-webui-backend",
4
- "version": "2.27.0",
4
+ "version": "2.28.0",
5
5
  "description": "PixivFlow WebUI Backend - CommonJS module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixivflow",
3
- "version": "2.27.0",
3
+ "version": "2.28.0",
4
4
  "description": "🎨 Pixiv 下载、筛选与自动收集工具 - 批量下载插画和小说、按标签/热度/日期筛选、定时任务与可靠 HTTP 交付 | Pixiv downloader and automation toolkit with filtering, scheduling and reliable HTTP delivery",
5
5
  "repository": {
6
6
  "type": "git",