codeksei 0.1.0 → 0.1.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.
Files changed (68) hide show
  1. package/LICENSE +661 -661
  2. package/README.en.md +109 -47
  3. package/README.md +79 -58
  4. package/bin/cyberboss.js +1 -1
  5. package/package.json +86 -86
  6. package/scripts/open_shared_wechat_thread.sh +77 -77
  7. package/scripts/open_wechat_thread.sh +108 -108
  8. package/scripts/shared-common.js +144 -144
  9. package/scripts/shared-open.js +14 -14
  10. package/scripts/shared-start.js +5 -5
  11. package/scripts/shared-status.js +27 -27
  12. package/scripts/show_shared_status.sh +45 -45
  13. package/scripts/start_shared_app_server.sh +52 -52
  14. package/scripts/start_shared_wechat.sh +94 -94
  15. package/scripts/timeline-screenshot.sh +14 -14
  16. package/src/adapters/channel/weixin/account-store.js +99 -99
  17. package/src/adapters/channel/weixin/api-v2.js +50 -50
  18. package/src/adapters/channel/weixin/api.js +169 -169
  19. package/src/adapters/channel/weixin/context-token-store.js +84 -84
  20. package/src/adapters/channel/weixin/index.js +618 -604
  21. package/src/adapters/channel/weixin/legacy.js +579 -566
  22. package/src/adapters/channel/weixin/media-mime.js +22 -22
  23. package/src/adapters/channel/weixin/media-receive.js +370 -370
  24. package/src/adapters/channel/weixin/media-send.js +102 -102
  25. package/src/adapters/channel/weixin/message-utils-v2.js +282 -282
  26. package/src/adapters/channel/weixin/message-utils.js +199 -199
  27. package/src/adapters/channel/weixin/redact.js +41 -41
  28. package/src/adapters/channel/weixin/reminder-queue-store.js +101 -101
  29. package/src/adapters/channel/weixin/sync-buffer-store.js +35 -35
  30. package/src/adapters/runtime/codex/events.js +215 -215
  31. package/src/adapters/runtime/codex/index.js +109 -104
  32. package/src/adapters/runtime/codex/message-utils.js +95 -95
  33. package/src/adapters/runtime/codex/model-catalog.js +106 -106
  34. package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -75
  35. package/src/adapters/runtime/codex/rpc-client.js +339 -339
  36. package/src/adapters/runtime/codex/session-store.js +286 -286
  37. package/src/app/channel-send-file-cli.js +57 -57
  38. package/src/app/diary-write-cli.js +236 -88
  39. package/src/app/note-sync-cli.js +2 -2
  40. package/src/app/reminder-write-cli.js +215 -210
  41. package/src/app/review-cli.js +7 -5
  42. package/src/app/system-checkin-poller.js +64 -64
  43. package/src/app/system-send-cli.js +129 -129
  44. package/src/app/timeline-event-cli.js +28 -25
  45. package/src/app/timeline-screenshot-cli.js +103 -100
  46. package/src/core/app.js +1763 -1763
  47. package/src/core/branding.js +2 -1
  48. package/src/core/command-registry.js +381 -369
  49. package/src/core/config.js +30 -14
  50. package/src/core/default-targets.js +163 -163
  51. package/src/core/durable-note-schema.js +9 -8
  52. package/src/core/instructions-template.js +17 -16
  53. package/src/core/note-sync.js +8 -7
  54. package/src/core/path-utils.js +54 -0
  55. package/src/core/project-radar.js +11 -10
  56. package/src/core/review.js +48 -50
  57. package/src/core/stream-delivery.js +1162 -983
  58. package/src/core/system-message-dispatcher.js +68 -68
  59. package/src/core/system-message-queue-store.js +128 -128
  60. package/src/core/thread-state-store.js +96 -96
  61. package/src/core/timeline-screenshot-queue-store.js +134 -134
  62. package/src/core/timezone.js +436 -0
  63. package/src/core/workspace-bootstrap.js +9 -1
  64. package/src/index.js +148 -146
  65. package/src/integrations/timeline/index.js +130 -74
  66. package/src/integrations/timeline/state-sync.js +240 -0
  67. package/templates/weixin-instructions.md +12 -38
  68. package/templates/weixin-operations.md +29 -31
@@ -1,7 +1,7 @@
1
- #!/bin/zsh
2
- set -euo pipefail
3
-
4
- ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
1
+ #!/bin/zsh
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
5
  PORT="${CODEKSEI_SHARED_PORT:-${CYBERBOSS_SHARED_PORT:-8765}}"
