codeksei 0.1.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/LICENSE +661 -0
- package/README.en.md +215 -0
- package/README.md +259 -0
- package/bin/codeksei.js +10 -0
- package/bin/cyberboss.js +11 -0
- package/package.json +86 -0
- package/scripts/install-background-tasks.ps1 +135 -0
- package/scripts/open_shared_wechat_thread.sh +94 -0
- package/scripts/open_wechat_thread.sh +117 -0
- package/scripts/shared-common.js +791 -0
- package/scripts/shared-open.js +46 -0
- package/scripts/shared-start.js +41 -0
- package/scripts/shared-status.js +74 -0
- package/scripts/shared-supervisor.js +141 -0
- package/scripts/shared-task-runner.ps1 +87 -0
- package/scripts/shared-watchdog.js +290 -0
- package/scripts/show_shared_status.sh +53 -0
- package/scripts/start_shared_app_server.sh +65 -0
- package/scripts/start_shared_wechat.sh +108 -0
- package/scripts/timeline-screenshot.sh +15 -0
- package/scripts/uninstall-background-tasks.ps1 +23 -0
- package/src/adapters/channel/weixin/account-store.js +135 -0
- package/src/adapters/channel/weixin/api-v2.js +258 -0
- package/src/adapters/channel/weixin/api.js +180 -0
- package/src/adapters/channel/weixin/context-token-store.js +84 -0
- package/src/adapters/channel/weixin/index.js +605 -0
- package/src/adapters/channel/weixin/legacy.js +567 -0
- package/src/adapters/channel/weixin/login-common.js +63 -0
- package/src/adapters/channel/weixin/login-legacy.js +124 -0
- package/src/adapters/channel/weixin/login-v2.js +186 -0
- package/src/adapters/channel/weixin/media-mime.js +22 -0
- package/src/adapters/channel/weixin/media-receive.js +370 -0
- package/src/adapters/channel/weixin/media-send.js +331 -0
- package/src/adapters/channel/weixin/message-utils-v2.js +282 -0
- package/src/adapters/channel/weixin/message-utils.js +199 -0
- package/src/adapters/channel/weixin/protocol.js +77 -0
- package/src/adapters/channel/weixin/redact.js +41 -0
- package/src/adapters/channel/weixin/reminder-queue-store.js +101 -0
- package/src/adapters/channel/weixin/sync-buffer-store.js +35 -0
- package/src/adapters/runtime/codex/events.js +252 -0
- package/src/adapters/runtime/codex/index.js +502 -0
- package/src/adapters/runtime/codex/message-utils.js +141 -0
- package/src/adapters/runtime/codex/model-catalog.js +106 -0
- package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -0
- package/src/adapters/runtime/codex/rpc-client.js +443 -0
- package/src/adapters/runtime/codex/session-store.js +376 -0
- package/src/app/channel-send-file-cli.js +57 -0
- package/src/app/diary-write-cli.js +620 -0
- package/src/app/note-auto-cli.js +201 -0
- package/src/app/note-sync-cli.js +130 -0
- package/src/app/project-radar-cli.js +165 -0
- package/src/app/reminder-write-cli.js +210 -0
- package/src/app/review-cli.js +134 -0
- package/src/app/system-checkin-poller.js +100 -0
- package/src/app/system-send-cli.js +129 -0
- package/src/app/timeline-event-cli.js +273 -0
- package/src/app/timeline-screenshot-cli.js +109 -0
- package/src/core/app.js +1810 -0
- package/src/core/branding.js +167 -0
- package/src/core/command-registry.js +609 -0
- package/src/core/config.js +84 -0
- package/src/core/default-targets.js +163 -0
- package/src/core/durable-note-schema.js +325 -0
- package/src/core/instructions-template.js +31 -0
- package/src/core/note-sync.js +433 -0
- package/src/core/project-radar.js +402 -0
- package/src/core/review-semantic.js +524 -0
- package/src/core/review.js +1081 -0
- package/src/core/shared-bridge-heartbeat.js +140 -0
- package/src/core/stream-delivery.js +990 -0
- package/src/core/system-message-dispatcher.js +68 -0
- package/src/core/system-message-queue-store.js +128 -0
- package/src/core/thread-state-store.js +135 -0
- package/src/core/timeline-screenshot-queue-store.js +134 -0
- package/src/core/workspace-alias.js +163 -0
- package/src/core/workspace-bootstrap.js +338 -0
- package/src/index.js +270 -0
- package/src/integrations/timeline/index.js +191 -0
- package/templates/weixin-instructions.md +53 -0
- package/templates/weixin-operations.md +69 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const TEXT_ITEM_TYPE = 1;
|
|
2
|
+
const IMAGE_ITEM_TYPE = 2;
|
|
3
|
+
const VOICE_ITEM_TYPE = 3;
|
|
4
|
+
const FILE_ITEM_TYPE = 4;
|
|
5
|
+
const VIDEO_ITEM_TYPE = 5;
|
|
6
|
+
const BOT_MESSAGE_TYPE = 2;
|
|
7
|
+
|
|
8
|
+
function normalizeWeixinIncomingMessage(message, config, accountId) {
|
|
9
|
+
if (!message || typeof message !== "object") {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (Number(message.message_type) === BOT_MESSAGE_TYPE) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const senderId = normalizeText(message.from_user_id);
|
|
17
|
+
if (!senderId) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const text = extractTextBody(message.item_list);
|
|
22
|
+
const attachments = extractAttachmentItems(message.item_list);
|
|
23
|
+
if (!text && !attachments.length) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
provider: "weixin",
|
|
29
|
+
accountId,
|
|
30
|
+
workspaceId: config.workspaceId,
|
|
31
|
+
senderId,
|
|
32
|
+
chatId: senderId,
|
|
33
|
+
messageId: normalizeText(message.message_id),
|
|
34
|
+
threadKey: normalizeText(message.session_id),
|
|
35
|
+
text,
|
|
36
|
+
attachments,
|
|
37
|
+
contextToken: normalizeText(message.context_token),
|
|
38
|
+
receivedAt: resolveReceivedAt(message),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extractTextBody(itemList) {
|
|
43
|
+
if (!Array.isArray(itemList) || !itemList.length) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const item of itemList) {
|
|
48
|
+
if (Number(item?.type) === TEXT_ITEM_TYPE && typeof item?.text_item?.text === "string") {
|
|
49
|
+
return item.text_item.text.trim();
|
|
50
|
+
}
|
|
51
|
+
if (Number(item?.type) === VOICE_ITEM_TYPE && typeof item?.voice_item?.text === "string") {
|
|
52
|
+
return item.voice_item.text.trim();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractAttachmentItems(itemList) {
|
|
60
|
+
if (!Array.isArray(itemList) || !itemList.length) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const attachments = [];
|
|
65
|
+
for (let index = 0; index < itemList.length; index += 1) {
|
|
66
|
+
const normalized = normalizeAttachmentItem(itemList[index], index);
|
|
67
|
+
if (normalized) {
|
|
68
|
+
attachments.push(normalized);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return attachments;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeAttachmentItem(item, index) {
|
|
76
|
+
const itemType = Number(item?.type);
|
|
77
|
+
const payload = resolveAttachmentPayload(itemType, item);
|
|
78
|
+
if (!payload) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const media = payload.media && typeof payload.media === "object"
|
|
83
|
+
? payload.media
|
|
84
|
+
: {};
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
kind: payload.kind,
|
|
88
|
+
itemType,
|
|
89
|
+
index,
|
|
90
|
+
fileName: normalizeText(
|
|
91
|
+
payload.body?.file_name
|
|
92
|
+
|| payload.body?.filename
|
|
93
|
+
|| item?.file_name
|
|
94
|
+
|| item?.filename
|
|
95
|
+
),
|
|
96
|
+
sizeBytes: parseOptionalInt(
|
|
97
|
+
payload.body?.len
|
|
98
|
+
|| payload.body?.file_size
|
|
99
|
+
|| payload.body?.size
|
|
100
|
+
|| payload.body?.video_size
|
|
101
|
+
|| item?.len
|
|
102
|
+
),
|
|
103
|
+
directUrls: collectStringValues([
|
|
104
|
+
payload.body?.url,
|
|
105
|
+
payload.body?.download_url,
|
|
106
|
+
payload.body?.cdn_url,
|
|
107
|
+
media?.url,
|
|
108
|
+
media?.download_url,
|
|
109
|
+
media?.cdn_url,
|
|
110
|
+
]),
|
|
111
|
+
mediaRef: {
|
|
112
|
+
encryptQueryParam: normalizeText(
|
|
113
|
+
media?.encrypt_query_param
|
|
114
|
+
|| media?.encrypted_query_param
|
|
115
|
+
|| payload.body?.encrypt_query_param
|
|
116
|
+
|| payload.body?.encrypted_query_param
|
|
117
|
+
|| item?.encrypt_query_param
|
|
118
|
+
|| item?.encrypted_query_param
|
|
119
|
+
),
|
|
120
|
+
aesKey: normalizeText(
|
|
121
|
+
media?.aes_key
|
|
122
|
+
|| payload.body?.aes_key
|
|
123
|
+
|| item?.aes_key
|
|
124
|
+
),
|
|
125
|
+
aesKeyHex: normalizeText(
|
|
126
|
+
payload.body?.aeskey
|
|
127
|
+
|| payload.body?.aes_key_hex
|
|
128
|
+
|| item?.aeskey
|
|
129
|
+
),
|
|
130
|
+
encryptType: Number(
|
|
131
|
+
media?.encrypt_type
|
|
132
|
+
?? payload.body?.encrypt_type
|
|
133
|
+
?? item?.encrypt_type
|
|
134
|
+
?? 1
|
|
135
|
+
),
|
|
136
|
+
fileKey: normalizeText(
|
|
137
|
+
media?.filekey
|
|
138
|
+
|| payload.body?.filekey
|
|
139
|
+
|| item?.filekey
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
rawItem: item,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveAttachmentPayload(itemType, item) {
|
|
147
|
+
if (itemType === IMAGE_ITEM_TYPE && item?.image_item && typeof item.image_item === "object") {
|
|
148
|
+
return { kind: "image", body: item.image_item, media: item.image_item.media };
|
|
149
|
+
}
|
|
150
|
+
if (itemType === FILE_ITEM_TYPE && item?.file_item && typeof item.file_item === "object") {
|
|
151
|
+
return { kind: "file", body: item.file_item, media: item.file_item.media };
|
|
152
|
+
}
|
|
153
|
+
if (itemType === VIDEO_ITEM_TYPE && item?.video_item && typeof item.video_item === "object") {
|
|
154
|
+
return { kind: "video", body: item.video_item, media: item.video_item.media };
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function collectStringValues(values) {
|
|
160
|
+
const seen = new Set();
|
|
161
|
+
const result = [];
|
|
162
|
+
for (const value of values) {
|
|
163
|
+
const normalized = normalizeText(value);
|
|
164
|
+
if (!normalized || seen.has(normalized)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
seen.add(normalized);
|
|
168
|
+
result.push(normalized);
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseOptionalInt(value) {
|
|
174
|
+
if (value == null || value === "") {
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
178
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeText(value) {
|
|
182
|
+
return typeof value === "string" ? value.trim() : "";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function resolveReceivedAt(message) {
|
|
186
|
+
const rawMs = Number(message?.create_time_ms);
|
|
187
|
+
if (Number.isFinite(rawMs) && rawMs > 0) {
|
|
188
|
+
return new Date(rawMs).toISOString();
|
|
189
|
+
}
|
|
190
|
+
const rawSeconds = Number(message?.create_time);
|
|
191
|
+
if (Number.isFinite(rawSeconds) && rawSeconds > 0) {
|
|
192
|
+
return new Date(rawSeconds * 1000).toISOString();
|
|
193
|
+
}
|
|
194
|
+
return new Date().toISOString();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
normalizeWeixinIncomingMessage,
|
|
199
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PROTOCOL_CLIENT_VERSION = "2.1.1";
|
|
4
|
+
const BOT_API_USER_AGENT = "node";
|
|
5
|
+
const ILINK_APP_ID = "bot";
|
|
6
|
+
// Keep one stable sender identity per bridge process. Re-randomizing this on
|
|
7
|
+
// every request makes retries look like they came from different clients,
|
|
8
|
+
// which weakens upstream dedupe and can amplify duplicate sends.
|
|
9
|
+
const PROCESS_WECHAT_UIN = createStableWechatUin();
|
|
10
|
+
|
|
11
|
+
function normalizeRouteTag(routeTag) {
|
|
12
|
+
return typeof routeTag === "string" ? routeTag.trim() : "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeProtocolClientVersion(version) {
|
|
16
|
+
const normalized = typeof version === "string" ? version.trim() : "";
|
|
17
|
+
return normalized || DEFAULT_PROTOCOL_CLIENT_VERSION;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function encodeClientVersion(version) {
|
|
21
|
+
const parts = normalizeProtocolClientVersion(version).split(".");
|
|
22
|
+
const parse = (index) => {
|
|
23
|
+
const value = Number.parseInt(parts[index] || "0", 10);
|
|
24
|
+
return Number.isFinite(value) ? value : 0;
|
|
25
|
+
};
|
|
26
|
+
const major = parse(0);
|
|
27
|
+
const minor = parse(1);
|
|
28
|
+
const patch = parse(2);
|
|
29
|
+
const encoded = ((major & 0xff) << 16) | ((minor & 0xff) << 8) | (patch & 0xff);
|
|
30
|
+
return String(encoded);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildCommonHeaders({ routeTag = "", clientVersion = DEFAULT_PROTOCOL_CLIENT_VERSION } = {}) {
|
|
34
|
+
const headers = {
|
|
35
|
+
"User-Agent": BOT_API_USER_AGENT,
|
|
36
|
+
"iLink-App-Id": ILINK_APP_ID,
|
|
37
|
+
"iLink-App-ClientVersion": encodeClientVersion(clientVersion),
|
|
38
|
+
};
|
|
39
|
+
const normalizedRouteTag = normalizeRouteTag(routeTag);
|
|
40
|
+
if (normalizedRouteTag) {
|
|
41
|
+
headers.SKRouteTag = normalizedRouteTag;
|
|
42
|
+
}
|
|
43
|
+
return headers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createStableWechatUin() {
|
|
47
|
+
const uint32 = crypto.randomBytes(4).readUInt32BE(0);
|
|
48
|
+
return Buffer.from(String(uint32), "utf8").toString("base64");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getStableWechatUin() {
|
|
52
|
+
return PROCESS_WECHAT_UIN;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildJsonHeaders({ body, token = "", routeTag = "", clientVersion = DEFAULT_PROTOCOL_CLIENT_VERSION }) {
|
|
56
|
+
const headers = {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
AuthorizationType: "ilink_bot_token",
|
|
59
|
+
"Content-Length": String(Buffer.byteLength(String(body || ""), "utf8")),
|
|
60
|
+
"X-WECHAT-UIN": getStableWechatUin(),
|
|
61
|
+
...buildCommonHeaders({ routeTag, clientVersion }),
|
|
62
|
+
};
|
|
63
|
+
const normalizedToken = typeof token === "string" ? token.trim() : "";
|
|
64
|
+
if (normalizedToken) {
|
|
65
|
+
headers.Authorization = `Bearer ${normalizedToken}`;
|
|
66
|
+
}
|
|
67
|
+
return headers;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
DEFAULT_PROTOCOL_CLIENT_VERSION,
|
|
72
|
+
buildCommonHeaders,
|
|
73
|
+
buildJsonHeaders,
|
|
74
|
+
getStableWechatUin,
|
|
75
|
+
normalizeProtocolClientVersion,
|
|
76
|
+
normalizeRouteTag,
|
|
77
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const SENSITIVE_FIELD_NAMES = [
|
|
2
|
+
"context_token",
|
|
3
|
+
"bot_token",
|
|
4
|
+
"token",
|
|
5
|
+
"authorization",
|
|
6
|
+
"Authorization",
|
|
7
|
+
"aeskey",
|
|
8
|
+
"aes_key",
|
|
9
|
+
"upload_param",
|
|
10
|
+
"encrypted_query_param",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const JSON_FIELD_PATTERN = new RegExp(
|
|
14
|
+
`"(${SENSITIVE_FIELD_NAMES.join("|")})"\\s*:\\s*"[^"]*"`,
|
|
15
|
+
"g"
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const QUERY_FIELD_PATTERN = new RegExp(
|
|
19
|
+
`([?&](?:${SENSITIVE_FIELD_NAMES.join("|")})=)[^&\\s]+`,
|
|
20
|
+
"g"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/g;
|
|
24
|
+
|
|
25
|
+
function redactSensitiveText(input, maxLen = 800) {
|
|
26
|
+
const text = typeof input === "string" ? input : String(input || "");
|
|
27
|
+
if (!text) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
const redacted = text
|
|
31
|
+
.replace(JSON_FIELD_PATTERN, '"$1":"<redacted>"')
|
|
32
|
+
.replace(QUERY_FIELD_PATTERN, "$1<redacted>")
|
|
33
|
+
.replace(BEARER_PATTERN, "Bearer <redacted>");
|
|
34
|
+
if (redacted.length <= maxLen) {
|
|
35
|
+
return redacted;
|
|
36
|
+
}
|
|
37
|
+
return `${redacted.slice(0, maxLen)}…(truncated, totalLen=${redacted.length})`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { redactSensitiveText };
|
|
41
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
class ReminderQueueStore {
|
|
5
|
+
constructor({ filePath }) {
|
|
6
|
+
this.filePath = filePath;
|
|
7
|
+
this.state = { reminders: [] };
|
|
8
|
+
this.ensureParentDirectory();
|
|
9
|
+
this.load();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ensureParentDirectory() {
|
|
13
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
load() {
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(this.filePath, "utf8");
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
const reminders = Array.isArray(parsed?.reminders) ? parsed.reminders : [];
|
|
21
|
+
this.state = {
|
|
22
|
+
reminders: reminders
|
|
23
|
+
.map(normalizeReminder)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.sort((left, right) => left.dueAtMs - right.dueAtMs),
|
|
26
|
+
};
|
|
27
|
+
} catch {
|
|
28
|
+
this.state = { reminders: [] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
save() {
|
|
33
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
enqueue(reminder) {
|
|
37
|
+
this.load();
|
|
38
|
+
const normalized = normalizeReminder(reminder);
|
|
39
|
+
if (!normalized) {
|
|
40
|
+
throw new Error("invalid reminder");
|
|
41
|
+
}
|
|
42
|
+
this.state.reminders.push(normalized);
|
|
43
|
+
this.state.reminders.sort((left, right) => left.dueAtMs - right.dueAtMs);
|
|
44
|
+
this.save();
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
listDue(nowMs = Date.now()) {
|
|
49
|
+
this.load();
|
|
50
|
+
const due = [];
|
|
51
|
+
const pending = [];
|
|
52
|
+
|
|
53
|
+
for (const reminder of this.state.reminders) {
|
|
54
|
+
if (reminder.dueAtMs <= nowMs) {
|
|
55
|
+
due.push(reminder);
|
|
56
|
+
} else {
|
|
57
|
+
pending.push(reminder);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (due.length) {
|
|
62
|
+
this.state.reminders = pending;
|
|
63
|
+
this.save();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return due;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
peekNextDueAtMs() {
|
|
70
|
+
this.load();
|
|
71
|
+
const first = this.state.reminders[0];
|
|
72
|
+
return Number.isFinite(first?.dueAtMs) ? first.dueAtMs : 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeReminder(reminder) {
|
|
77
|
+
if (!reminder || typeof reminder !== "object") {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const id = typeof reminder.id === "string" ? reminder.id.trim() : "";
|
|
81
|
+
const accountId = typeof reminder.accountId === "string" ? reminder.accountId.trim() : "";
|
|
82
|
+
const senderId = typeof reminder.senderId === "string" ? reminder.senderId.trim() : "";
|
|
83
|
+
const contextToken = typeof reminder.contextToken === "string" ? reminder.contextToken.trim() : "";
|
|
84
|
+
const text = typeof reminder.text === "string" ? reminder.text.trim() : "";
|
|
85
|
+
const dueAtMs = Number(reminder.dueAtMs);
|
|
86
|
+
const createdAt = typeof reminder.createdAt === "string" ? reminder.createdAt.trim() : "";
|
|
87
|
+
if (!id || !accountId || !senderId || !contextToken || !text || !Number.isFinite(dueAtMs) || dueAtMs <= 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
accountId,
|
|
93
|
+
senderId,
|
|
94
|
+
contextToken,
|
|
95
|
+
text,
|
|
96
|
+
dueAtMs,
|
|
97
|
+
createdAt: createdAt || new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { ReminderQueueStore };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { normalizeAccountId } = require("./account-store");
|
|
4
|
+
|
|
5
|
+
function ensureSyncBufferDir(config) {
|
|
6
|
+
fs.mkdirSync(config.syncBufferDir, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function resolveSyncBufferPath(config, accountId) {
|
|
10
|
+
ensureSyncBufferDir(config);
|
|
11
|
+
return path.join(config.syncBufferDir, `${normalizeAccountId(accountId)}.txt`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadSyncBuffer(config, accountId) {
|
|
15
|
+
try {
|
|
16
|
+
const filePath = resolveSyncBufferPath(config, accountId);
|
|
17
|
+
if (!fs.existsSync(filePath)) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
return fs.readFileSync(filePath, "utf8").trim();
|
|
21
|
+
} catch {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function saveSyncBuffer(config, accountId, buffer) {
|
|
27
|
+
const filePath = resolveSyncBufferPath(config, accountId);
|
|
28
|
+
fs.writeFileSync(filePath, String(buffer || ""), "utf8");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
loadSyncBuffer,
|
|
33
|
+
resolveSyncBufferPath,
|
|
34
|
+
saveSyncBuffer,
|
|
35
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
const {
|
|
2
|
+
extractAssistantPhase,
|
|
3
|
+
extractAssistantText,
|
|
4
|
+
extractFailureText,
|
|
5
|
+
extractThreadIdFromParams,
|
|
6
|
+
extractTurnIdFromParams,
|
|
7
|
+
} = require("./message-utils");
|
|
8
|
+
|
|
9
|
+
function mapCodexMessageToRuntimeEvent(message) {
|
|
10
|
+
if (message?.type === "event_msg" && message?.payload?.type === "token_count") {
|
|
11
|
+
return {
|
|
12
|
+
type: "runtime.usage.updated",
|
|
13
|
+
payload: normalizeUsagePayload(message.payload),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const method = normalizeString(message?.method);
|
|
17
|
+
const params = message?.params || {};
|
|
18
|
+
const threadId = extractThreadIdFromParams(params);
|
|
19
|
+
const turnId = extractTurnIdFromParams(params);
|
|
20
|
+
|
|
21
|
+
if (!method) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (method === "turn/started" || method === "turn/start") {
|
|
26
|
+
return {
|
|
27
|
+
type: "runtime.turn.started",
|
|
28
|
+
payload: {
|
|
29
|
+
threadId,
|
|
30
|
+
turnId,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (method === "turn/completed") {
|
|
36
|
+
return {
|
|
37
|
+
type: "runtime.turn.completed",
|
|
38
|
+
payload: {
|
|
39
|
+
threadId,
|
|
40
|
+
turnId,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (method === "turn/failed") {
|
|
46
|
+
return {
|
|
47
|
+
type: "runtime.turn.failed",
|
|
48
|
+
payload: {
|
|
49
|
+
threadId,
|
|
50
|
+
turnId,
|
|
51
|
+
text: extractFailureText(params),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (method === "item/agentMessage/delta") {
|
|
57
|
+
const text = extractAssistantText(params);
|
|
58
|
+
const phase = extractAssistantPhase(params);
|
|
59
|
+
if (!text) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
type: "runtime.reply.delta",
|
|
64
|
+
payload: {
|
|
65
|
+
threadId,
|
|
66
|
+
turnId,
|
|
67
|
+
itemId: normalizeString(params?.itemId || params?.item?.id),
|
|
68
|
+
text,
|
|
69
|
+
phase,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (method === "item/completed" && normalizeString(params?.item?.type).toLowerCase() === "agentmessage") {
|
|
75
|
+
const text = extractAssistantText(params);
|
|
76
|
+
const phase = extractAssistantPhase(params);
|
|
77
|
+
return {
|
|
78
|
+
type: "runtime.reply.completed",
|
|
79
|
+
payload: {
|
|
80
|
+
threadId,
|
|
81
|
+
turnId,
|
|
82
|
+
itemId: normalizeString(params?.item?.id),
|
|
83
|
+
text,
|
|
84
|
+
phase,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (isApprovalRequestMethod(method)) {
|
|
90
|
+
return {
|
|
91
|
+
type: "runtime.approval.requested",
|
|
92
|
+
payload: {
|
|
93
|
+
threadId,
|
|
94
|
+
requestId: message?.id ?? null,
|
|
95
|
+
reason: normalizeString(params?.reason),
|
|
96
|
+
command: extractApprovalDisplayCommand(params),
|
|
97
|
+
commandTokens: extractApprovalCommandTokens(params),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeUsagePayload(payload) {
|
|
106
|
+
const info = payload?.info || {};
|
|
107
|
+
const total = info?.total_token_usage || {};
|
|
108
|
+
const last = info?.last_token_usage || {};
|
|
109
|
+
const rateLimits = payload?.rate_limits || {};
|
|
110
|
+
return {
|
|
111
|
+
totalInputTokens: numberOrZero(total.input_tokens),
|
|
112
|
+
totalCachedInputTokens: numberOrZero(total.cached_input_tokens),
|
|
113
|
+
totalOutputTokens: numberOrZero(total.output_tokens),
|
|
114
|
+
totalReasoningTokens: numberOrZero(total.reasoning_output_tokens),
|
|
115
|
+
totalTokens: numberOrZero(total.total_tokens),
|
|
116
|
+
lastInputTokens: numberOrZero(last.input_tokens),
|
|
117
|
+
lastCachedInputTokens: numberOrZero(last.cached_input_tokens),
|
|
118
|
+
lastOutputTokens: numberOrZero(last.output_tokens),
|
|
119
|
+
lastReasoningTokens: numberOrZero(last.reasoning_output_tokens),
|
|
120
|
+
lastTotalTokens: numberOrZero(last.total_tokens),
|
|
121
|
+
modelContextWindow: numberOrZero(info?.model_context_window),
|
|
122
|
+
primaryUsedPercent: numberOrZero(rateLimits?.primary?.used_percent),
|
|
123
|
+
secondaryUsedPercent: numberOrZero(rateLimits?.secondary?.used_percent),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isApprovalRequestMethod(method) {
|
|
128
|
+
return typeof method === "string" && method.endsWith("requestApproval");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractApprovalDisplayCommand(params) {
|
|
132
|
+
const commandTokens = extractApprovalCommandTokens(params);
|
|
133
|
+
const direct = params?.command;
|
|
134
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
135
|
+
return direct.trim();
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(direct)) {
|
|
138
|
+
const normalized = normalizeCommandTokens(direct);
|
|
139
|
+
if (normalized.length) {
|
|
140
|
+
return buildApprovalCommandPreview(normalized);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return buildApprovalCommandPreview(commandTokens);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function extractApprovalCommandTokens(params) {
|
|
147
|
+
return normalizeCommandTokens(extractTokens(params));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function extractTokens(value) {
|
|
151
|
+
if (!value) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
return value.every((entry) => typeof entry === "string")
|
|
156
|
+
? value.map((entry) => entry.trim()).filter(Boolean)
|
|
157
|
+
: [];
|
|
158
|
+
}
|
|
159
|
+
if (typeof value === "string") {
|
|
160
|
+
return splitCommandLine(value);
|
|
161
|
+
}
|
|
162
|
+
if (typeof value !== "object") {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const key of ["proposedExecpolicyAmendment", "argv", "args", "command", "cmd", "exec", "shellCommand", "script"]) {
|
|
167
|
+
const tokens = extractTokens(value[key]);
|
|
168
|
+
if (tokens.length) {
|
|
169
|
+
return tokens;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
174
|
+
const normalizedKey = key.toLowerCase();
|
|
175
|
+
if (normalizedKey.includes("execpolicy") || normalizedKey.includes("exec_policy")) {
|
|
176
|
+
const tokens = extractTokens(nested);
|
|
177
|
+
if (tokens.length) {
|
|
178
|
+
return tokens;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function splitCommandLine(input) {
|
|
187
|
+
const tokens = [];
|
|
188
|
+
let current = "";
|
|
189
|
+
let quote = null;
|
|
190
|
+
let escaped = false;
|
|
191
|
+
|
|
192
|
+
for (const char of String(input || "")) {
|
|
193
|
+
if (escaped) {
|
|
194
|
+
current += char;
|
|
195
|
+
escaped = false;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === "\\") {
|
|
199
|
+
escaped = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (quote) {
|
|
203
|
+
if (char === quote) {
|
|
204
|
+
quote = null;
|
|
205
|
+
} else {
|
|
206
|
+
current += char;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (char === "\"" || char === "'") {
|
|
211
|
+
quote = char;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (/\s/.test(char)) {
|
|
215
|
+
if (current) {
|
|
216
|
+
tokens.push(current);
|
|
217
|
+
current = "";
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
current += char;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (current) {
|
|
225
|
+
tokens.push(current);
|
|
226
|
+
}
|
|
227
|
+
return tokens;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function buildApprovalCommandPreview(tokens) {
|
|
231
|
+
const normalized = normalizeCommandTokens(tokens);
|
|
232
|
+
if (!normalized.length) {
|
|
233
|
+
return "";
|
|
234
|
+
}
|
|
235
|
+
return normalized.map((token) => (token.includes(" ") ? JSON.stringify(token) : token)).join(" ");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function normalizeCommandTokens(tokens) {
|
|
239
|
+
return Array.isArray(tokens)
|
|
240
|
+
? tokens.map((part) => normalizeString(part)).filter(Boolean)
|
|
241
|
+
: [];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function normalizeString(value) {
|
|
245
|
+
return typeof value === "string" ? value.trim() : "";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function numberOrZero(value) {
|
|
249
|
+
return Number.isFinite(Number(value)) ? Number(value) : 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = { mapCodexMessageToRuntimeEvent };
|