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,210 +1,215 @@
1
- const crypto = require("crypto");
2
-
3
- const { resolveSelectedAccount } = require("../adapters/channel/weixin/account-store");
4
- const { loadPersistedContextTokens } = require("../adapters/channel/weixin/context-token-store");
5
- const { ReminderQueueStore } = require("../adapters/channel/weixin/reminder-queue-store");
6
- const { SessionStore } = require("../adapters/runtime/codex/session-store");
7
- const { resolvePreferredSenderId } = require("../core/default-targets");
8
-
9
- const DELAY_UNIT_MS = {
10
- s: 1_000,
11
- m: 60_000,
12
- h: 60 * 60_000,
13
- d: 24 * 60 * 60_000,
14
- };
15
- const LOCAL_TIMEZONE_OFFSET = "+08:00";
16
-
17
- async function runReminderWriteCommand(config) {
18
- const args = process.argv.slice(4);
19
- const options = parseArgs(args);
20
- const body = await resolveBody(options);
21
- if (!body) {
22
- throw new Error("提醒内容不能为空,传 --text 或通过 stdin 输入");
23
- }
24
-
25
- const dueAtMs = resolveDueAtMs(options);
26
- if (!Number.isFinite(dueAtMs) || dueAtMs <= Date.now()) {
27
- throw new Error("缺少有效时间,使用 --delay 30s|10m|1h30m|2d4h20m 或 --at 2026-04-07T21:30+08:00");
28
- }
29
-
30
- const account = resolveSelectedAccount(config);
31
- const sessionStore = new SessionStore({ filePath: config.sessionsFile });
32
- const senderId = resolvePreferredSenderId({
33
- config,
34
- accountId: account.accountId,
35
- explicitUser: options.user,
36
- sessionStore,
37
- });
38
- if (!senderId) {
39
- throw new Error("无法确定 reminder 的微信用户,传 --user 或先让唯一活跃用户和 bot 聊过一次");
40
- }
41
-
42
- const contextTokens = loadPersistedContextTokens(config, account.accountId);
43
- const contextToken = String(contextTokens[senderId] || "").trim();
44
- if (!contextToken) {
45
- throw new Error(`找不到 ${senderId} context_token,先让这个用户和 bot 聊过一次`);
46
- }
47
-
48
- const queue = new ReminderQueueStore({ filePath: config.reminderQueueFile });
49
- const reminder = queue.enqueue({
50
- id: crypto.randomUUID(),
51
- accountId: account.accountId,
52
- senderId,
53
- contextToken,
54
- text: body,
55
- dueAtMs,
56
- createdAt: new Date().toISOString(),
57
- });
58
- console.log(`reminder queued: ${reminder.id}`);
59
- }
60
-
61
- function parseArgs(args) {
62
- const options = {
63
- delay: "",
64
- at: "",
65
- text: "",
66
- user: "",
67
- useStdin: false,
68
- };
69
- for (let index = 0; index < args.length; index += 1) {
70
- const arg = args[index];
71
- if (arg === "--delay") {
72
- options.delay = String(args[index + 1] || "");
73
- index += 1;
74
- continue;
75
- }
76
- if (arg === "--at") {
77
- options.at = String(args[index + 1] || "");
78
- index += 1;
79
- continue;
80
- }
81
- if (arg === "--text") {
82
- options.text = String(args[index + 1] || "");
83
- index += 1;
84
- continue;
85
- }
86
- if (arg === "--user") {
87
- options.user = String(args[index + 1] || "");
88
- index += 1;
89
- continue;
90
- }
91
- if (arg === "--stdin") {
92
- options.useStdin = true;
93
- continue;
94
- }
95
- throw new Error(`未知参数: ${arg}`);
96
- }
97
- return options;
98
- }
99
-
100
- function resolveDueAtMs(options) {
101
- const delayMs = parseDelay(options.delay);
102
- const scheduledAtMs = parseAbsoluteTime(options.at);
103
- if (delayMs && scheduledAtMs) {
104
- throw new Error("--delay 和 --at 不能同时传");
105
- }
106
- if (delayMs) {
107
- return Date.now() + delayMs;
108
- }
109
- if (scheduledAtMs) {
110
- return scheduledAtMs;
111
- }
112
- return 0;
113
- }
114
-
115
- function parseDelay(rawValue) {
116
- const normalized = String(rawValue || "").trim().toLowerCase();
117
- if (!normalized) {
118
- return 0;
119
- }
120
-
121
- let totalMs = 0;
122
- let index = 0;
123
- while (index < normalized.length) {
124
- while (index < normalized.length && /\s/.test(normalized[index])) {
125
- index += 1;
126
- }
127
- if (index >= normalized.length) {
128
- break;
129
- }
130
-
131
- const match = normalized.slice(index).match(/^(\d+)\s*([smhd])/);
132
- if (!match) {
133
- return 0;
134
- }
135
-
136
- const amount = Number.parseInt(match[1], 10);
137
- const unitMs = DELAY_UNIT_MS[match[2]] || 0;
138
- if (!Number.isFinite(amount) || amount <= 0 || !unitMs) {
139
- return 0;
140
- }
141
-
142
- totalMs += amount * unitMs;
143
- index += match[0].length;
144
- }
145
-
146
- return totalMs > 0 ? totalMs : 0;
147
- }
148
-
149
- function parseAbsoluteTime(rawValue) {
150
- const normalized = String(rawValue || "").trim();
151
- if (!normalized) {
152
- return 0;
153
- }
154
-
155
- const normalizedIso = normalizeAbsoluteTimeString(normalized);
156
- const parsed = Date.parse(normalizedIso);
157
- return Number.isFinite(parsed) ? parsed : 0;
158
- }
159
-
160
- function normalizeAbsoluteTimeString(value) {
161
- const normalized = String(value || "").trim();
162
- if (!normalized) {
163
- return "";
164
- }
165
-
166
- if (/([zZ]|[+-]\d{2}:\d{2})$/.test(normalized)) {
167
- return normalized.replace(" ", "T");
168
- }
169
-
170
- const dateTimeMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}(?::\d{2})?)$/);
171
- if (dateTimeMatch) {
172
- return `${dateTimeMatch[1]}T${dateTimeMatch[2]}${LOCAL_TIMEZONE_OFFSET}`;
173
- }
174
-
175
- const dateOnlyMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})$/);
176
- if (dateOnlyMatch) {
177
- return `${dateOnlyMatch[1]}T09:00:00${LOCAL_TIMEZONE_OFFSET}`;
178
- }
179
-
180
- return normalized;
181
- }
182
-
183
- async function resolveBody(options) {
184
- const inlineText = normalizeBody(options.text);
185
- if (inlineText) {
186
- return inlineText;
187
- }
188
- if (!options.useStdin && process.stdin.isTTY) {
189
- return "";
190
- }
191
- return normalizeBody(await readStdin());
192
- }
193
-
194
- function readStdin() {
195
- return new Promise((resolve, reject) => {
196
- let buffer = "";
197
- process.stdin.setEncoding("utf8");
198
- process.stdin.on("data", (chunk) => {
199
- buffer += chunk;
200
- });
201
- process.stdin.on("end", () => resolve(buffer));
202
- process.stdin.on("error", reject);
203
- });
204
- }
205
-
206
- function normalizeBody(value) {
207
- return String(value || "").replace(/\r\n/g, "\n").trim();
208
- }
209
-
210
- module.exports = { runReminderWriteCommand };
1
+ const crypto = require("crypto");
2
+
3
+ const { resolveSelectedAccount } = require("../adapters/channel/weixin/account-store");
4
+ const { loadPersistedContextTokens } = require("../adapters/channel/weixin/context-token-store");
5
+ const { ReminderQueueStore } = require("../adapters/channel/weixin/reminder-queue-store");
6
+ const { SessionStore } = require("../adapters/runtime/codex/session-store");
7
+ const { resolvePreferredSenderId } = require("../core/default-targets");
8
+ const {
9
+ LEGACY_TIMELINE_TIMEZONE,
10
+ coerceLocalDateTimeToIso,
11
+ } = require("../core/timezone");
12
+
13
+ const DELAY_UNIT_MS = {
14
+ s: 1_000,
15
+ m: 60_000,
16
+ h: 60 * 60_000,
17
+ d: 24 * 60 * 60_000,
18
+ };
19
+
20
+ async function runReminderWriteCommand(config) {
21
+ const args = process.argv.slice(4);
22
+ const options = parseArgs(args);
23
+ const body = await resolveBody(options);
24
+ if (!body) {
25
+ throw new Error("提醒内容不能为空,传 --text 或通过 stdin 输入");
26
+ }
27
+
28
+ const timezone = config?.timezone || LEGACY_TIMELINE_TIMEZONE;
29
+ const dueAtMs = resolveDueAtMs(options, timezone);
30
+ if (!Number.isFinite(dueAtMs) || dueAtMs <= Date.now()) {
31
+ throw new Error(
32
+ `缺少有效时间,使用 --delay 30s|10m|1h30m|2d4h20m 或 --at ${buildAbsoluteTimeExample(timezone)}`
33
+ );
34
+ }
35
+
36
+ const account = resolveSelectedAccount(config);
37
+ const sessionStore = new SessionStore({ filePath: config.sessionsFile });
38
+ const senderId = resolvePreferredSenderId({
39
+ config,
40
+ accountId: account.accountId,
41
+ explicitUser: options.user,
42
+ sessionStore,
43
+ });
44
+ if (!senderId) {
45
+ throw new Error("无法确定 reminder 的微信用户,传 --user 或先让唯一活跃用户和 bot 聊过一次");
46
+ }
47
+
48
+ const contextTokens = loadPersistedContextTokens(config, account.accountId);
49
+ const contextToken = String(contextTokens[senderId] || "").trim();
50
+ if (!contextToken) {
51
+ throw new Error(`找不到 ${senderId} 的 context_token,先让这个用户和 bot 聊过一次`);
52
+ }
53
+
54
+ const queue = new ReminderQueueStore({ filePath: config.reminderQueueFile });
55
+ const reminder = queue.enqueue({
56
+ id: crypto.randomUUID(),
57
+ accountId: account.accountId,
58
+ senderId,
59
+ contextToken,
60
+ text: body,
61
+ dueAtMs,
62
+ createdAt: new Date().toISOString(),
63
+ });
64
+ console.log(`reminder queued: ${reminder.id}`);
65
+ }
66
+
67
+ function parseArgs(args) {
68
+ const options = {
69
+ delay: "",
70
+ at: "",
71
+ text: "",
72
+ user: "",
73
+ useStdin: false,
74
+ };
75
+ for (let index = 0; index < args.length; index += 1) {
76
+ const arg = args[index];
77
+ if (arg === "--delay") {
78
+ options.delay = String(args[index + 1] || "");
79
+ index += 1;
80
+ continue;
81
+ }
82
+ if (arg === "--at") {
83
+ options.at = String(args[index + 1] || "");
84
+ index += 1;
85
+ continue;
86
+ }
87
+ if (arg === "--text") {
88
+ options.text = String(args[index + 1] || "");
89
+ index += 1;
90
+ continue;
91
+ }
92
+ if (arg === "--user") {
93
+ options.user = String(args[index + 1] || "");
94
+ index += 1;
95
+ continue;
96
+ }
97
+ if (arg === "--stdin") {
98
+ options.useStdin = true;
99
+ continue;
100
+ }
101
+ throw new Error(`未知参数: ${arg}`);
102
+ }
103
+ return options;
104
+ }
105
+
106
+ function resolveDueAtMs(options, timezone = LEGACY_TIMELINE_TIMEZONE) {
107
+ const delayMs = parseDelay(options.delay);
108
+ const scheduledAtMs = parseAbsoluteTime(options.at, timezone);
109
+ if (delayMs && scheduledAtMs) {
110
+ throw new Error("--delay 和 --at 不能同时传");
111
+ }
112
+ if (delayMs) {
113
+ return Date.now() + delayMs;
114
+ }
115
+ if (scheduledAtMs) {
116
+ return scheduledAtMs;
117
+ }
118
+ return 0;
119
+ }
120
+
121
+ function parseDelay(rawValue) {
122
+ const normalized = String(rawValue || "").trim().toLowerCase();
123
+ if (!normalized) {
124
+ return 0;
125
+ }
126
+
127
+ let totalMs = 0;
128
+ let index = 0;
129
+ while (index < normalized.length) {
130
+ while (index < normalized.length && /\s/.test(normalized[index])) {
131
+ index += 1;
132
+ }
133
+ if (index >= normalized.length) {
134
+ break;
135
+ }
136
+
137
+ const match = normalized.slice(index).match(/^(\d+)\s*([smhd])/);
138
+ if (!match) {
139
+ return 0;
140
+ }
141
+
142
+ const amount = Number.parseInt(match[1], 10);
143
+ const unitMs = DELAY_UNIT_MS[match[2]] || 0;
144
+ if (!Number.isFinite(amount) || amount <= 0 || !unitMs) {
145
+ return 0;
146
+ }
147
+
148
+ totalMs += amount * unitMs;
149
+ index += match[0].length;
150
+ }
151
+
152
+ return totalMs > 0 ? totalMs : 0;
153
+ }
154
+
155
+ function parseAbsoluteTime(rawValue, timezone = LEGACY_TIMELINE_TIMEZONE) {
156
+ const normalized = String(rawValue || "").trim();
157
+ if (!normalized) {
158
+ return 0;
159
+ }
160
+
161
+ const normalizedIso = normalizeAbsoluteTimeString(normalized, timezone);
162
+ const parsed = Date.parse(normalizedIso);
163
+ return Number.isFinite(parsed) ? parsed : 0;
164
+ }
165
+
166
+ function normalizeAbsoluteTimeString(value, timezone = LEGACY_TIMELINE_TIMEZONE) {
167
+ const normalized = String(value || "").trim();
168
+ if (!normalized) {
169
+ return "";
170
+ }
171
+ return coerceLocalDateTimeToIso(normalized, {
172
+ timeZone: timezone,
173
+ defaultTime: "09:00:00",
174
+ }) || normalized;
175
+ }
176
+
177
+ async function resolveBody(options) {
178
+ const inlineText = normalizeBody(options.text);
179
+ if (inlineText) {
180
+ return inlineText;
181
+ }
182
+ if (!options.useStdin && process.stdin.isTTY) {
183
+ return "";
184
+ }
185
+ return normalizeBody(await readStdin());
186
+ }
187
+
188
+ function readStdin() {
189
+ return new Promise((resolve, reject) => {
190
+ let buffer = "";
191
+ process.stdin.setEncoding("utf8");
192
+ process.stdin.on("data", (chunk) => {
193
+ buffer += chunk;
194
+ });
195
+ process.stdin.on("end", () => resolve(buffer));
196
+ process.stdin.on("error", reject);
197
+ });
198
+ }
199
+
200
+ function normalizeBody(value) {
201
+ return String(value || "").replace(/\r\n/g, "\n").trim();
202
+ }
203
+
204
+ function buildAbsoluteTimeExample(timezone = LEGACY_TIMELINE_TIMEZONE) {
205
+ const explicit = normalizeAbsoluteTimeString("2026-04-07 21:30", timezone);
206
+ return `${explicit || "2026-04-07T21:30+08:00"} 或 2026-04-07 21:30(后者按当前 timezone 解释)`;
207
+ }
208
+
209
+ module.exports = {
210
+ buildAbsoluteTimeExample,
211
+ normalizeAbsoluteTimeString,
212
+ parseAbsoluteTime,
213
+ resolveDueAtMs,
214
+ runReminderWriteCommand,
215
+ };
@@ -1,9 +1,10 @@
1
1
  const { buildReview, writeReview } = require("../core/review");
