@teamlearners/clawops 0.33.0 → 0.35.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.
package/README.md CHANGED
@@ -413,7 +413,13 @@ await messageService.send({ to: '01012345678', from: '07052358010', text: '인
413
413
  알림톡도 솔라피로 갑니다.
414
414
 
415
415
  `type` 을 `SMS`/`LMS` 로 지정하면 그대로 따르고, 지정하지 않고 vendor 옵션도 없으면 서버와 같은
416
- 규칙으로 고릅니다 — `subject` 가 있으면 `lms`, 본문이 200 byte(UTF-8)를 넘으면 `lms`, 아니면 `sms`.
416
+ 규칙으로 고릅니다 — `subject` 가 있으면 `lms`, 본문이 **EUC-KR 90 byte(한글 45자)** 를 넘으면
417
+ `lms`, 아니면 `sms`. **솔라피의 SMS 기준과 같습니다**(솔라피 상태코드 `1029`·`1031`).
418
+ 기존 분기 로직을 그대로 옮기셔도 됩니다.
419
+
420
+ ⚠️ `type: 'SMS'` 로 **명시**한 본문이 90 byte 를 넘으면 ClawOps 는 `400` 으로 거절합니다.
421
+ 통신사는 이런 본문을 거절하지 않고 **90 byte 에서 잘라서 보내므로**, 잘린 문자가 나가는 대신
422
+ 에러를 돌려줍니다. 길이가 런타임에 정해진다면 `type` 을 생략하십시오 — 알아서 `lms` 로 올라갑니다.
417
423
 
418
424
  `imageId` 는 솔라피에 업로드된 파일 ID 라 ClawOps 로 옮길 수 없습니다. 이미지가 붙은 메시지는
419
425
  조용히 텍스트만 보내지 않고 **에러를 던집니다**. 첨부가 필요하면 `client.messages.create` 의
@@ -487,14 +493,35 @@ const messageService = new ClawOpsMessageService({
487
493
  mode: 'sweep', // 리포트를 주기적으로 훑는다
488
494
  intervalMs: 5 * 60_000, // 기본 5분
489
495
  lookbackMs: 60 * 60_000, // 커서가 없을 때 거슬러 볼 구간. 기본 1시간
490
- on: ['3104', '3107', '3102'], // 기본값 (수신자 사유만)
496
+ except: RECOMMENDED_EXCLUDED_CODES, // 기본은 3XXX 전부. 덮으면 안 되는 것만 뺀다
491
497
  types: ['ATA'], // 훑을 타입. 친구톡까지 보려면 ['ATA','CTA','CTI']
492
498
  onFallback: (e) => logger.info({ messageId: e.messageId, statusCode: e.statusCode }, '대체발송'),
493
499
  onBlocked: (e) => logger.warn({ messageId: e.messageId, reason: e.reason }, '대체발송 못 함'),
500
+ onError: (e) => logger.error({ err: e }, '스윕 실패'), // ← 지정하지 않으면 조용히 넘어갑니다
494
501
  },
495
502
  });
496
503
  ```
497
504
 
505
+ ⚠️ **`onError` 를 지정하지 않으면 스윕이 통째로 실패해도 아무 소리가 나지 않습니다.** 솔라피
506
+ 조회가 막히거나 자격증명이 만료되면 다음 주기에 다시 시도할 뿐이고, 그동안 대체발송은
507
+ 한 건도 나가지 않습니다. 실패가 계속되면 **문자가 안 나가는데 로그도 조용한** 상태가 되므로,
508
+ 운영에 올리실 때는 반드시 지정하십시오.
509
+
510
+ `onError` 는 스윕 **전체**가 실패했을 때만 불립니다. 개별 건의 실패는 `onBlocked` 로 갑니다
511
+ (문구를 만들지 못했거나 ClawOps 가 거절한 경우) — 한 건이 실패해도 나머지는 그대로 진행됩니다.
512
+
513
+ 커서를 프로세스 밖에 보관하시려면 `initialCursor` 로 넣고 `onCursor` 로 받으십시오. 재시작 후
514
+ 같은 구간을 다시 훑는 것을 줄여 줍니다(멱등키가 있어 중복 발송은 어차피 막히지만, 요청은 아낍니다).
515
+
516
+ ```typescript
517
+ fallback: {
518
+ enabled: true,
519
+ mode: 'sweep',
520
+ initialCursor: await loadCursor(), // 없으면 lookbackMs 만큼 거슬러 본다
521
+ onCursor: (cursor) => saveCursor(cursor), // 직렬화 가능한 값만 담겨 있습니다
522
+ }
523
+ ```
524
+
498
525
  **켜기만 하면 됩니다.** 커서·저장소·크론이 필요 없습니다. 발송할 때 `customFields` 에 마커를
499
526
  심어 두고, 스윕이 그 마커가 있는 실패 건만 골라 문자를 보냅니다. 고객이 솔라피로 직접 보낸
500
527
  알림톡은 마커가 없어 건드리지 않습니다.
@@ -507,10 +534,37 @@ const messageService = new ClawOpsMessageService({
507
534
  ClawOps 가 대체 문자를 거절하면 `onBlocked` 에 `reason: 'send_rejected'` 로 알리고, 다음 스윕이
508
535
  다시 시도합니다(멱등키가 같아 중복 발송은 되지 않습니다).
509
536
 
510
- 기본 대상은 **수신자 사유만**입니다. 설정 오류(`3101` 발신프로필 무효 · `3105` 미등록 템플릿 ·
511
- `3106` 유효하지 않은 채널)를 문자로 덮으면 알림톡이 깨진 걸 모르게 되고, `3108`(발송 가능 시간
512
- 아님)을 대체하면 21시 이후 발송이 되어 야간 규제에 걸립니다. 이런 건은 보내지 않고
513
- `onBlocked` `reason: 'code_not_eligible'` **알리기만** 합니다.
537
+ ##### 어떤 코드를 대체발송할지
538
+
539
+ **기본은 3XXX 전부입니다.** 코드를 골라 담지 않았습니다 알림톡이라고 `31xx` 오는
540
+ 아니기 때문입니다. 실측에서 알림톡 건에 `3058`(전송경로 없음)이 돌아왔습니다. 목록을 추리면
541
+ 그렇게 새는 코드가 조용히 미발송이 됩니다.
542
+
543
+ 대신 **문자로 덮으면 안 되는 코드가 섞여 있습니다.** `RECOMMENDED_EXCLUDED_CODES` 를
544
+ `except` 로 넘겨 빼시는 것을 권합니다.
545
+
546
+ | 군 | 코드 | 덮으면 생기는 일 |
547
+ |---|---|---|
548
+ | 수신거부 | `3061` | 080 수신거부한 분께 문자가 나갑니다 |
549
+ | 스팸·발신번호 변작 차단 | `3054` `3055` `3059` `3112` `3113` | 막힌 발송을 문자로 우회하는 셈이 됩니다 |
550
+ | 설정 오류 | `3013` `3101` `3103` `3105` `3106` `3109` `3117` | 전건이 실패하는데 문자가 덮어, 알림톡이 깨진 걸 오래 모르게 됩니다. 단가도 조용히 올라갑니다 |
551
+ | 시간 규제 | `3108` | 발송 가능 시간이 아닌데 문자로 나갑니다 |
552
+
553
+ ```typescript
554
+ import { RECOMMENDED_EXCLUDED_CODES } from '@teamlearners/clawops/solapi';
555
+
556
+ fallback: {
557
+ enabled: true,
558
+ mode: 'sweep',
559
+ except: RECOMMENDED_EXCLUDED_CODES, // 권장 제외
560
+ // except: [...RECOMMENDED_EXCLUDED_CODES, '3032'], // 더 빼려면 이어 붙이십시오
561
+ }
562
+ ```
563
+
564
+ `on` 은 반대로 **대상을 좁힙니다** — 주시면 그 코드만 나갑니다. `on` 으로 좁힌 뒤 `except` 로
565
+ 다시 뺄 수도 있습니다. `on: []` 는 아무것도 보내지 않는다는 뜻이라 기본(전부)과 구별됩니다.
566
+
567
+ 빠진 코드는 조용히 사라지지 않고 `onBlocked` 에 `reason: 'code_not_eligible'` 로 **알립니다.**
514
568
 
515
569
  **상주 프로세스가 없다면**(서버리스·크론) 같은 일을 하는 함수를 직접 부르십시오.
516
570
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkZCUZCZTF_cjs = require('../chunk-ZCUZCZTF.cjs');
3
+ var chunkNT6ZS3TQ_cjs = require('../chunk-NT6ZS3TQ.cjs');
4
4
  var chunkTVDUOKJD_cjs = require('../chunk-TVDUOKJD.cjs');
5
5
  var chunkQD2ZY4UP_cjs = require('../chunk-QD2ZY4UP.cjs');
6
6
  var fs = require('fs');
@@ -729,7 +729,7 @@ var MAX_ERROR_MESSAGE_LENGTH = 200;
729
729
  function getSdkInfo() {
730
730
  return {
731
731
  name: "clawops-node",
732
- version: chunkZCUZCZTF_cjs.VERSION,
732
+ version: chunkNT6ZS3TQ_cjs.VERSION,
733
733
  runtime: `node/${process.versions.node}`,
734
734
  os: `${process.platform}/${os__default.default.arch()}`
735
735
  };
@@ -1474,7 +1474,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1474
1474
  constructor(options) {
1475
1475
  this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
1476
1476
  this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
1477
- this._baseUrl = options.baseUrl ?? chunkZCUZCZTF_cjs.DEFAULT_BASE_URL;
1477
+ this._baseUrl = options.baseUrl ?? chunkNT6ZS3TQ_cjs.DEFAULT_BASE_URL;
1478
1478
  this._fromNumber = options.from;
1479
1479
  this._session = options.session;
1480
1480
  this._recording = options.recording ?? false;
@@ -1,4 +1,4 @@
1
- import { DEFAULT_BASE_URL, VERSION } from '../chunk-4HYFEKBG.js';
1
+ import { DEFAULT_BASE_URL, VERSION } from '../chunk-M6BYGYJO.js';
2
2
  import { AgentError, AgentConnectionError } from '../chunk-36JABW7P.js';
3
3
  import { NOOP_LOGGER, resolveBuiltinTools, createAgentLogger, createPipelineLogger, BufferingCall, attachBuffered, ulawToPcm16, resamplePcm16, getBuiltinToolSchemas, BUILTIN_TOOL_NAMES, executeBuiltinTool, CALL_NOT_READY_RESULT, pcm16ToUlaw, applyUlawGain } from '../chunk-4YLSDMLL.js';
4
4
  export { BUILTIN_TOOL_NAMES, BuiltinTool, DECODE_TABLE, createAgentLogger, createPipelineLogger, executeBuiltinTool, getBuiltinToolSchemas, isBuiltinTool, pcm16ToUlaw, resamplePcm16, ulawToPcm16 } from '../chunk-4YLSDMLL.js';
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.33.0" ;
2
+ var VERSION = "0.35.0" ;
3
3
 
4
4
  // src/constants.ts
5
5
  var DEFAULT_BASE_URL = "https://api.claw-ops.com";
@@ -9,5 +9,5 @@ var INITIAL_RETRY_DELAY = 500;
9
9
  var MAX_RETRY_DELAY = 8e3;
10
10
 
11
11
  export { DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, INITIAL_RETRY_DELAY, MAX_RETRY_DELAY, VERSION };
12
- //# sourceMappingURL=chunk-4HYFEKBG.js.map
13
- //# sourceMappingURL=chunk-4HYFEKBG.js.map
12
+ //# sourceMappingURL=chunk-M6BYGYJO.js.map
13
+ //# sourceMappingURL=chunk-M6BYGYJO.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-4HYFEKBG.js","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-M6BYGYJO.js","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/version.ts
4
- var VERSION = "0.33.0" ;
4
+ var VERSION = "0.35.0" ;
5
5
 
6
6
  // src/constants.ts
7
7
  var DEFAULT_BASE_URL = "https://api.claw-ops.com";
@@ -16,5 +16,5 @@ exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
16
16
  exports.INITIAL_RETRY_DELAY = INITIAL_RETRY_DELAY;
17
17
  exports.MAX_RETRY_DELAY = MAX_RETRY_DELAY;
18
18
  exports.VERSION = VERSION;
19
- //# sourceMappingURL=chunk-ZCUZCZTF.cjs.map
20
- //# sourceMappingURL=chunk-ZCUZCZTF.cjs.map
19
+ //# sourceMappingURL=chunk-NT6ZS3TQ.cjs.map
20
+ //# sourceMappingURL=chunk-NT6ZS3TQ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-ZCUZCZTF.cjs","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-NT6ZS3TQ.cjs","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var chunkZCUZCZTF_cjs = require('./chunk-ZCUZCZTF.cjs');
5
+ var chunkNT6ZS3TQ_cjs = require('./chunk-NT6ZS3TQ.cjs');
6
6
  var chunkTVDUOKJD_cjs = require('./chunk-TVDUOKJD.cjs');
7
7
  var zod = require('zod');
8
8
  var crypto = require('crypto');
@@ -31,9 +31,9 @@ var APIClient = class {
31
31
  _defaultHeaders;
32
32
  constructor(options) {
33
33
  this._apiKey = options.apiKey;
34
- this._baseURL = validateBaseUrl((options.baseURL ?? chunkZCUZCZTF_cjs.DEFAULT_BASE_URL).replace(/\/+$/, ""));
35
- this._maxRetries = options.maxRetries ?? chunkZCUZCZTF_cjs.DEFAULT_MAX_RETRIES;
36
- this._timeout = options.timeout ?? chunkZCUZCZTF_cjs.DEFAULT_TIMEOUT;
34
+ this._baseURL = validateBaseUrl((options.baseURL ?? chunkNT6ZS3TQ_cjs.DEFAULT_BASE_URL).replace(/\/+$/, ""));
35
+ this._maxRetries = options.maxRetries ?? chunkNT6ZS3TQ_cjs.DEFAULT_MAX_RETRIES;
36
+ this._timeout = options.timeout ?? chunkNT6ZS3TQ_cjs.DEFAULT_TIMEOUT;
37
37
  this._fetch = options.fetch ?? globalThis.fetch;
38
38
  this._defaultHeaders = options.defaultHeaders ?? {};
39
39
  }
@@ -42,7 +42,7 @@ var APIClient = class {
42
42
  Authorization: `Bearer ${this._apiKey}`,
43
43
  "Content-Type": "application/json",
44
44
  Accept: "application/json",
45
- "User-Agent": `claw-ops-node/${chunkZCUZCZTF_cjs.VERSION}`,
45
+ "User-Agent": `claw-ops-node/${chunkNT6ZS3TQ_cjs.VERSION}`,
46
46
  ...this._defaultHeaders
47
47
  };
48
48
  if (extra) Object.assign(headers, extra);
@@ -124,7 +124,7 @@ var APIClient = class {
124
124
  return status === 408 || status === 409 || status === 429 || status >= 500;
125
125
  }
126
126
  _retryDelay(retriesTaken) {
127
- const delay = Math.min(chunkZCUZCZTF_cjs.INITIAL_RETRY_DELAY * 2 ** retriesTaken, chunkZCUZCZTF_cjs.MAX_RETRY_DELAY);
127
+ const delay = Math.min(chunkNT6ZS3TQ_cjs.INITIAL_RETRY_DELAY * 2 ** retriesTaken, chunkNT6ZS3TQ_cjs.MAX_RETRY_DELAY);
128
128
  return delay * (1 + Math.random());
129
129
  }
130
130
  _sleep(ms) {
@@ -995,7 +995,7 @@ var ClawOps = class extends APIClient {
995
995
  if (!accountId) {
996
996
  throw new chunkTVDUOKJD_cjs.ClawOpsError("accountId\uB97C \uC9C0\uC815\uD558\uAC70\uB098 CLAWOPS_ACCOUNT_ID \uD658\uACBD\uBCC0\uC218\uB97C \uC124\uC815\uD558\uC138\uC694.");
997
997
  }
998
- const baseURL = options.baseURL ?? process.env.CLAWOPS_BASE_URL ?? chunkZCUZCZTF_cjs.DEFAULT_BASE_URL;
998
+ const baseURL = options.baseURL ?? process.env.CLAWOPS_BASE_URL ?? chunkNT6ZS3TQ_cjs.DEFAULT_BASE_URL;
999
999
  super({
1000
1000
  apiKey,
1001
1001
  baseURL,
@@ -1046,7 +1046,7 @@ var client_default_default = ClawOps;
1046
1046
 
1047
1047
  Object.defineProperty(exports, "VERSION", {
1048
1048
  enumerable: true,
1049
- get: function () { return chunkZCUZCZTF_cjs.VERSION; }
1049
+ get: function () { return chunkNT6ZS3TQ_cjs.VERSION; }
1050
1050
  });
1051
1051
  Object.defineProperty(exports, "APIConnectionError", {
1052
1052
  enumerable: true,
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, VERSION, INITIAL_RETRY_DELAY, MAX_RETRY_DELAY } from './chunk-4HYFEKBG.js';
2
- export { VERSION } from './chunk-4HYFEKBG.js';
1
+ import { DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, VERSION, INITIAL_RETRY_DELAY, MAX_RETRY_DELAY } from './chunk-M6BYGYJO.js';
2
+ export { VERSION } from './chunk-M6BYGYJO.js';
3
3
  import { APITimeoutError, APIConnectionError, makeStatusError, APIResponseValidationError, ClawOpsError } from './chunk-36JABW7P.js';
4
4
  export { APIConnectionError, APIError, APIResponseValidationError, APIStatusError, APITimeoutError, AgentConnectionError, AgentError, AuthenticationError, BadRequestError, ClawOpsError, ConflictError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, ServiceUnavailableError, SolapiBridgeError, UnprocessableEntityError } from './chunk-36JABW7P.js';
5
5
  import { z } from 'zod';
@@ -18,7 +18,12 @@ async function mapWithConcurrency(items, limit, run) {
18
18
  }
19
19
 
20
20
  // src/solapi/_message-type.ts
21
- var SMS_MAX_BYTES = 200;
21
+ var SMS_MAX_EUCKR_BYTES = 90;
22
+ function euckrByteLength(text) {
23
+ let bytes = 0;
24
+ for (const ch of text) bytes += ch.codePointAt(0) < 128 ? 1 : 2;
25
+ return bytes;
26
+ }
22
27
  var normalizePhone = (value) => value.replace(/-/g, "");
23
28
  var upperType = (message) => message.type === void 0 ? "" : String(message.type).toUpperCase();
24
29
  function clawopsType(message) {
@@ -28,7 +33,7 @@ function clawopsType(message) {
28
33
  if (message.subject !== void 0 && message.subject !== null && message.subject !== "") {
29
34
  return "lms";
30
35
  }
31
- return Buffer.byteLength(message.text ?? "", "utf8") > SMS_MAX_BYTES ? "lms" : "sms";
36
+ return euckrByteLength(message.text ?? "") > SMS_MAX_EUCKR_BYTES ? "lms" : "sms";
32
37
  }
33
38
 
34
39
  // src/solapi/fallback-text.ts
@@ -123,7 +128,27 @@ async function deliverFallback(request, solapi, context) {
123
128
  // src/solapi/sweep.ts
124
129
  var FALLBACK_MARKER_FIELD = "clawopsFallback";
125
130
  var FALLBACK_MARKER_VALUE = "1";
126
- var DEFAULT_FALLBACK_CODES = ["3104", "3107", "3102"];
131
+ var RECOMMENDED_EXCLUDED_CODES = [
132
+ // 수신거부 — 문자로 대체하면 거부한 사람에게 보내는 것이 된다
133
+ "3061",
134
+ // 스팸·발신번호 변작 차단 — 막힌 발송을 문자로 우회하는 셈이 된다
135
+ "3054",
136
+ "3055",
137
+ "3059",
138
+ "3112",
139
+ "3113",
140
+ // 설정 오류 — 전건이 실패한다. 문자가 덮으면 알림톡이 깨진 걸 몇 달간 모르고,
141
+ // 단가도 알림톡에서 문자로 조용히 올라간다
142
+ "3013",
143
+ "3101",
144
+ "3103",
145
+ "3105",
146
+ "3106",
147
+ "3109",
148
+ "3117",
149
+ // 발송 가능 시간이 아님 — 문자로 대체하면 야간 발송이 된다
150
+ "3108"
151
+ ];
127
152
  var DEFAULT_SWEEP_TYPES = ["ATA"];
128
153
  var DELIVERY_FAILURE = /^3\d{3}$/;
129
154
  var IN_FLIGHT_CODE = "3000";
@@ -133,7 +158,8 @@ var MAX_PAGES = 50;
133
158
  var firstRecipient = (to) => Array.isArray(to) ? to[0] : to;
134
159
  var timeOf = (row) => row.dateUpdated ?? row.dateCreated;
135
160
  async function sweepFailedAlimtalk(options) {
136
- const targets = new Set(options.on ?? DEFAULT_FALLBACK_CODES);
161
+ const targets = options.on ? new Set(options.on) : null;
162
+ const excluded = new Set(options.except ?? []);
137
163
  const boundary = new Set(options.cursor?.seen ?? []);
138
164
  const templateCache = options.templateCache ?? /* @__PURE__ */ new Map();
139
165
  const window = options.lookbackMs ?? DEFAULT_LOOKBACK_MS;
@@ -176,7 +202,7 @@ async function sweepFailedAlimtalk(options) {
176
202
  if (row.customFields?.[FALLBACK_MARKER_FIELD] !== FALLBACK_MARKER_VALUE) continue;
177
203
  const code = String(row.statusCode ?? "");
178
204
  if (code === IN_FLIGHT_CODE || !DELIVERY_FAILURE.test(code)) continue;
179
- if (!targets.has(code)) {
205
+ if (targets !== null && !targets.has(code) || excluded.has(code)) {
180
206
  ineligible.push(row);
181
207
  continue;
182
208
  }
@@ -578,6 +604,7 @@ function startSweepLoop(options, sweep, solapi, templateCache) {
578
604
  cursor,
579
605
  lookbackMs: sweep.lookbackMs,
580
606
  on: sweep.on,
607
+ except: sweep.except,
581
608
  types: sweep.types,
582
609
  fallbackField: sweep.field,
583
610
  concurrency: options.concurrency,
@@ -648,9 +675,9 @@ Object.defineProperty(exports, "SolapiBridgeError", {
648
675
  });
649
676
  exports.CLAWOPS_FAILURE_STATUS_CODE = CLAWOPS_FAILURE_STATUS_CODE;
650
677
  exports.ClawOpsMessageService = ClawOpsMessageService;
651
- exports.DEFAULT_FALLBACK_CODES = DEFAULT_FALLBACK_CODES;
652
678
  exports.DEFAULT_FALLBACK_FIELD = DEFAULT_FALLBACK_FIELD;
653
679
  exports.FALLBACK_MARKER_FIELD = FALLBACK_MARKER_FIELD;
680
+ exports.RECOMMENDED_EXCLUDED_CODES = RECOMMENDED_EXCLUDED_CODES;
654
681
  exports.resolveFallbackText = resolveFallbackText;
655
682
  exports.sweepFailedAlimtalk = sweepFailedAlimtalk;
656
683
  //# sourceMappingURL=index.cjs.map