chatccc 0.2.229 → 0.2.231
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/.agents/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/README.md +90 -90
- package/agent-prompts/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/bin/cccagent.mjs +17 -17
- package/im-skills/feishu-skill/receive-send-file.md +63 -63
- package/im-skills/feishu-skill/receive-send-image.md +24 -24
- package/im-skills/feishu-skill/skill.md +3 -3
- package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
- package/im-skills/wechat-file-skill/send-file.mjs +83 -83
- package/im-skills/wechat-file-skill/skill.md +10 -10
- package/im-skills/wechat-image-skill/skill.md +10 -10
- package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
- package/im-skills/wechat-video-skill/send-video.mjs +79 -79
- package/im-skills/wechat-video-skill/skill.md +10 -10
- package/package.json +1 -1
- package/scripts/postinstall-sharp-check.mjs +58 -58
- package/src/__tests__/agent-activity.test.ts +86 -76
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
- package/src/__tests__/builtin-chat-session.test.ts +65 -1
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +26 -26
- package/src/__tests__/builtin-context.test.ts +202 -163
- package/src/__tests__/builtin-permissions.test.ts +211 -211
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/builtin-web-tools.test.ts +220 -220
- package/src/__tests__/card-action-routing.test.ts +18 -18
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/ccc-adapter.test.ts +170 -136
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
- package/src/__tests__/chatgpt-subscription.test.ts +135 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
- package/src/__tests__/claude-adapter.test.ts +614 -614
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
- package/src/__tests__/codex-adapter.test.ts +58 -58
- package/src/__tests__/codex-raw-stream-log.test.ts +170 -170
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/cursor-adapter.test.ts +268 -268
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +164 -164
- package/src/__tests__/feishu-message-ingress.test.ts +138 -138
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/jsonl-stream.test.ts +79 -79
- package/src/__tests__/package-files.test.ts +24 -24
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/progress-reducer.test.ts +121 -110
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/response-stall.test.ts +49 -49
- package/src/__tests__/session.test.ts +174 -16
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/sim-platform.test.ts +16 -16
- package/src/__tests__/startup-lifecycle.test.ts +231 -231
- package/src/__tests__/stop-session.test.ts +34 -34
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/terminal-error.test.ts +54 -0
- package/src/__tests__/terminal-renderer.test.ts +247 -247
- package/src/__tests__/update-command-guard.test.ts +144 -144
- package/src/__tests__/web-ui.test.ts +326 -326
- package/src/adapters/adapter-interface.ts +24 -18
- package/src/adapters/ccc-adapter.ts +141 -131
- package/src/adapters/claude-adapter.ts +620 -620
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +426 -426
- package/src/adapters/cursor-adapter.ts +681 -681
- package/src/adapters/jsonl-stream.ts +157 -157
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-activity.ts +175 -170
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +91 -91
- package/src/agent-reload-config-rpc.ts +34 -34
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +9 -2
- package/src/builtin/config.ts +84 -84
- package/src/builtin/context.ts +62 -4
- package/src/builtin/file-log.ts +38 -38
- package/src/builtin/index.ts +83 -19
- package/src/builtin/proc-tree-kill.ts +61 -61
- package/src/builtin/progress/cards-helpers.ts +76 -76
- package/src/builtin/progress/reducer.ts +113 -108
- package/src/builtin/progress/terminal-renderer.ts +294 -294
- package/src/builtin/progress/view.ts +77 -77
- package/src/builtin/raw-stream-log.ts +124 -124
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/builtin/web-tools.ts +313 -313
- package/src/card-action-routing.ts +14 -14
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -318
- package/src/codex-reset-actions.ts +184 -184
- package/src/feishu-api.ts +193 -193
- package/src/feishu-message-ingress.ts +195 -195
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +8 -3
- package/src/platform-adapter.ts +6 -6
- package/src/privacy.ts +118 -118
- package/src/progress/reducer.ts +113 -108
- package/src/progress/terminal-renderer.ts +294 -294
- package/src/progress/view.ts +77 -77
- package/src/response-stall.ts +28 -28
- package/src/runtime-reload.ts +34 -34
- package/src/session-chat-binding.ts +82 -82
- package/src/session-name.ts +8 -8
- package/src/session.ts +149 -26
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/startup-lifecycle.ts +250 -250
- package/src/stream-state.ts +21 -18
- package/src/terminal-error.ts +129 -0
- package/src/turn-cards.ts +117 -117
- package/src/update-command-guard.ts +165 -165
package/src/feishu-api.ts
CHANGED
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
CHAT_LOGS_DIR,
|
|
12
12
|
PROJECT_ROOT,
|
|
13
13
|
USER_DATA_DIR,
|
|
14
|
-
CLAUDE_SESSION_PREFIX,
|
|
15
|
-
CURSOR_SESSION_PREFIX,
|
|
16
|
-
CODEX_SESSION_PREFIX,
|
|
17
|
-
CCC_SESSION_PREFIX,
|
|
14
|
+
CLAUDE_SESSION_PREFIX,
|
|
15
|
+
CURSOR_SESSION_PREFIX,
|
|
16
|
+
CODEX_SESSION_PREFIX,
|
|
17
|
+
CCC_SESSION_PREFIX,
|
|
18
18
|
ts,
|
|
19
19
|
resolveDefaultAgentTool,
|
|
20
20
|
toolDisplayName,
|
|
@@ -277,16 +277,16 @@ export async function disbandChat(
|
|
|
277
277
|
|
|
278
278
|
export function extractSessionInfo(description: string): { sessionId: string; tool: string } | null {
|
|
279
279
|
const PREFIXES: Array<{ prefix: string; tool: string }> = [
|
|
280
|
-
{ prefix: CLAUDE_SESSION_PREFIX, tool: "claude" },
|
|
281
|
-
{ prefix: CURSOR_SESSION_PREFIX, tool: "cursor" },
|
|
282
|
-
{ prefix: CODEX_SESSION_PREFIX, tool: "codex" },
|
|
283
|
-
{ prefix: CCC_SESSION_PREFIX, tool: "ccc" },
|
|
284
|
-
];
|
|
285
|
-
for (const { prefix, tool } of PREFIXES) {
|
|
286
|
-
const idx = description.indexOf(prefix);
|
|
287
|
-
if (idx === -1) continue;
|
|
288
|
-
const after = description.slice(idx + prefix.length).trim();
|
|
289
|
-
const match = after.match(/^([a-zA-Z0-9_.-]+)/);
|
|
280
|
+
{ prefix: CLAUDE_SESSION_PREFIX, tool: "claude" },
|
|
281
|
+
{ prefix: CURSOR_SESSION_PREFIX, tool: "cursor" },
|
|
282
|
+
{ prefix: CODEX_SESSION_PREFIX, tool: "codex" },
|
|
283
|
+
{ prefix: CCC_SESSION_PREFIX, tool: "ccc" },
|
|
284
|
+
];
|
|
285
|
+
for (const { prefix, tool } of PREFIXES) {
|
|
286
|
+
const idx = description.indexOf(prefix);
|
|
287
|
+
if (idx === -1) continue;
|
|
288
|
+
const after = description.slice(idx + prefix.length).trim();
|
|
289
|
+
const match = after.match(/^([a-zA-Z0-9_.-]+)/);
|
|
290
290
|
if (match) return { sessionId: match[1], tool };
|
|
291
291
|
}
|
|
292
292
|
return null;
|
|
@@ -319,30 +319,30 @@ const AVATAR_BADGES: Record<string, string> = {
|
|
|
319
319
|
cursor: resolvePath(AVATAR_BADGE_DIR, "badge_cursor.png"),
|
|
320
320
|
codex: resolvePath(AVATAR_BADGE_DIR, "badge_codex.png"),
|
|
321
321
|
};
|
|
322
|
-
const AVATAR_SIZE = 256;
|
|
323
|
-
const AVATAR_BADGE_SIZE = 92;
|
|
324
|
-
const AVATAR_BADGE_MARGIN = 10;
|
|
325
|
-
const PLAIN_AVATAR_TOOL = "plain";
|
|
326
|
-
const CODEX_AVATAR_USAGE_STYLE_VERSION = "usage-window-aware-v14";
|
|
327
|
-
const CODEX_FAST_FRAME_STYLE_VERSION = "fast-champagne-frame-v2";
|
|
328
|
-
const CURSOR_AVATAR_USAGE_STYLE_VERSION = "usage-battery-v1";
|
|
329
|
-
|
|
330
|
-
export interface CodexUsageBalance {
|
|
331
|
-
usedPercent: number;
|
|
332
|
-
remainingPercent: number;
|
|
333
|
-
resetAtEpochSeconds: number | null;
|
|
334
|
-
resetAfterSeconds: number | null;
|
|
335
|
-
limitWindowSeconds?: number | null;
|
|
336
|
-
}
|
|
322
|
+
const AVATAR_SIZE = 256;
|
|
323
|
+
const AVATAR_BADGE_SIZE = 92;
|
|
324
|
+
const AVATAR_BADGE_MARGIN = 10;
|
|
325
|
+
const PLAIN_AVATAR_TOOL = "plain";
|
|
326
|
+
const CODEX_AVATAR_USAGE_STYLE_VERSION = "usage-window-aware-v14";
|
|
327
|
+
const CODEX_FAST_FRAME_STYLE_VERSION = "fast-champagne-frame-v2";
|
|
328
|
+
const CURSOR_AVATAR_USAGE_STYLE_VERSION = "usage-battery-v1";
|
|
329
|
+
|
|
330
|
+
export interface CodexUsageBalance {
|
|
331
|
+
usedPercent: number;
|
|
332
|
+
remainingPercent: number;
|
|
333
|
+
resetAtEpochSeconds: number | null;
|
|
334
|
+
resetAfterSeconds: number | null;
|
|
335
|
+
limitWindowSeconds?: number | null;
|
|
336
|
+
}
|
|
337
337
|
|
|
338
338
|
export interface CodexRateLimitResetCredit {
|
|
339
339
|
grantedAt: string | null;
|
|
340
340
|
expiresAt: string;
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
export interface CodexUsageSummary {
|
|
344
|
-
fiveHour: CodexUsageBalance | null;
|
|
345
|
-
weekly: CodexUsageBalance | null;
|
|
343
|
+
export interface CodexUsageSummary {
|
|
344
|
+
fiveHour: CodexUsageBalance | null;
|
|
345
|
+
weekly: CodexUsageBalance | null;
|
|
346
346
|
rateLimitResetCreditsAvailable: number | null;
|
|
347
347
|
rateLimitResetCredits: CodexRateLimitResetCredit[] | null;
|
|
348
348
|
}
|
|
@@ -357,35 +357,35 @@ export interface CodexResetConsumeResult {
|
|
|
357
357
|
const avatarKeyCache = new Map<string, string>();
|
|
358
358
|
let avatarKeyCacheLoaded = false;
|
|
359
359
|
|
|
360
|
-
function normalizeAvatarTool(tool: string): string {
|
|
361
|
-
return AVATAR_BADGES[tool] ? tool : PLAIN_AVATAR_TOOL;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const FIVE_HOUR_WINDOW_SECONDS = 5 * 60 * 60;
|
|
365
|
-
const SEVEN_DAY_WINDOW_SECONDS = 7 * 24 * 60 * 60;
|
|
360
|
+
function normalizeAvatarTool(tool: string): string {
|
|
361
|
+
return AVATAR_BADGES[tool] ? tool : PLAIN_AVATAR_TOOL;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const FIVE_HOUR_WINDOW_SECONDS = 5 * 60 * 60;
|
|
365
|
+
const SEVEN_DAY_WINDOW_SECONDS = 7 * 24 * 60 * 60;
|
|
366
366
|
|
|
367
367
|
function normalizeAvatarStatus(status: string): string {
|
|
368
368
|
return AVATAR_SOURCES[status] ? status : "idle";
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
function avatarCombinationPath(tool: string, status: string): string {
|
|
372
|
-
return resolvePath(AVATAR_COMBINATIONS_DIR, `avatar_${normalizeAvatarTool(tool)}_${normalizeAvatarStatus(status)}.png`);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function avatarCacheKey(
|
|
376
|
-
tool: string,
|
|
377
|
-
status: string,
|
|
378
|
-
codexUsage: CodexUsageSummary | null = null,
|
|
379
|
-
cursorBatteryPercent: number | null = null,
|
|
380
|
-
fastMode = false,
|
|
381
|
-
): string {
|
|
382
|
-
const normalizedTool = normalizeAvatarTool(tool);
|
|
383
|
-
const normalizedStatus = normalizeAvatarStatus(status);
|
|
384
|
-
if (normalizedTool === "codex") {
|
|
385
|
-
const fastKey = fastMode ? `:${CODEX_FAST_FRAME_STYLE_VERSION}` : "";
|
|
386
|
-
if (!codexUsage?.weekly) return `${normalizedTool}:${normalizedStatus}:plain${fastKey}`;
|
|
387
|
-
const ringKey = codexUsage.fiveHour ? `:5h-ring:${codexUsage.fiveHour.remainingPercent}` : "";
|
|
388
|
-
return `${normalizedTool}:${normalizedStatus}:${CODEX_AVATAR_USAGE_STYLE_VERSION}:7d-battery:${codexUsage.weekly.remainingPercent}${ringKey}${fastKey}`;
|
|
371
|
+
function avatarCombinationPath(tool: string, status: string): string {
|
|
372
|
+
return resolvePath(AVATAR_COMBINATIONS_DIR, `avatar_${normalizeAvatarTool(tool)}_${normalizeAvatarStatus(status)}.png`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function avatarCacheKey(
|
|
376
|
+
tool: string,
|
|
377
|
+
status: string,
|
|
378
|
+
codexUsage: CodexUsageSummary | null = null,
|
|
379
|
+
cursorBatteryPercent: number | null = null,
|
|
380
|
+
fastMode = false,
|
|
381
|
+
): string {
|
|
382
|
+
const normalizedTool = normalizeAvatarTool(tool);
|
|
383
|
+
const normalizedStatus = normalizeAvatarStatus(status);
|
|
384
|
+
if (normalizedTool === "codex") {
|
|
385
|
+
const fastKey = fastMode ? `:${CODEX_FAST_FRAME_STYLE_VERSION}` : "";
|
|
386
|
+
if (!codexUsage?.weekly) return `${normalizedTool}:${normalizedStatus}:plain${fastKey}`;
|
|
387
|
+
const ringKey = codexUsage.fiveHour ? `:5h-ring:${codexUsage.fiveHour.remainingPercent}` : "";
|
|
388
|
+
return `${normalizedTool}:${normalizedStatus}:${CODEX_AVATAR_USAGE_STYLE_VERSION}:7d-battery:${codexUsage.weekly.remainingPercent}${ringKey}${fastKey}`;
|
|
389
389
|
}
|
|
390
390
|
if (normalizedTool === "cursor") {
|
|
391
391
|
return cursorBatteryPercent !== null
|
|
@@ -423,30 +423,30 @@ function clampPercent(value: number): number {
|
|
|
423
423
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
function usageBalanceFromWindow(raw: Record<string, unknown>, fieldName: string): CodexUsageBalance {
|
|
426
|
+
function usageBalanceFromWindow(raw: Record<string, unknown>, fieldName: string): CodexUsageBalance {
|
|
427
427
|
const value = raw.used_percent;
|
|
428
428
|
const usedPercent = Number(value);
|
|
429
429
|
if (!Number.isFinite(usedPercent)) throw new Error(`missing ${fieldName}.used_percent`);
|
|
430
|
-
const used = clampPercent(usedPercent);
|
|
431
|
-
const resetAt = Number(raw.reset_at);
|
|
432
|
-
const resetAfter = Number(raw.reset_after_seconds);
|
|
433
|
-
const rawLimitWindow = raw.limit_window_seconds;
|
|
434
|
-
const limitWindow = rawLimitWindow === null || rawLimitWindow === undefined || rawLimitWindow === ""
|
|
435
|
-
? Number.NaN
|
|
436
|
-
: Number(rawLimitWindow);
|
|
437
|
-
const balance: CodexUsageBalance = {
|
|
438
|
-
usedPercent: used,
|
|
439
|
-
remainingPercent: clampPercent(100 - used),
|
|
440
|
-
resetAtEpochSeconds: Number.isFinite(resetAt) ? resetAt : null,
|
|
441
|
-
resetAfterSeconds: Number.isFinite(resetAfter) ? resetAfter : null,
|
|
442
|
-
};
|
|
443
|
-
if (Number.isFinite(limitWindow)) balance.limitWindowSeconds = limitWindow;
|
|
444
|
-
return balance;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function isUsageWindowDuration(balance: CodexUsageBalance | null, seconds: number): boolean {
|
|
448
|
-
return balance?.limitWindowSeconds === seconds;
|
|
449
|
-
}
|
|
430
|
+
const used = clampPercent(usedPercent);
|
|
431
|
+
const resetAt = Number(raw.reset_at);
|
|
432
|
+
const resetAfter = Number(raw.reset_after_seconds);
|
|
433
|
+
const rawLimitWindow = raw.limit_window_seconds;
|
|
434
|
+
const limitWindow = rawLimitWindow === null || rawLimitWindow === undefined || rawLimitWindow === ""
|
|
435
|
+
? Number.NaN
|
|
436
|
+
: Number(rawLimitWindow);
|
|
437
|
+
const balance: CodexUsageBalance = {
|
|
438
|
+
usedPercent: used,
|
|
439
|
+
remainingPercent: clampPercent(100 - used),
|
|
440
|
+
resetAtEpochSeconds: Number.isFinite(resetAt) ? resetAt : null,
|
|
441
|
+
resetAfterSeconds: Number.isFinite(resetAfter) ? resetAfter : null,
|
|
442
|
+
};
|
|
443
|
+
if (Number.isFinite(limitWindow)) balance.limitWindowSeconds = limitWindow;
|
|
444
|
+
return balance;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function isUsageWindowDuration(balance: CodexUsageBalance | null, seconds: number): boolean {
|
|
448
|
+
return balance?.limitWindowSeconds === seconds;
|
|
449
|
+
}
|
|
450
450
|
|
|
451
451
|
function parseOptionalUsageWindow(rateLimit: Record<string, unknown>, keys: string[]): CodexUsageBalance | null {
|
|
452
452
|
for (const key of keys) {
|
|
@@ -558,25 +558,25 @@ export async function getCodexUsageSummary(): Promise<CodexUsageSummary> {
|
|
|
558
558
|
const rateLimit = data.rate_limit;
|
|
559
559
|
if (!rateLimit || typeof rateLimit !== "object") throw new Error("missing rate_limit");
|
|
560
560
|
|
|
561
|
-
const primaryWindow = parseOptionalUsageWindow(rateLimit, ["primary_window"]);
|
|
562
|
-
const secondaryWindow = parseOptionalUsageWindow(rateLimit, [
|
|
563
|
-
"secondary_window",
|
|
564
|
-
"weekly_window",
|
|
565
|
-
"week_window",
|
|
566
|
-
"long_window",
|
|
567
|
-
]);
|
|
568
|
-
if (!primaryWindow && !secondaryWindow) throw new Error("missing supported rate_limit usage window");
|
|
569
|
-
|
|
570
|
-
const windows = [primaryWindow, secondaryWindow];
|
|
571
|
-
const fiveHour = windows.find((window) => isUsageWindowDuration(window, FIVE_HOUR_WINDOW_SECONDS))
|
|
572
|
-
?? (primaryWindow?.limitWindowSeconds === undefined ? primaryWindow : null);
|
|
573
|
-
const weekly = windows.find((window) => isUsageWindowDuration(window, SEVEN_DAY_WINDOW_SECONDS))
|
|
574
|
-
?? (secondaryWindow?.limitWindowSeconds === undefined ? secondaryWindow : null);
|
|
575
|
-
const resetCredits = await resetCreditsPromise;
|
|
576
|
-
|
|
577
|
-
return {
|
|
578
|
-
fiveHour,
|
|
579
|
-
weekly,
|
|
561
|
+
const primaryWindow = parseOptionalUsageWindow(rateLimit, ["primary_window"]);
|
|
562
|
+
const secondaryWindow = parseOptionalUsageWindow(rateLimit, [
|
|
563
|
+
"secondary_window",
|
|
564
|
+
"weekly_window",
|
|
565
|
+
"week_window",
|
|
566
|
+
"long_window",
|
|
567
|
+
]);
|
|
568
|
+
if (!primaryWindow && !secondaryWindow) throw new Error("missing supported rate_limit usage window");
|
|
569
|
+
|
|
570
|
+
const windows = [primaryWindow, secondaryWindow];
|
|
571
|
+
const fiveHour = windows.find((window) => isUsageWindowDuration(window, FIVE_HOUR_WINDOW_SECONDS))
|
|
572
|
+
?? (primaryWindow?.limitWindowSeconds === undefined ? primaryWindow : null);
|
|
573
|
+
const weekly = windows.find((window) => isUsageWindowDuration(window, SEVEN_DAY_WINDOW_SECONDS))
|
|
574
|
+
?? (secondaryWindow?.limitWindowSeconds === undefined ? secondaryWindow : null);
|
|
575
|
+
const resetCredits = await resetCreditsPromise;
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
fiveHour,
|
|
579
|
+
weekly,
|
|
580
580
|
rateLimitResetCreditsAvailable: resetCredits?.availableCount ?? parseRateLimitResetCredits(data),
|
|
581
581
|
rateLimitResetCredits: resetCredits?.availableCredits ?? null,
|
|
582
582
|
};
|
|
@@ -760,10 +760,10 @@ function buildCodexUsageRingSvg(remainingPercent: number): Buffer {
|
|
|
760
760
|
</svg>`);
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
-
async function buildAgentBadgeOverlay(
|
|
764
|
-
tool: string,
|
|
765
|
-
margin = AVATAR_BADGE_MARGIN,
|
|
766
|
-
): Promise<sharp.OverlayOptions> {
|
|
763
|
+
async function buildAgentBadgeOverlay(
|
|
764
|
+
tool: string,
|
|
765
|
+
margin = AVATAR_BADGE_MARGIN,
|
|
766
|
+
): Promise<sharp.OverlayOptions> {
|
|
767
767
|
const badge = await sharp(AVATAR_BADGES[tool])
|
|
768
768
|
.resize(AVATAR_BADGE_SIZE, AVATAR_BADGE_SIZE, {
|
|
769
769
|
fit: "contain",
|
|
@@ -774,70 +774,70 @@ async function buildAgentBadgeOverlay(
|
|
|
774
774
|
.toBuffer();
|
|
775
775
|
return {
|
|
776
776
|
input: badge,
|
|
777
|
-
left: AVATAR_SIZE - AVATAR_BADGE_SIZE - margin,
|
|
778
|
-
top: AVATAR_SIZE - AVATAR_BADGE_SIZE - margin,
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
function buildCodexFastFrameOverlay(): sharp.OverlayOptions {
|
|
783
|
-
const size = AVATAR_BADGE_SIZE + 20;
|
|
784
|
-
const innerOffset = 9;
|
|
785
|
-
const innerSize = size - innerOffset * 2;
|
|
786
|
-
const frame = Buffer.from(`
|
|
787
|
-
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
788
|
-
<defs>
|
|
789
|
-
<linearGradient id="champagne" x1="0" y1="0" x2="${size}" y2="${size}" gradientUnits="userSpaceOnUse">
|
|
790
|
-
<stop offset="0" stop-color="#FFF0BE"/>
|
|
791
|
-
<stop offset="0.55" stop-color="#FFD56A"/>
|
|
792
|
-
<stop offset="1" stop-color="#EFA923"/>
|
|
793
|
-
</linearGradient>
|
|
794
|
-
</defs>
|
|
795
|
-
<rect x="0.5" y="0.5" width="${size - 1}" height="${size - 1}" rx="25" fill="url(#champagne)"/>
|
|
796
|
-
<rect x="${innerOffset}" y="${innerOffset}" width="${innerSize}" height="${innerSize}" rx="21" fill="#fffdf4"/>
|
|
797
|
-
</svg>`);
|
|
798
|
-
return {
|
|
799
|
-
input: frame,
|
|
800
|
-
left: AVATAR_SIZE - size - 1,
|
|
801
|
-
top: AVATAR_SIZE - size - 1,
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
async function renderAvatar(
|
|
806
|
-
tool: string,
|
|
807
|
-
status: string,
|
|
808
|
-
codexUsage: CodexUsageSummary | null = null,
|
|
809
|
-
cursorBatteryPercent: number | null = null,
|
|
810
|
-
fastMode = false,
|
|
811
|
-
): Promise<{ buffer: Buffer; contentType: string; filename: string }> {
|
|
812
|
-
const normalizedTool = normalizeAvatarTool(tool);
|
|
813
|
-
const normalizedStatus = normalizeAvatarStatus(status);
|
|
814
|
-
const composites: sharp.OverlayOptions[] = [];
|
|
815
|
-
const hasAgentBadge = normalizedTool !== PLAIN_AVATAR_TOOL;
|
|
816
|
-
const useFastCodexAvatar = normalizedTool === "codex" && fastMode;
|
|
817
|
-
|
|
818
|
-
const codexWeeklyUsage = normalizedTool === "codex" ? codexUsage?.weekly ?? null : null;
|
|
819
|
-
const useDynamicCodexAvatar = normalizedTool === "codex" && codexUsage !== null && codexWeeklyUsage !== null;
|
|
820
|
-
const useDynamicCursorAvatar = normalizedTool === "cursor" && cursorBatteryPercent !== null;
|
|
821
|
-
const useDynamicBadgeAvatar = useDynamicCodexAvatar || useDynamicCursorAvatar || useFastCodexAvatar;
|
|
822
|
-
const basePath = useDynamicBadgeAvatar
|
|
823
|
-
? AVATAR_SOURCES[normalizedStatus]
|
|
824
|
-
: hasAgentBadge
|
|
825
|
-
? avatarCombinationPath(normalizedTool, normalizedStatus)
|
|
826
|
-
: AVATAR_SOURCES[normalizedStatus];
|
|
827
|
-
|
|
828
|
-
if (useDynamicCodexAvatar) {
|
|
829
|
-
if (codexUsage.fiveHour) {
|
|
830
|
-
composites.push({ input: buildCodexUsageRingSvg(codexUsage.fiveHour.remainingPercent), left: 0, top: 0 });
|
|
831
|
-
}
|
|
832
|
-
composites.push({ input: buildCodexUsageBatterySvg(codexWeeklyUsage.remainingPercent), left: 0, top: 0 });
|
|
833
|
-
} else if (useDynamicCursorAvatar) {
|
|
834
|
-
composites.push({ input: buildCodexUsageBatterySvg(cursorBatteryPercent), left: 0, top: 0 });
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (useDynamicBadgeAvatar) {
|
|
838
|
-
if (useFastCodexAvatar) composites.push(buildCodexFastFrameOverlay());
|
|
839
|
-
composites.push(await buildAgentBadgeOverlay(normalizedTool, useFastCodexAvatar ? 11 : AVATAR_BADGE_MARGIN));
|
|
840
|
-
}
|
|
777
|
+
left: AVATAR_SIZE - AVATAR_BADGE_SIZE - margin,
|
|
778
|
+
top: AVATAR_SIZE - AVATAR_BADGE_SIZE - margin,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function buildCodexFastFrameOverlay(): sharp.OverlayOptions {
|
|
783
|
+
const size = AVATAR_BADGE_SIZE + 20;
|
|
784
|
+
const innerOffset = 9;
|
|
785
|
+
const innerSize = size - innerOffset * 2;
|
|
786
|
+
const frame = Buffer.from(`
|
|
787
|
+
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
|
|
788
|
+
<defs>
|
|
789
|
+
<linearGradient id="champagne" x1="0" y1="0" x2="${size}" y2="${size}" gradientUnits="userSpaceOnUse">
|
|
790
|
+
<stop offset="0" stop-color="#FFF0BE"/>
|
|
791
|
+
<stop offset="0.55" stop-color="#FFD56A"/>
|
|
792
|
+
<stop offset="1" stop-color="#EFA923"/>
|
|
793
|
+
</linearGradient>
|
|
794
|
+
</defs>
|
|
795
|
+
<rect x="0.5" y="0.5" width="${size - 1}" height="${size - 1}" rx="25" fill="url(#champagne)"/>
|
|
796
|
+
<rect x="${innerOffset}" y="${innerOffset}" width="${innerSize}" height="${innerSize}" rx="21" fill="#fffdf4"/>
|
|
797
|
+
</svg>`);
|
|
798
|
+
return {
|
|
799
|
+
input: frame,
|
|
800
|
+
left: AVATAR_SIZE - size - 1,
|
|
801
|
+
top: AVATAR_SIZE - size - 1,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async function renderAvatar(
|
|
806
|
+
tool: string,
|
|
807
|
+
status: string,
|
|
808
|
+
codexUsage: CodexUsageSummary | null = null,
|
|
809
|
+
cursorBatteryPercent: number | null = null,
|
|
810
|
+
fastMode = false,
|
|
811
|
+
): Promise<{ buffer: Buffer; contentType: string; filename: string }> {
|
|
812
|
+
const normalizedTool = normalizeAvatarTool(tool);
|
|
813
|
+
const normalizedStatus = normalizeAvatarStatus(status);
|
|
814
|
+
const composites: sharp.OverlayOptions[] = [];
|
|
815
|
+
const hasAgentBadge = normalizedTool !== PLAIN_AVATAR_TOOL;
|
|
816
|
+
const useFastCodexAvatar = normalizedTool === "codex" && fastMode;
|
|
817
|
+
|
|
818
|
+
const codexWeeklyUsage = normalizedTool === "codex" ? codexUsage?.weekly ?? null : null;
|
|
819
|
+
const useDynamicCodexAvatar = normalizedTool === "codex" && codexUsage !== null && codexWeeklyUsage !== null;
|
|
820
|
+
const useDynamicCursorAvatar = normalizedTool === "cursor" && cursorBatteryPercent !== null;
|
|
821
|
+
const useDynamicBadgeAvatar = useDynamicCodexAvatar || useDynamicCursorAvatar || useFastCodexAvatar;
|
|
822
|
+
const basePath = useDynamicBadgeAvatar
|
|
823
|
+
? AVATAR_SOURCES[normalizedStatus]
|
|
824
|
+
: hasAgentBadge
|
|
825
|
+
? avatarCombinationPath(normalizedTool, normalizedStatus)
|
|
826
|
+
: AVATAR_SOURCES[normalizedStatus];
|
|
827
|
+
|
|
828
|
+
if (useDynamicCodexAvatar) {
|
|
829
|
+
if (codexUsage.fiveHour) {
|
|
830
|
+
composites.push({ input: buildCodexUsageRingSvg(codexUsage.fiveHour.remainingPercent), left: 0, top: 0 });
|
|
831
|
+
}
|
|
832
|
+
composites.push({ input: buildCodexUsageBatterySvg(codexWeeklyUsage.remainingPercent), left: 0, top: 0 });
|
|
833
|
+
} else if (useDynamicCursorAvatar) {
|
|
834
|
+
composites.push({ input: buildCodexUsageBatterySvg(cursorBatteryPercent), left: 0, top: 0 });
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (useDynamicBadgeAvatar) {
|
|
838
|
+
if (useFastCodexAvatar) composites.push(buildCodexFastFrameOverlay());
|
|
839
|
+
composites.push(await buildAgentBadgeOverlay(normalizedTool, useFastCodexAvatar ? 11 : AVATAR_BADGE_MARGIN));
|
|
840
|
+
}
|
|
841
841
|
|
|
842
842
|
let pipeline = sharp(await readFile(basePath))
|
|
843
843
|
.resize(AVATAR_SIZE, AVATAR_SIZE, { fit: "cover", position: "center" });
|
|
@@ -851,26 +851,26 @@ async function renderAvatar(
|
|
|
851
851
|
.jpeg({ quality: 95, progressive: false })
|
|
852
852
|
.toBuffer();
|
|
853
853
|
|
|
854
|
-
return {
|
|
855
|
-
buffer: jpeg,
|
|
856
|
-
contentType: "image/jpeg",
|
|
857
|
-
filename: normalizedTool === "codex" && codexUsage?.weekly
|
|
858
|
-
? `avatar_${normalizedTool}_${normalizedStatus}${useFastCodexAvatar ? "_fast" : ""}_7d_${codexUsage.weekly.remainingPercent}${codexUsage.fiveHour ? `_5h_${codexUsage.fiveHour.remainingPercent}` : ""}.jpg`
|
|
859
|
-
: normalizedTool === "cursor" && cursorBatteryPercent !== null
|
|
860
|
-
? `avatar_${normalizedTool}_${normalizedStatus}_battery_${cursorBatteryPercent}.jpg`
|
|
861
|
-
: `avatar_${normalizedTool}_${normalizedStatus}${useFastCodexAvatar ? "_fast" : ""}.jpg`,
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
async function uploadImage(
|
|
866
|
-
token: string,
|
|
867
|
-
tool: string,
|
|
868
|
-
status: string,
|
|
869
|
-
codexUsage: CodexUsageSummary | null = null,
|
|
870
|
-
cursorBatteryPercent: number | null = null,
|
|
871
|
-
fastMode = false,
|
|
872
|
-
): Promise<string> {
|
|
873
|
-
const image = await renderAvatar(tool, status, codexUsage, cursorBatteryPercent, fastMode);
|
|
854
|
+
return {
|
|
855
|
+
buffer: jpeg,
|
|
856
|
+
contentType: "image/jpeg",
|
|
857
|
+
filename: normalizedTool === "codex" && codexUsage?.weekly
|
|
858
|
+
? `avatar_${normalizedTool}_${normalizedStatus}${useFastCodexAvatar ? "_fast" : ""}_7d_${codexUsage.weekly.remainingPercent}${codexUsage.fiveHour ? `_5h_${codexUsage.fiveHour.remainingPercent}` : ""}.jpg`
|
|
859
|
+
: normalizedTool === "cursor" && cursorBatteryPercent !== null
|
|
860
|
+
? `avatar_${normalizedTool}_${normalizedStatus}_battery_${cursorBatteryPercent}.jpg`
|
|
861
|
+
: `avatar_${normalizedTool}_${normalizedStatus}${useFastCodexAvatar ? "_fast" : ""}.jpg`,
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async function uploadImage(
|
|
866
|
+
token: string,
|
|
867
|
+
tool: string,
|
|
868
|
+
status: string,
|
|
869
|
+
codexUsage: CodexUsageSummary | null = null,
|
|
870
|
+
cursorBatteryPercent: number | null = null,
|
|
871
|
+
fastMode = false,
|
|
872
|
+
): Promise<string> {
|
|
873
|
+
const image = await renderAvatar(tool, status, codexUsage, cursorBatteryPercent, fastMode);
|
|
874
874
|
const blob = new Blob([new Uint8Array(image.buffer)], { type: image.contentType });
|
|
875
875
|
const form = new FormData();
|
|
876
876
|
form.append("image_type", "avatar");
|
|
@@ -901,14 +901,14 @@ async function getOrUploadAvatarKey(
|
|
|
901
901
|
const normalizedTool = normalizeAvatarTool(tool);
|
|
902
902
|
const normalizedStatus = normalizeAvatarStatus(status);
|
|
903
903
|
const codexUsage = normalizedTool === "codex" ? await resolveCodexAvatarUsage(usageHints.codexUsage) : null;
|
|
904
|
-
const cursorBatteryPercent = normalizedTool === "cursor"
|
|
905
|
-
? await resolveCursorAvatarBatteryPercent(usageHints.cursorUsage)
|
|
906
|
-
: null;
|
|
907
|
-
const fastMode = normalizedTool === "codex" && usageHints.fastMode === true;
|
|
908
|
-
const keyName = avatarCacheKey(normalizedTool, normalizedStatus, codexUsage, cursorBatteryPercent, fastMode);
|
|
909
|
-
const cached = avatarKeyCache.get(keyName);
|
|
910
|
-
if (cached) return cached;
|
|
911
|
-
const key = await uploadImage(token, normalizedTool, normalizedStatus, codexUsage, cursorBatteryPercent, fastMode);
|
|
904
|
+
const cursorBatteryPercent = normalizedTool === "cursor"
|
|
905
|
+
? await resolveCursorAvatarBatteryPercent(usageHints.cursorUsage)
|
|
906
|
+
: null;
|
|
907
|
+
const fastMode = normalizedTool === "codex" && usageHints.fastMode === true;
|
|
908
|
+
const keyName = avatarCacheKey(normalizedTool, normalizedStatus, codexUsage, cursorBatteryPercent, fastMode);
|
|
909
|
+
const cached = avatarKeyCache.get(keyName);
|
|
910
|
+
if (cached) return cached;
|
|
911
|
+
const key = await uploadImage(token, normalizedTool, normalizedStatus, codexUsage, cursorBatteryPercent, fastMode);
|
|
912
912
|
avatarKeyCache.set(keyName, key);
|
|
913
913
|
await persistAvatarKeyCache().catch((err) => {
|
|
914
914
|
console.error(`[${ts()}] [AVATAR] persist cache FAIL: ${(err as Error).message}`);
|
|
@@ -1056,7 +1056,7 @@ export function formatDelayNotice(createTimeMs: number, messageText?: string, no
|
|
|
1056
1056
|
}
|
|
1057
1057
|
|
|
1058
1058
|
const contentLine = messageText ? `\n> 原始内容:${messageText.slice(0, 200)}` : "";
|
|
1059
|
-
return `> ⚠️ 延迟送达提醒:此消息于 ${sendTimeStr} 发送,现延迟约 ${delayStr}后送达${contentLine}`;
|
|
1059
|
+
return `> ⚠️ 延迟送达提醒:此消息于 ${sendTimeStr} 发送,现延迟约 ${delayStr}后送达${contentLine}`;
|
|
1060
1060
|
}
|
|
1061
1061
|
|
|
1062
1062
|
/**
|