2
+ const { LEGACY_TIMELINE_TIMEZONE } = require("../core/timezone");
2
3
 
3
4
  async function runReviewCommand(config, kind, args = process.argv.slice(4)) {
4
5
  const options = parseReviewArgs(args, kind);
5
6
  if (options.help) {
6
- printReviewHelp(kind);
7
+ printReviewHelp(kind, config?.timezone);
7
8
  return;
8
9
  }
9
10
  if (options.stdout) {
@@ -79,13 +80,14 @@ function parseReviewArgs(args, kind) {
79
80
  return options;
80
81
  }
81
82
 
82
- function printReviewHelp(kind) {
83
+ function printReviewHelp(kind, timezone = LEGACY_TIMELINE_TIMEZONE) {
84
+ const resolvedTimezone = String(timezone || "").trim() || LEGACY_TIMELINE_TIMEZONE;
83
85
  const nightly = [
84
86
  "用法: npm run review:nightly -- [--date YYYY-MM-DD] [--stdout] [--deterministic] [--model <id>]",
85
87
  "",
86
88
  "说明:",
87
89
  " 从当前 diary 真相源生成一份 Codeksei 睡前收口。",
88
- " 默认按 Asia/Shanghai 的当前日期推断今天,并给周/月复盘提供更轻的日级原料。",
90
+ ` 默认按 ${resolvedTimezone} 的当前日期推断今天,并给周/月复盘提供更轻的日级原料。`,
89
91
  " 默认走 hybrid:脚本保骨架,Codex 负责结构化语义提炼;失败时自动回退。",
90
92
  "",
91
93
  "示例:",
@@ -98,7 +100,7 @@ function printReviewHelp(kind) {
98
100
  "",
99
101
  "说明:",
100
102
  " 从当前 diary 真相源生成一份 Codeksei 生活助理周复盘。",
101
- " 默认按 Asia/Shanghai 的当前日期推断本周(周一到周日)。",
103
+ ` 默认按 ${resolvedTimezone} 的当前日期推断本周(周一到周日)。`,
102
104
  " 默认走 hybrid:脚本保骨架,Codex 负责结构化语义提炼;失败时自动回退。",
103
105
  "",
104
106
  "示例:",
@@ -112,7 +114,7 @@ function printReviewHelp(kind) {
112
114
  "",
113
115
  "说明:",
114
116
  " 从当前 diary 真相源生成一份 Codeksei 生活助理月复盘。",
115
- " 默认按 Asia/Shanghai 的当前日期推断本月。",
117
+ ` 默认按 ${resolvedTimezone} 的当前日期推断本月。`,
116
118
  " 默认走 hybrid:脚本保骨架,Codex 负责结构化语义提炼;失败时自动回退。",
117
119
  "",
118
120
  "示例:",
@@ -1,19 +1,19 @@
1
- const crypto = require("crypto");
2
-
1
+ const crypto = require("crypto");
2
+
3
3
  const { resolveSelectedAccount } = require("../adapters/channel/weixin/account-store");
4
4
  const { SessionStore } = require("../adapters/runtime/codex/session-store");
5
5
  const { PACKAGE_NAME, readPrefixedEnv } = require("../core/branding");
6
6
  const { resolvePreferredSenderId, resolvePreferredWorkspaceRoot } = require("../core/default-targets");
7
7
  const { SystemMessageQueueStore } = require("../core/system-message-queue-store");
8
-
9
- const DEFAULT_MIN_INTERVAL_MS = 3 * 60_000;
10
- const DEFAULT_MAX_INTERVAL_MS = 60 * 60_000;
11
- const INTERNAL_CHECKIN_TRIGGER_TEMPLATE = "Decide whether to reach out to %USER% now. You may stay silent, send one short WeChat message, update diary/timeline, or take another useful action. If no user-visible message should be sent, output exactly SILENT. If you do send a message, output only the message text.";
12
-
13
- async function runSystemCheckinPoller(config) {
14
- const account = resolveSelectedAccount(config);
15
- const queue = new SystemMessageQueueStore({ filePath: config.systemMessageQueueFile });
16
- const sessionStore = new SessionStore({ filePath: config.sessionsFile });
8
+
9
+ const DEFAULT_MIN_INTERVAL_MS = 3 * 60_000;
10
+ const DEFAULT_MAX_INTERVAL_MS = 60 * 60_000;
11
+ const INTERNAL_CHECKIN_TRIGGER_TEMPLATE = "Decide whether to reach out to %USER% now. You may stay silent, send one short WeChat message, update diary/timeline, or take another useful action. If no user-visible message should be sent, output exactly SILENT. If you do send a message, output only the message text.";
12
+
13
+ async function runSystemCheckinPoller(config) {
14
+ const account = resolveSelectedAccount(config);
15
+ const queue = new SystemMessageQueueStore({ filePath: config.systemMessageQueueFile });
16
+ const sessionStore = new SessionStore({ filePath: config.sessionsFile });
17
17
  const target = resolvePollerTarget({ config, account, sessionStore });
18
18
  const minIntervalMs = readIntervalMs(readPrefixedEnv(process.env, "CHECKIN_MIN_INTERVAL_MS"), DEFAULT_MIN_INTERVAL_MS);
19
19
  const maxIntervalMs = Math.max(
@@ -23,32 +23,32 @@ async function runSystemCheckinPoller(config) {
23
23
 
24
24
  console.log(`[${PACKAGE_NAME}] checkin poller ready user=${target.senderId} workspace=${target.workspaceRoot}`);
25
25
  console.log(`[${PACKAGE_NAME}] checkin interval range ${Math.round(minIntervalMs / 60000)}m-${Math.round(maxIntervalMs / 60000)}m`);
26
-
27
- while (true) {
28
- const delayMs = pickRandomDelayMs(minIntervalMs, maxIntervalMs);
29
- const wakeAt = new Date(Date.now() + delayMs).toISOString();
26
+
27
+ while (true) {
28
+ const delayMs = pickRandomDelayMs(minIntervalMs, maxIntervalMs);
29
+ const wakeAt = new Date(Date.now() + delayMs).toISOString();
30
30
  console.log(`[${PACKAGE_NAME}] next checkin in ${Math.round(delayMs / 60000)}m at ${wakeAt}`);
31
- await sleep(delayMs);
32
-
33
- if (queue.hasPendingForAccount(account.accountId)) {
31
+ await sleep(delayMs);
32
+
33
+ if (queue.hasPendingForAccount(account.accountId)) {
34
34
  console.log(`[${PACKAGE_NAME}] checkin skipped: pending system message still in queue`);
35
- continue;
36
- }
37
-
38
- const queued = queue.enqueue({
39
- id: crypto.randomUUID(),
40
- accountId: account.accountId,
41
- senderId: target.senderId,
42
- workspaceRoot: target.workspaceRoot,
43
- text: buildCheckinTrigger(config),
44
- createdAt: new Date().toISOString(),
45
- });
35
+ continue;
36
+ }
37
+
38
+ const queued = queue.enqueue({
39
+ id: crypto.randomUUID(),
40
+ accountId: account.accountId,
41
+ senderId: target.senderId,
42
+ workspaceRoot: target.workspaceRoot,
43
+ text: buildCheckinTrigger(config),
44
+ createdAt: new Date().toISOString(),
45
+ });
46
46
  console.log(`[${PACKAGE_NAME}] checkin queued id=${queued.id}`);
47
- }
48
- }
49
-
50
- function resolvePollerTarget({ config, account, sessionStore }) {
51
- const senderId = resolvePreferredSenderId({
47
+ }
48
+ }
49
+
50
+ function resolvePollerTarget({ config, account, sessionStore }) {
51
+ const senderId = resolvePreferredSenderId({
52
52
  config,
53
53
  accountId: account.accountId,
54
54
  explicitUser: readPrefixedEnv(process.env, "CHECKIN_USER_ID") || "",
@@ -68,33 +68,33 @@ function resolvePollerTarget({ config, account, sessionStore }) {
68
68
  if (!workspaceRoot) {
69
69
  throw new Error("无法确定 checkin poller 的 workspace,先设置 CODEKSEI_WORKSPACE_ROOT(或旧的 CYBERBOSS_WORKSPACE_ROOT)");
70
70
  }
71
-
72
- return { senderId, workspaceRoot };
73
- }
74
-
75
- function readIntervalMs(rawValue, fallback) {
76
- const parsed = Number.parseInt(String(rawValue || ""), 10);
77
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
78
- }
79
-
80
- function pickRandomDelayMs(minIntervalMs, maxIntervalMs) {
81
- if (maxIntervalMs <= minIntervalMs) {
82
- return minIntervalMs;
83
- }
84
- return minIntervalMs + Math.floor(Math.random() * (maxIntervalMs - minIntervalMs + 1));
85
- }
86
-
87
- function normalizeText(value) {
88
- return typeof value === "string" ? value.trim() : "";
89
- }
90
-
91
- function sleep(ms) {
92
- return new Promise((resolve) => setTimeout(resolve, ms));
93
- }
94
-
95
- function buildCheckinTrigger(config) {
96
- const userName = normalizeText(config?.userName) || "用户";
97
- return INTERNAL_CHECKIN_TRIGGER_TEMPLATE.replace("%USER%", userName);
98
- }
99
-
100
- module.exports = { runSystemCheckinPoller };
71
+
72
+ return { senderId, workspaceRoot };
73
+ }
74
+
75
+ function readIntervalMs(rawValue, fallback) {
76
+ const parsed = Number.parseInt(String(rawValue || ""), 10);
77
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
78
+ }
79
+
80
+ function pickRandomDelayMs(minIntervalMs, maxIntervalMs) {
81
+ if (maxIntervalMs <= minIntervalMs) {
82
+ return minIntervalMs;
83
+ }
84
+ return minIntervalMs + Math.floor(Math.random() * (maxIntervalMs - minIntervalMs + 1));
85
+ }
86
+
87
+ function normalizeText(value) {
88
+ return typeof value === "string" ? value.trim() : "";
89
+ }
90
+
91
+ function sleep(ms) {
92
+ return new Promise((resolve) => setTimeout(resolve, ms));
93
+ }
94
+
95
+ function buildCheckinTrigger(config) {
96
+ const userName = normalizeText(config?.userName) || "用户";
97
+ return INTERNAL_CHECKIN_TRIGGER_TEMPLATE.replace("%USER%", userName);
98
+ }
99
+
100
+ module.exports = { runSystemCheckinPoller };