6
6
  STATE_DIR="${CODEKSEI_STATE_DIR:-${CYBERBOSS_STATE_DIR:-$HOME/.codeksei}}"
7
7
  if [[ ! -d "${STATE_DIR}" && -d "$HOME/.cyberboss" ]]; then
@@ -9,100 +9,100 @@ if [[ ! -d "${STATE_DIR}" && -d "$HOME/.cyberboss" ]]; then
9
9
  fi
10
10
  LOG_DIR="${STATE_DIR}/logs"
11
11
  PID_FILE="${LOG_DIR}/shared-wechat.pid"
12
-
13
- function resolve_pid_cwd() {
14
- local pid="$1"
15
- lsof -a -p "${pid}" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
16
- }
17
-
12
+
13
+ function resolve_pid_cwd() {
14
+ local pid="$1"
15
+ lsof -a -p "${pid}" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
16
+ }
17
+
18
18
  function list_bridge_processes() {
19
19
  ps -ax -o pid=,ppid=,command= | awk '/node \.\/bin\/(codeksei|cyberboss)\.js start --checkin/ { print }'
20
20
  }
21
-
22
- function find_bridge_child_pid() {
23
- local parent_pid="$1"
24
- list_bridge_processes | awk -v target_ppid="${parent_pid}" '$2 == target_ppid { print $1; exit }'
25
- }
26
-
27
- function resolve_bridge_pid() {
28
- local candidate_pid="$1"
29
- [[ -n "${candidate_pid}" ]] || return 1
30
- if ! kill -0 "${candidate_pid}" 2>/dev/null; then
31
- return 1
32
- fi
33
-
34
- local child_pid
35
- child_pid="$(find_bridge_child_pid "${candidate_pid}")"
36
- if [[ -n "${child_pid}" ]]; then
37
- echo "${child_pid}"
38
- return 0
39
- fi
40
-
41
- if [[ "$(resolve_pid_cwd "${candidate_pid}")" == "${ROOT_DIR}" ]]; then
42
- echo "${candidate_pid}"
43
- return 0
44
- fi
45
-
46
- return 1
47
- }
48
-
49
- function find_existing_bridge_pid() {
50
- if [[ -f "${PID_FILE}" ]]; then
51
- local pid_from_file
52
- pid_from_file="$(cat "${PID_FILE}" 2>/dev/null || true)"
53
- local resolved_from_file
54
- resolved_from_file="$(resolve_bridge_pid "${pid_from_file}" || true)"
55
- if [[ -n "${resolved_from_file}" ]]; then
56
- echo "${resolved_from_file}"
57
- return 0
58
- fi
59
- fi
60
-
61
- local pid
62
- while read -r pid _; do
63
- [[ -n "${pid}" ]] || continue
64
- if [[ "$(resolve_pid_cwd "${pid}")" == "${ROOT_DIR}" ]]; then
65
- echo "${pid}"
66
- return 0
67
- fi
68
- done < <(list_bridge_processes)
69
-
70
- return 1
71
- }
72
-
73
- function cleanup_pid_file() {
74
- if [[ -f "${PID_FILE}" ]]; then
75
- local current_pid
76
- current_pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
77
- if [[ "${current_pid}" == "$$" ]]; then
78
- rm -f "${PID_FILE}"
79
- fi
80
- fi
81
- }
82
-
83
- "${ROOT_DIR}/scripts/start_shared_app_server.sh"
84
- mkdir -p "${LOG_DIR}"
85
-
86
- EXISTING_PID="$(find_existing_bridge_pid || true)"
87
- if [[ -n "${EXISTING_PID}" ]]; then
88
- echo "${EXISTING_PID}" > "${PID_FILE}"
21
+
22
+ function find_bridge_child_pid() {
23
+ local parent_pid="$1"
24
+ list_bridge_processes | awk -v target_ppid="${parent_pid}" '$2 == target_ppid { print $1; exit }'
25
+ }
26
+
27
+ function resolve_bridge_pid() {
28
+ local candidate_pid="$1"
29
+ [[ -n "${candidate_pid}" ]] || return 1
30
+ if ! kill -0 "${candidate_pid}" 2>/dev/null; then
31
+ return 1
32
+ fi
33
+
34
+ local child_pid
35
+ child_pid="$(find_bridge_child_pid "${candidate_pid}")"
36
+ if [[ -n "${child_pid}" ]]; then
37
+ echo "${child_pid}"
38
+ return 0
39
+ fi
40
+
41
+ if [[ "$(resolve_pid_cwd "${candidate_pid}")" == "${ROOT_DIR}" ]]; then
42
+ echo "${candidate_pid}"
43
+ return 0
44
+ fi
45
+
46
+ return 1
47
+ }
48
+
49
+ function find_existing_bridge_pid() {
50
+ if [[ -f "${PID_FILE}" ]]; then
51
+ local pid_from_file
52
+ pid_from_file="$(cat "${PID_FILE}" 2>/dev/null || true)"
53
+ local resolved_from_file
54
+ resolved_from_file="$(resolve_bridge_pid "${pid_from_file}" || true)"
55
+ if [[ -n "${resolved_from_file}" ]]; then
56
+ echo "${resolved_from_file}"
57
+ return 0
58
+ fi
59
+ fi
60
+
61
+ local pid
62
+ while read -r pid _; do
63
+ [[ -n "${pid}" ]] || continue
64
+ if [[ "$(resolve_pid_cwd "${pid}")" == "${ROOT_DIR}" ]]; then
65
+ echo "${pid}"
66
+ return 0
67
+ fi
68
+ done < <(list_bridge_processes)
69
+
70
+ return 1
71
+ }
72
+
73
+ function cleanup_pid_file() {
74
+ if [[ -f "${PID_FILE}" ]]; then
75
+ local current_pid
76
+ current_pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
77
+ if [[ "${current_pid}" == "$$" ]]; then
78
+ rm -f "${PID_FILE}"
79
+ fi
80
+ fi
81
+ }
82
+
83
+ "${ROOT_DIR}/scripts/start_shared_app_server.sh"
84
+ mkdir -p "${LOG_DIR}"
85
+
86
+ EXISTING_PID="$(find_existing_bridge_pid || true)"
87
+ if [[ -n "${EXISTING_PID}" ]]; then
88
+ echo "${EXISTING_PID}" > "${PID_FILE}"
89
89
  echo "shared codeksei already running pid=${EXISTING_PID}"
