chatccc 0.2.181 → 0.2.182

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.
@@ -1,36 +1,36 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- ABD_APPEND_PROMPT,
5
- applySharedPrefix,
6
- } from "../shared-prefix.ts";
7
-
8
- describe("applySharedPrefix", () => {
9
- it("leaves normal messages unchanged", () => {
10
- expect(applySharedPrefix("帮我分析")).toEqual({
11
- matched: false,
12
- text: "帮我分析",
13
- body: "帮我分析",
14
- });
15
- });
16
-
17
- it("removes /abd without requiring a space", () => {
18
- const result = applySharedPrefix("/abd帮我分析");
19
-
20
- expect(result.matched).toBe(true);
21
- expect(result.body).toBe("帮我分析");
22
- expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
23
- });
24
-
25
- it("removes whitespace after /abd", () => {
26
- const result = applySharedPrefix("/abd 帮我分析");
27
-
28
- expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
29
- });
30
-
31
- it("keeps the appendix when the user body is empty", () => {
32
- const result = applySharedPrefix("/abd ");
33
-
34
- expect(result.text).toBe(`---\n${ABD_APPEND_PROMPT}`);
35
- });
36
- });
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ ABD_APPEND_PROMPT,
5
+ applySharedPrefix,
6
+ } from "../shared-prefix.ts";
7
+
8
+ describe("applySharedPrefix", () => {
9
+ it("leaves normal messages unchanged", () => {
10
+ expect(applySharedPrefix("帮我分析")).toEqual({
11
+ matched: false,
12
+ text: "帮我分析",
13
+ body: "帮我分析",
14
+ });
15
+ });
16
+
17
+ it("removes /abd without requiring a space", () => {
18
+ const result = applySharedPrefix("/abd帮我分析");
19
+
20
+ expect(result.matched).toBe(true);
21
+ expect(result.body).toBe("帮我分析");
22
+ expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
23
+ });
24
+
25
+ it("removes whitespace after /abd", () => {
26
+ const result = applySharedPrefix("/abd 帮我分析");
27
+
28
+ expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
29
+ });
30
+
31
+ it("keeps the appendix when the user body is empty", () => {
32
+ const result = applySharedPrefix("/abd ");
33
+
34
+ expect(result.text).toBe(`---\n${ABD_APPEND_PROMPT}`);
35
+ });
36
+ });
package/src/cards.ts CHANGED
@@ -1,10 +1,10 @@
1
- // ---------------------------------------------------------------------------
2
- // Button helpers
3
- // ---------------------------------------------------------------------------
4
-
5
- import { ABD_HELP_LINE } from "./shared-prefix.ts";
6
-
7
- export interface ButtonDef {
1
+ // ---------------------------------------------------------------------------
2
+ // Button helpers
3
+ // ---------------------------------------------------------------------------
4
+
5
+ import { ABD_HELP_LINE } from "./shared-prefix.ts";
6
+
7
+ export interface ButtonDef {
8
8
  text: string;
9
9
  value: string;
10
10
  type?: "primary" | "default" | "danger";
@@ -137,11 +137,11 @@ export function buildHelpCard(
137
137
  "发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
138
138
  "发送 **/plan** 以规划模式提问(只读,不执行写操作)",
139
139
  "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
140
- "发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡",
141
- "发送 **/restart** 重启 ChatCCC 进程",
142
- "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
143
- ABD_HELP_LINE,
144
- ].join("\n");
140
+ "发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡",
141
+ "发送 **/restart** 重启 ChatCCC 进程",
142
+ "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
143
+ ABD_HELP_LINE,
144
+ ].join("\n");
145
145
  return JSON.stringify({
146
146
  config: { wide_screen_mode: true },
147
147
  header: { template: "blue", title: { content: "ChatCCC", tag: "plain_text" } },
@@ -410,11 +410,11 @@ export function buildQueueFullCard(): string {
410
410
  }
411
411
 
412
412
  // 状态卡片(带关闭按钮)
413
- export function buildStatusCard(statusText: string, template = "blue"): string {
414
- return JSON.stringify({
415
- config: { wide_screen_mode: true },
416
- header: { template, title: { content: "会话状态", tag: "plain_text" } },
417
- elements: [
413
+ export function buildStatusCard(statusText: string, template = "blue"): string {
414
+ return JSON.stringify({
415
+ config: { wide_screen_mode: true },
416
+ header: { template, title: { content: "会话状态", tag: "plain_text" } },
417
+ elements: [
418
418
  { tag: "div", text: { tag: "lark_md", content: statusText } },
419
419
  { tag: "hr" },
420
420
  {
@@ -426,78 +426,78 @@ export function buildStatusCard(statusText: string, template = "blue"): string {
426
426
  value: { action: "close" },
427
427
  }],
428
428
  },
429
- ],
430
- });
431
- }
432
-
433
- export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
434
- const elements: object[] = [
435
- { tag: "div", text: { tag: "lark_md", content } },
436
- ];
437
- if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
438
- elements.push({ tag: "hr" });
439
- elements.push({
440
- tag: "action",
441
- actions: [{
442
- tag: "button",
443
- text: { tag: "plain_text", content: "发起重置" },
444
- type: "primary",
445
- value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
446
- }],
447
- });
448
- }
449
-
450
- return JSON.stringify({
451
- config: { wide_screen_mode: true },
452
- header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
453
- elements,
454
- });
455
- }
456
-
457
- export function buildCodexResetConfirmCard(params: {
458
- availableCount: number;
459
- parentMessageId: string;
460
- requestId: string;
461
- }): string {
462
- const valueBase = {
463
- parentMessageId: params.parentMessageId,
464
- requestId: params.requestId,
465
- };
466
- return JSON.stringify({
467
- config: { wide_screen_mode: true },
468
- header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
469
- elements: [
470
- {
471
- tag: "div",
472
- text: {
473
- tag: "lark_md",
474
- content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
475
- },
476
- },
477
- { tag: "hr" },
478
- {
479
- tag: "action",
480
- actions: [
481
- {
482
- tag: "button",
483
- text: { tag: "plain_text", content: "是,发起重置" },
484
- type: "danger",
485
- value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
486
- },
487
- {
488
- tag: "button",
489
- text: { tag: "plain_text", content: "否" },
490
- type: "default",
491
- value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
492
- },
493
- ],
494
- },
495
- ],
496
- });
497
- }
498
-
499
- /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
500
- export function buildModelCard(
429
+ ],
430
+ });
431
+ }
432
+
433
+ export function buildCodexUsageCard(content: string, resetCreditsAvailable: number | null): string {
434
+ const elements: object[] = [
435
+ { tag: "div", text: { tag: "lark_md", content } },
436
+ ];
437
+ if (resetCreditsAvailable !== null && resetCreditsAvailable > 0) {
438
+ elements.push({ tag: "hr" });
439
+ elements.push({
440
+ tag: "action",
441
+ actions: [{
442
+ tag: "button",
443
+ text: { tag: "plain_text", content: "发起重置" },
444
+ type: "primary",
445
+ value: { action: "codex_reset_request", availableCount: resetCreditsAvailable },
446
+ }],
447
+ });
448
+ }
449
+
450
+ return JSON.stringify({
451
+ config: { wide_screen_mode: true },
452
+ header: { template: "blue", title: { content: "Codex Usage", tag: "plain_text" } },
453
+ elements,
454
+ });
455
+ }
456
+
457
+ export function buildCodexResetConfirmCard(params: {
458
+ availableCount: number;
459
+ parentMessageId: string;
460
+ requestId: string;
461
+ }): string {
462
+ const valueBase = {
463
+ parentMessageId: params.parentMessageId,
464
+ requestId: params.requestId,
465
+ };
466
+ return JSON.stringify({
467
+ config: { wide_screen_mode: true },
468
+ header: { template: "yellow", title: { content: "确认 Codex 主动重置", tag: "plain_text" } },
469
+ elements: [
470
+ {
471
+ tag: "div",
472
+ text: {
473
+ tag: "lark_md",
474
+ content: `当前可用主动重置次数:**${params.availableCount}**。\n\n确认后会消耗 1 次主动重置,并重置当前可重置的 Codex 用量窗口。`,
475
+ },
476
+ },
477
+ { tag: "hr" },
478
+ {
479
+ tag: "action",
480
+ actions: [
481
+ {
482
+ tag: "button",
483
+ text: { tag: "plain_text", content: "是,发起重置" },
484
+ type: "danger",
485
+ value: { action: "codex_reset_confirm", decision: "yes", ...valueBase },
486
+ },
487
+ {
488
+ tag: "button",
489
+ text: { tag: "plain_text", content: "否" },
490
+ type: "default",
491
+ value: { action: "codex_reset_confirm", decision: "no", ...valueBase },
492
+ },
493
+ ],
494
+ },
495
+ ],
496
+ });
497
+ }
498
+
499
+ /** 模型切换卡片(/model 命令,飞书专用,含切换按钮) */
500
+ export function buildModelCard(
501
501
  currentModel: string,
502
502
  models: string[],
503
503
  tool?: string,
@@ -14,8 +14,10 @@ interface PendingCodexResetRequest {
14
14
  export interface CodexResetActionDeps {
15
15
  getTenantAccessToken(): Promise<string>;
16
16
  sendRawCard(token: string, chatId: string, cardJson: string): Promise<boolean>;
17
+ sendTextReply(token: string, chatId: string, text: string): Promise<boolean>;
17
18
  sendCardReply(token: string, chatId: string, title: string, content: string, template?: string): Promise<boolean>;
18
19
  updateCardMessage(token: string, messageId: string, content: string): Promise<boolean>;
20
+ recallMessage(token: string, messageId: string): Promise<boolean>;
19
21
  consumeCodexRateLimitResetCredit(redeemRequestId: string): Promise<CodexResetConsumeResult>;
20
22
  createRequestId?: () => string;
21
23
  }
@@ -38,7 +40,13 @@ function eventMessageId(raw: Record<string, unknown>): string {
38
40
  ? raw.open_message_id
39
41
  : typeof (raw.context as Record<string, unknown> | undefined)?.open_message_id === "string"
40
42
  ? (raw.context as Record<string, string>).open_message_id
41
- : "";
43
+ : typeof raw.message_id === "string"
44
+ ? raw.message_id
45
+ : typeof (raw.context as Record<string, unknown> | undefined)?.message_id === "string"
46
+ ? (raw.context as Record<string, string>).message_id
47
+ : typeof (raw.message as Record<string, unknown> | undefined)?.message_id === "string"
48
+ ? (raw.message as Record<string, string>).message_id
49
+ : "";
42
50
  }
43
51
 
44
52
  function eventChatId(raw: Record<string, unknown>): string {
@@ -124,7 +132,12 @@ async function handleResetConfirmation(
124
132
  const token = await deps.getTenantAccessToken();
125
133
  const confirmMessageId = eventMessageId(raw);
126
134
  if (confirmMessageId) {
127
- await deps.updateCardMessage(token, confirmMessageId, collapsedCard());
135
+ const collapsed = await deps.updateCardMessage(token, confirmMessageId, collapsedCard());
136
+ console.log(`[Codex Reset] collapse confirmation card ${collapsed ? "OK" : "FAILED"}: messageId=${confirmMessageId}`);
137
+ const recalled = await deps.recallMessage(token, confirmMessageId);
138
+ console.log(`[Codex Reset] recall confirmation card ${recalled ? "OK" : "FAILED"}: messageId=${confirmMessageId}`);
139
+ } else {
140
+ console.error("[Codex Reset] missing confirmation card message id; cannot collapse");
128
141
  }
129
142
 
130
143
  const pending = pendingCodexResetRequests.get(requestId);
@@ -132,22 +145,22 @@ async function handleResetConfirmation(
132
145
  if (!chatId) return true;
133
146
 
134
147
  if (!pending || pending.status !== "pending" || pending.parentMessageId !== parentMessageId) {
135
- await deps.sendCardReply(token, chatId, "Codex 主动重置", "这张确认卡片已经失效或已处理。", "yellow");
148
+ await deps.sendTextReply(token, chatId, "Codex 主动重置:这张确认卡片已经失效或已处理。");
136
149
  return true;
137
150
  }
138
151
  pending.status = "handled";
139
152
 
140
153
  if (decision === "no") {
141
- await deps.sendCardReply(token, chatId, "Codex 主动重置", "用户取消了重置。", "grey");
154
+ await deps.sendTextReply(token, chatId, "Codex 主动重置:用户取消了重置。");
142
155
  return true;
143
156
  }
144
157
 
145
158
  try {
146
159
  const result = await deps.consumeCodexRateLimitResetCredit(requestId);
147
160
  const message = resetResultMessage(result);
148
- await deps.sendCardReply(token, chatId, "Codex 主动重置", message.content, message.template);
161
+ await deps.sendTextReply(token, chatId, `Codex 主动重置:${message.content}`);
149
162
  } catch (err) {
150
- await deps.sendCardReply(token, chatId, "Codex 主动重置", `重置失败:${(err as Error).message}`, "red");
163
+ await deps.sendTextReply(token, chatId, `Codex 主动重置失败:${(err as Error).message}`);
151
164
  }
152
165
  return true;
153
166
  }