90
- exit 0
91
- fi
92
-
93
- BRIDGE_PID=""
94
- function shutdown_bridge() {
95
- if [[ -n "${BRIDGE_PID}" ]] && kill -0 "${BRIDGE_PID}" 2>/dev/null; then
96
- kill "${BRIDGE_PID}" 2>/dev/null || true
97
- fi
98
- cleanup_pid_file
99
- }
100
-
101
- trap shutdown_bridge EXIT INT TERM
102
- cd "${ROOT_DIR}"
90
+ exit 0
91
+ fi
92
+
93
+ BRIDGE_PID=""
94
+ function shutdown_bridge() {
95
+ if [[ -n "${BRIDGE_PID}" ]] && kill -0 "${BRIDGE_PID}" 2>/dev/null; then
96
+ kill "${BRIDGE_PID}" 2>/dev/null || true
97
+ fi
98
+ cleanup_pid_file
99
+ }
100
+
101
+ trap shutdown_bridge EXIT INT TERM
102
+ cd "${ROOT_DIR}"
103
103
  export CODEKSEI_CODEX_ENDPOINT="ws://127.0.0.1:${PORT}"
104
104
  export CYBERBOSS_CODEX_ENDPOINT="ws://127.0.0.1:${PORT}"
105
105
  node ./bin/codeksei.js start --checkin &
106
- BRIDGE_PID="$!"
107
- echo "${BRIDGE_PID}" > "${PID_FILE}"
108
- wait "${BRIDGE_PID}"
106
+ BRIDGE_PID="$!"
107
+ echo "${BRIDGE_PID}" > "${PID_FILE}"
108
+ wait "${BRIDGE_PID}"
@@ -1,15 +1,15 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
- ARGS=()
6
-
7
- for arg in "$@"; do
8
- if [[ "$arg" == "--send" ]]; then
9
- continue
10
- fi
11
- ARGS+=("$arg")
12
- done
13
-
14
- cd "$ROOT"
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+ ARGS=()
6
+
7
+ for arg in "$@"; do
8
+ if [[ "$arg" == "--send" ]]; then
9
+ continue
10
+ fi
11
+ ARGS+=("$arg")
12
+ done
13
+
14
+ cd "$ROOT"
15
15
  exec node ./bin/codeksei.js timeline screenshot "${ARGS[@]}"
@@ -1,41 +1,41 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { normalizeRouteTag } = require("./protocol");
4
-
5
- function normalizeAccountId(raw) {
6
- return String(raw || "")
7
- .trim()
8
- .toLowerCase()
9
- .replace(/[^a-z0-9._-]+/g, "-")
10
- .replace(/-+/g, "-")
11
- .replace(/^-|-$/g, "");
12
- }
13
-
14
- function ensureAccountsDir(config) {
15
- fs.mkdirSync(config.accountsDir, { recursive: true });
16
- }
17
-
18
- function resolveAccountPath(config, accountId) {
19
- return path.join(config.accountsDir, `${normalizeAccountId(accountId)}.json`);
20
- }
21
-
22
- function deleteWeixinAccount(config, accountId) {
23
- const normalized = normalizeAccountId(accountId);
24
- if (!normalized) {
25
- return false;
26
- }
27
- try {
28
- const filePath = resolveAccountPath(config, normalized);
29
- if (!fs.existsSync(filePath)) {
30
- return false;
31
- }
32
- fs.unlinkSync(filePath);
33
- return true;
34
- } catch {
35
- return false;
36
- }
37
- }
38
-
4
+
5
+ function normalizeAccountId(raw) {
6
+ return String(raw || "")
7
+ .trim()
8
+ .toLowerCase()
9
+ .replace(/[^a-z0-9._-]+/g, "-")
10
+ .replace(/-+/g, "-")
11
+ .replace(/^-|-$/g, "");
12
+ }
13
+
14
+ function ensureAccountsDir(config) {
15
+ fs.mkdirSync(config.accountsDir, { recursive: true });
16
+ }
17
+
18
+ function resolveAccountPath(config, accountId) {
19
+ return path.join(config.accountsDir, `${normalizeAccountId(accountId)}.json`);
20
+ }
21
+
22
+ function deleteWeixinAccount(config, accountId) {
23
+ const normalized = normalizeAccountId(accountId);
24
+ if (!normalized) {
25
+ return false;
26
+ }
27
+ try {
28
+ const filePath = resolveAccountPath(config, normalized);
29
+ if (!fs.existsSync(filePath)) {
30
+ return false;
31
+ }
32
+ fs.unlinkSync(filePath);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
39
  function saveWeixinAccount(config, rawAccountId, update) {
40
40
  ensureAccountsDir(config);
41
41
  const accountId = normalizeAccountId(rawAccountId);
@@ -53,26 +53,26 @@ function saveWeixinAccount(config, rawAccountId, update) {
53
53
  : normalizeRouteTag(existing.routeTag || config.weixinRouteTag),
54
54
  savedAt: new Date().toISOString(),
55
55
  };
56
- fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf8");
57
- try {
58
- fs.chmodSync(filePath, 0o600);
59
- } catch {
60
- // best effort
61
- }
62
- return next;
63
- }
64
-
65
- function loadWeixinAccount(config, accountId) {
66
- const normalized = normalizeAccountId(accountId);
67
- if (!normalized) {
68
- return null;
69
- }
70
- try {
71
- const raw = fs.readFileSync(resolveAccountPath(config, normalized), "utf8");
72
- const parsed = JSON.parse(raw);
73
- if (!parsed || typeof parsed !== "object") {
74
- return null;
75
- }
56
+ fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf8");
57
+ try {
58
+ fs.chmodSync(filePath, 0o600);
59
+ } catch {
60
+ // best effort
61
+ }
62
+ return next;
63
+ }
64
+
65
+ function loadWeixinAccount(config, accountId) {
66
+ const normalized = normalizeAccountId(accountId);
67
+ if (!normalized) {
68
+ return null;
69
+ }
70
+ try {
71
+ const raw = fs.readFileSync(resolveAccountPath(config, normalized), "utf8");
72
+ const parsed = JSON.parse(raw);
73
+ if (!parsed || typeof parsed !== "object") {
74
+ return null;
75
+ }
76
76
  return {
77
77
  accountId: normalized,
78
78
  rawAccountId: typeof parsed.rawAccountId === "string" ? parsed.rawAccountId : "",
@@ -85,51 +85,51 @@ function loadWeixinAccount(config, accountId) {
85
85
  savedAt: typeof parsed.savedAt === "string" ? parsed.savedAt : "",
86
86
  };
87
87
  } catch {
88
- return null;
89
- }
90
- }
91
-
92
- function listWeixinAccounts(config) {
93
- ensureAccountsDir(config);
94
- const files = fs.readdirSync(config.accountsDir, { withFileTypes: true });
95
- return files
96
- .filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !entry.name.endsWith(".context-tokens.json"))
97
- .map((entry) => loadWeixinAccount(config, entry.name.slice(0, -5)))
98
- .filter(Boolean)
99
- .sort((left, right) => String(right.savedAt || "").localeCompare(String(left.savedAt || "")));
100
- }
101
-
102
- function resolveSelectedAccount(config) {
103
- if (config.accountId) {
104
- const account = loadWeixinAccount(config, config.accountId);
105
- if (!account) {
106
- throw new Error(`未找到微信账号: ${config.accountId}`);
107
- }
108
- if (!account.token) {
109
- throw new Error(`微信账号缺少 token: ${account.accountId},请重新执行 login`);
110
- }
111
- return account;
112
- }
113
- const accounts = listWeixinAccounts(config);
114
- if (!accounts.length) {
115
- throw new Error("当前没有已保存的微信账号,请先执行 `npm run login`");
116
- }
88
+ return null;
89
+ }
90
+ }
91
+
92
+ function listWeixinAccounts(config) {
93
+ ensureAccountsDir(config);
94
+ const files = fs.readdirSync(config.accountsDir, { withFileTypes: true });
95
+ return files
96
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !entry.name.endsWith(".context-tokens.json"))
97
+ .map((entry) => loadWeixinAccount(config, entry.name.slice(0, -5)))
98
+ .filter(Boolean)
99
+ .sort((left, right) => String(right.savedAt || "").localeCompare(String(left.savedAt || "")));
100
+ }
101
+
102
+ function resolveSelectedAccount(config) {
103
+ if (config.accountId) {
104
+ const account = loadWeixinAccount(config, config.accountId);
105
+ if (!account) {
106
+ throw new Error(`未找到微信账号: ${config.accountId}`);
107
+ }
108
+ if (!account.token) {
109
+ throw new Error(`微信账号缺少 token: ${account.accountId},请重新执行 login`);
110
+ }
111
+ return account;
112
+ }
113
+ const accounts = listWeixinAccounts(config);
114
+ if (!accounts.length) {
115
+ throw new Error("当前没有已保存的微信账号,请先执行 `npm run login`");
116
+ }
117
117
  if (accounts.length > 1) {
118
118
  const accountIds = accounts.map((account) => account.accountId).join(", ");
119
119
  throw new Error(`检测到多个微信账号,请设置 CODEKSEI_ACCOUNT_ID(或旧的 CYBERBOSS_ACCOUNT_ID)。可选值: ${accountIds}`);
120
120
  }
121
- if (!accounts[0].token) {
122
- throw new Error(`微信账号缺少 token: ${accounts[0].accountId},请重新执行 login`);
123
- }
124
- return accounts[0];
125
- }
126
-
127
- module.exports = {
128
- deleteWeixinAccount,
129
- listWeixinAccounts,
130
- loadWeixinAccount,
131
- normalizeAccountId,
132
- resolveAccountPath,
133
- resolveSelectedAccount,
134
- saveWeixinAccount,
135
- };
121
+ if (!accounts[0].token) {
122
+ throw new Error(`微信账号缺少 token: ${accounts[0].accountId},请重新执行 login`);
123
+ }
124
+ return accounts[0];
125
+ }
126
+
127
+ module.exports = {
128
+ deleteWeixinAccount,
129
+ listWeixinAccounts,
130
+ loadWeixinAccount,
131
+ normalizeAccountId,
132
+ resolveAccountPath,
133
+ resolveSelectedAccount,
134
+ saveWeixinAccount,
135
+ };
@@ -7,15 +7,15 @@ const DEFAULT_API_TIMEOUT_MS = 15_000;
7
7
  const DEFAULT_CONFIG_TIMEOUT_MS = 10_000;
8
8
  const MAX_RESPONSE_BODY_BYTES = 64 << 20;
9
9
  const CHANNEL_VERSION = PRIMARY_CHANNEL_VERSION;
10
-
11
- function buildBaseInfo() {
12
- return { channel_version: CHANNEL_VERSION };
13
- }
14
-
10
+
11
+ function buildBaseInfo() {
12
+ return { channel_version: CHANNEL_VERSION };
13
+ }
14
+
15
15
  function ensureTrailingSlash(url) {
16
16
  return url.endsWith("/") ? url : `${url}/`;
17
17
  }
18
-
18
+
19
19
  async function apiPost({
20
20
  baseUrl,
21
21
  endpoint,
@@ -38,19 +38,19 @@ async function apiPost({
38
38
  body,
39
39
  signal: controller.signal,
40
40
  });
41
- const raw = await response.text();
42
- if (Buffer.byteLength(raw, "utf8") > MAX_RESPONSE_BODY_BYTES) {
43
- throw new Error(`${label} response body exceeds ${MAX_RESPONSE_BODY_BYTES} bytes`);
44
- }
45
- if (!response.ok) {
46
- throw new Error(`${label} http ${response.status}: ${truncateForLog(raw, 512)}`);
47
- }
48
- return raw;
49
- } finally {
50
- clearTimeout(timer);
51
- }
52
- }
53
-
41
+ const raw = await response.text();
42
+ if (Buffer.byteLength(raw, "utf8") > MAX_RESPONSE_BODY_BYTES) {
43
+ throw new Error(`${label} response body exceeds ${MAX_RESPONSE_BODY_BYTES} bytes`);
44
+ }
45
+ if (!response.ok) {
46
+ throw new Error(`${label} http ${response.status}: ${truncateForLog(raw, 512)}`);
47
+ }
48
+ return raw;
49
+ } finally {
50
+ clearTimeout(timer);
51
+ }
52
+ }
53
+
54
54
  function parseJson(raw, label) {
55
55
  try {
56
56
  return JSON.parse(raw);
@@ -67,12 +67,12 @@ function assertApiSuccess(parsed, label) {
67
67
  }
68
68
  return parsed;
69
69
  }
70
-
71
- function truncateForLog(value, max) {
72
- const text = typeof value === "string" ? value : String(value || "");
73
- return text.length <= max ? text : `${text.slice(0, max)}…`;
74
- }
75
-
70
+
71
+ function truncateForLog(value, max) {
72
+ const text = typeof value === "string" ? value : String(value || "");
73
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
74
+ }
75
+
76
76
  async function getUpdatesV2({
77
77
  baseUrl,
78
78
  token,
@@ -84,11 +84,11 @@ async function getUpdatesV2({
84
84
  const payload = JSON.stringify({
85
85
  get_updates_buf: getUpdatesBuf,
86
86
  base_info: buildBaseInfo(),
87
- });
88
- try {
89
- const raw = await apiPost({
90
- baseUrl,
91
- endpoint: "ilink/bot/getupdates",
87
+ });
88
+ try {
89
+ const raw = await apiPost({
90
+ baseUrl,
91
+ endpoint: "ilink/bot/getupdates",
92
92
  token,
93
93
  body: payload,
94
94
  timeoutMs,
@@ -98,16 +98,16 @@ async function getUpdatesV2({
98
98
  });
99
99
  return parseJson(raw, "getUpdates");
100
100
  } catch (error) {
101
- if (error instanceof Error && error.name === "AbortError") {
102
- return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
103
- }
104
- if (String(error?.message || "").includes("aborted")) {
105
- return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
106
- }
107
- throw error;
108
- }
109
- }
110
-
101
+ if (error instanceof Error && error.name === "AbortError") {
102
+ return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
103
+ }
104
+ if (String(error?.message || "").includes("aborted")) {
105
+ return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
106
+ }
107
+ throw error;
108
+ }
109
+ }
110
+
111
111
  async function sendTextV2({
112
112
  baseUrl,
113
113
  token,
@@ -121,16 +121,16 @@ async function sendTextV2({
121
121
  if (!String(contextToken || "").trim()) {
122
122
  throw new Error("weixin-v2 sendText requires contextToken");
123
123
  }
124
- const itemList = [];
125
- if (String(text || "").trim()) {
126
- itemList.push({
127
- type: 1,
128
- text_item: { text: String(text) },
129
- });
130
- }
131
- if (!itemList.length) {
132
- throw new Error("weixin-v2 sendText requires non-empty text");
133
- }
124
+ const itemList = [];
125
+ if (String(text || "").trim()) {
126
+ itemList.push({
127
+ type: 1,
128
+ text_item: { text: String(text) },
129
+ });
130
+ }
131
+ if (!itemList.length) {
132
+ throw new Error("weixin-v2 sendText requires non-empty text");
133
+ }
134
134
  return sendMessageV2({
135
135
  baseUrl,
136
136
  token,