chatccc 0.2.213 → 0.2.215
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 -0
- package/.agents/skills/create-chatccc-feishu-app/agents/openai.yaml +4 -0
- package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -0
- package/.claude/skills/create-chatccc-feishu-app/agents/openai.yaml +4 -0
- package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -0
- package/.cursor/skills/create-chatccc-feishu-app/agents/openai.yaml +4 -0
- package/README.md +12 -7
- package/config.sample.json +2 -1
- package/package.json +4 -1
- package/src/__tests__/cards.test.ts +37 -1
- package/src/__tests__/codex-adapter.test.ts +18 -3
- package/src/__tests__/config-reload.test.ts +6 -5
- package/src/__tests__/config-sample.test.ts +8 -7
- package/src/__tests__/orchestrator.test.ts +60 -1
- package/src/__tests__/package-files.test.ts +24 -0
- package/src/__tests__/session.test.ts +24 -2
- package/src/__tests__/web-ui.test.ts +17 -2
- package/src/adapters/codex-adapter.ts +40 -6
- package/src/cards.ts +43 -2
- package/src/config.ts +10 -6
- package/src/orchestrator.ts +116 -21
- package/src/session.ts +26 -5
- package/src/web-ui.ts +54 -37
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-chatccc-feishu-app
|
|
3
|
+
description: Create, clone, or configure a Feishu enterprise self-built bot application for ChatCCC. Use when asked to create a ChatCCC Feishu app, imitate an existing lowercase `chatccc` app, prepare a bot for another computer, or configure its bot capability, `im:`/`cardkit:` permissions, events, credentials, version, and internal release from this repository's README.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a ChatCCC Feishu App
|
|
7
|
+
|
|
8
|
+
Use the logged-in Feishu developer console to create and completely configure a ChatCCC bot app. Treat this repository's README as the minimum requirement and the existing app named exactly lowercase `chatccc` as the configuration template.
|
|
9
|
+
|
|
10
|
+
## 1. Establish the target and authorization
|
|
11
|
+
|
|
12
|
+
- Read the `README.md` section `#### 飞书(推荐)` before changing the console.
|
|
13
|
+
- Determine the requested app name, target computer, description, icon, availability, and whether the user wants configuration only or a real release. Derive a concise description when one is not supplied; for example, `用飞书聊天控制<目标电脑>上的 Claude Code / Cursor / Codex。`.
|
|
14
|
+
- Use the browser surface the user requested. If the user specifies Chrome DevTools/CDP, connect directly to `chromeDevtools.port` or the README default `15166`; do not substitute extension-based Chrome control.
|
|
15
|
+
- Obtain action-time confirmation before creating the app, granting permissions, saving event/callback subscriptions, or publishing. One explicit instruction such as `完整配置并真正发布` may authorize the listed actions as a batch.
|
|
16
|
+
|
|
17
|
+
## 2. Inventory the lowercase template
|
|
18
|
+
|
|
19
|
+
1. Open `https://open.feishu.cn/app?from=devbotmenu` in the authenticated browser.
|
|
20
|
+
2. Open the app named exactly lowercase `chatccc` in a separate tab.
|
|
21
|
+
3. Record its non-secret configuration:
|
|
22
|
+
- app description and icon;
|
|
23
|
+
- application capabilities, especially `机器人`;
|
|
24
|
+
- every granted permission and its identity type;
|
|
25
|
+
- event and callback subscription modes;
|
|
26
|
+
- every event and callback identifier;
|
|
27
|
+
- version availability, external sharing choices, and release state.
|
|
28
|
+
4. Never reveal, log, export to chat, or persist the template App Secret.
|
|
29
|
+
|
|
30
|
+
Prefer current console data over a stale hard-coded list. At the time this workflow was verified, lowercase `chatccc` had 62 application-identity permissions, one user-identity permission, 12 application events, and one callback. Use those counts as a sanity check, not as a replacement for inspecting the template.
|
|
31
|
+
|
|
32
|
+
## 3. Create the target app
|
|
33
|
+
|
|
34
|
+
1. Choose `创建企业自建应用` and fill the requested name and description.
|
|
35
|
+
2. Reuse the lowercase `chatccc` icon only when imitation was requested, and verify its crop preview.
|
|
36
|
+
3. Click the final `创建` action only after confirmation.
|
|
37
|
+
4. Click once and wait for SPA navigation. Verify the exact app name, App ID, URL, and status before retrying; the console can temporarily show stale text after a successful mutation.
|
|
38
|
+
|
|
39
|
+
## 4. Clone the complete ChatCCC configuration
|
|
40
|
+
|
|
41
|
+
Perform the configuration in this order:
|
|
42
|
+
|
|
43
|
+
1. Add the `机器人` application capability and verify that the page offers `删除能力`, which means the capability is enabled.
|
|
44
|
+
2. Clone permissions with `权限管理` > `批量处理` > `批量导入/导出权限`:
|
|
45
|
+
- export from lowercase `chatccc`;
|
|
46
|
+
- import into the target app;
|
|
47
|
+
- confirm all additions and verify both application-identity and user-identity totals;
|
|
48
|
+
- do not save the export JSON in the repository or chat.
|
|
49
|
+
3. Under `事件与回调` > `事件配置`, set the subscription mode to `使用长连接接收事件`, then add every event present in the template. The verified baseline is:
|
|
50
|
+
- `im.chat.disbanded_v1`
|
|
51
|
+
- `im.chat.member.bot.added_v1`
|
|
52
|
+
- `im.chat.member.bot.deleted_v1`
|
|
53
|
+
- `im.chat.member.user.added_v1`
|
|
54
|
+
- `im.chat.member.user.deleted_v1`
|
|
55
|
+
- `im.chat.member.user.withdrawn_v1`
|
|
56
|
+
- `im.message.bot_muted_v1`
|
|
57
|
+
- `im.message.message_read_v1`
|
|
58
|
+
- `im.message.reaction.created_v1`
|
|
59
|
+
- `im.message.reaction.deleted_v1`
|
|
60
|
+
- `im.message.recalled_v1`
|
|
61
|
+
- `im.message.receive_v1`
|
|
62
|
+
4. Under `回调配置`, independently set `使用长连接接收回调` and add `card.action.trigger`. Configuring the event mode does not configure the callback mode.
|
|
63
|
+
5. Verify that every listed event shows a sufficient permission as `已开通`.
|
|
64
|
+
|
|
65
|
+
The README minimum is the bot capability, relevant `im:` and `cardkit:` permissions, `im.message.receive_v1`, and `card.action.trigger`; a clone request requires the complete current template configuration, not only that minimum.
|
|
66
|
+
|
|
67
|
+
## 5. Create and publish a version
|
|
68
|
+
|
|
69
|
+
1. Open `版本管理与发布` and create a version such as `1.0.0`.
|
|
70
|
+
2. Keep mobile and desktop default abilities set to `机器人` and write a meaningful update note.
|
|
71
|
+
3. Match the requested or template availability. For a personal computer, default to the owner-only internal range and leave external group/private-chat sharing disabled unless explicitly requested.
|
|
72
|
+
4. Save the version, then complete the separate `确认发布` action. A successful version creation alone is not a release.
|
|
73
|
+
5. Refresh the version list and require all of the following evidence before reporting success:
|
|
74
|
+
- application status `已启用`;
|
|
75
|
+
- banner `当前修改均已发布`;
|
|
76
|
+
- version status `已发布` with a publication time.
|
|
77
|
+
6. If administrator review is required, submit everything authorized and report the exact remaining approval state.
|
|
78
|
+
|
|
79
|
+
## 6. Verify and hand off safely
|
|
80
|
+
|
|
81
|
+
- Re-open the bot, permission, event/callback, and version pages after publication. Verify the bot is enabled, permission identity counts match, both subscription modes are long connection, event/callback identifiers match, and the version is published.
|
|
82
|
+
- Treat network responses and a refreshed page as stronger evidence than an immediately stale SPA view. Never repeat a create, add, or publish action only because the first page read is stale.
|
|
83
|
+
- Report the app name, App ID, capability, permission counts, event/callback counts, version, release state, and any remaining action.
|
|
84
|
+
- Never print the App Secret. If the user explicitly asks to configure ChatCCC, transfer the App ID and App Secret directly into the local private configuration without echoing the secret in commands, logs, screenshots, files tracked by Git, or chat.
|
|
85
|
+
- Do not modify local ChatCCC configuration or transmit credentials elsewhere unless explicitly authorized.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-chatccc-feishu-app
|
|
3
|
+
description: Create, clone, or configure a Feishu enterprise self-built bot application for ChatCCC. Use when asked to create a ChatCCC Feishu app, imitate an existing lowercase `chatccc` app, prepare a bot for another computer, or configure its bot capability, `im:`/`cardkit:` permissions, events, credentials, version, and internal release from this repository's README.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a ChatCCC Feishu App
|
|
7
|
+
|
|
8
|
+
Use the logged-in Feishu developer console to create and completely configure a ChatCCC bot app. Treat this repository's README as the minimum requirement and the existing app named exactly lowercase `chatccc` as the configuration template.
|
|
9
|
+
|
|
10
|
+
## 1. Establish the target and authorization
|
|
11
|
+
|
|
12
|
+
- Read the `README.md` section `#### 飞书(推荐)` before changing the console.
|
|
13
|
+
- Determine the requested app name, target computer, description, icon, availability, and whether the user wants configuration only or a real release. Derive a concise description when one is not supplied; for example, `用飞书聊天控制<目标电脑>上的 Claude Code / Cursor / Codex。`.
|
|
14
|
+
- Use the browser surface the user requested. If the user specifies Chrome DevTools/CDP, connect directly to `chromeDevtools.port` or the README default `15166`; do not substitute extension-based Chrome control.
|
|
15
|
+
- Obtain action-time confirmation before creating the app, granting permissions, saving event/callback subscriptions, or publishing. One explicit instruction such as `完整配置并真正发布` may authorize the listed actions as a batch.
|
|
16
|
+
|
|
17
|
+
## 2. Inventory the lowercase template
|
|
18
|
+
|
|
19
|
+
1. Open `https://open.feishu.cn/app?from=devbotmenu` in the authenticated browser.
|
|
20
|
+
2. Open the app named exactly lowercase `chatccc` in a separate tab.
|
|
21
|
+
3. Record its non-secret configuration:
|
|
22
|
+
- app description and icon;
|
|
23
|
+
- application capabilities, especially `机器人`;
|
|
24
|
+
- every granted permission and its identity type;
|
|
25
|
+
- event and callback subscription modes;
|
|
26
|
+
- every event and callback identifier;
|
|
27
|
+
- version availability, external sharing choices, and release state.
|
|
28
|
+
4. Never reveal, log, export to chat, or persist the template App Secret.
|
|
29
|
+
|
|
30
|
+
Prefer current console data over a stale hard-coded list. At the time this workflow was verified, lowercase `chatccc` had 62 application-identity permissions, one user-identity permission, 12 application events, and one callback. Use those counts as a sanity check, not as a replacement for inspecting the template.
|
|
31
|
+
|
|
32
|
+
## 3. Create the target app
|
|
33
|
+
|
|
34
|
+
1. Choose `创建企业自建应用` and fill the requested name and description.
|
|
35
|
+
2. Reuse the lowercase `chatccc` icon only when imitation was requested, and verify its crop preview.
|
|
36
|
+
3. Click the final `创建` action only after confirmation.
|
|
37
|
+
4. Click once and wait for SPA navigation. Verify the exact app name, App ID, URL, and status before retrying; the console can temporarily show stale text after a successful mutation.
|
|
38
|
+
|
|
39
|
+
## 4. Clone the complete ChatCCC configuration
|
|
40
|
+
|
|
41
|
+
Perform the configuration in this order:
|
|
42
|
+
|
|
43
|
+
1. Add the `机器人` application capability and verify that the page offers `删除能力`, which means the capability is enabled.
|
|
44
|
+
2. Clone permissions with `权限管理` > `批量处理` > `批量导入/导出权限`:
|
|
45
|
+
- export from lowercase `chatccc`;
|
|
46
|
+
- import into the target app;
|
|
47
|
+
- confirm all additions and verify both application-identity and user-identity totals;
|
|
48
|
+
- do not save the export JSON in the repository or chat.
|
|
49
|
+
3. Under `事件与回调` > `事件配置`, set the subscription mode to `使用长连接接收事件`, then add every event present in the template. The verified baseline is:
|
|
50
|
+
- `im.chat.disbanded_v1`
|
|
51
|
+
- `im.chat.member.bot.added_v1`
|
|
52
|
+
- `im.chat.member.bot.deleted_v1`
|
|
53
|
+
- `im.chat.member.user.added_v1`
|
|
54
|
+
- `im.chat.member.user.deleted_v1`
|
|
55
|
+
- `im.chat.member.user.withdrawn_v1`
|
|
56
|
+
- `im.message.bot_muted_v1`
|
|
57
|
+
- `im.message.message_read_v1`
|
|
58
|
+
- `im.message.reaction.created_v1`
|
|
59
|
+
- `im.message.reaction.deleted_v1`
|
|
60
|
+
- `im.message.recalled_v1`
|
|
61
|
+
- `im.message.receive_v1`
|
|
62
|
+
4. Under `回调配置`, independently set `使用长连接接收回调` and add `card.action.trigger`. Configuring the event mode does not configure the callback mode.
|
|
63
|
+
5. Verify that every listed event shows a sufficient permission as `已开通`.
|
|
64
|
+
|
|
65
|
+
The README minimum is the bot capability, relevant `im:` and `cardkit:` permissions, `im.message.receive_v1`, and `card.action.trigger`; a clone request requires the complete current template configuration, not only that minimum.
|
|
66
|
+
|
|
67
|
+
## 5. Create and publish a version
|
|
68
|
+
|
|
69
|
+
1. Open `版本管理与发布` and create a version such as `1.0.0`.
|
|
70
|
+
2. Keep mobile and desktop default abilities set to `机器人` and write a meaningful update note.
|
|
71
|
+
3. Match the requested or template availability. For a personal computer, default to the owner-only internal range and leave external group/private-chat sharing disabled unless explicitly requested.
|
|
72
|
+
4. Save the version, then complete the separate `确认发布` action. A successful version creation alone is not a release.
|
|
73
|
+
5. Refresh the version list and require all of the following evidence before reporting success:
|
|
74
|
+
- application status `已启用`;
|
|
75
|
+
- banner `当前修改均已发布`;
|
|
76
|
+
- version status `已发布` with a publication time.
|
|
77
|
+
6. If administrator review is required, submit everything authorized and report the exact remaining approval state.
|
|
78
|
+
|
|
79
|
+
## 6. Verify and hand off safely
|
|
80
|
+
|
|
81
|
+
- Re-open the bot, permission, event/callback, and version pages after publication. Verify the bot is enabled, permission identity counts match, both subscription modes are long connection, event/callback identifiers match, and the version is published.
|
|
82
|
+
- Treat network responses and a refreshed page as stronger evidence than an immediately stale SPA view. Never repeat a create, add, or publish action only because the first page read is stale.
|
|
83
|
+
- Report the app name, App ID, capability, permission counts, event/callback counts, version, release state, and any remaining action.
|
|
84
|
+
- Never print the App Secret. If the user explicitly asks to configure ChatCCC, transfer the App ID and App Secret directly into the local private configuration without echoing the secret in commands, logs, screenshots, files tracked by Git, or chat.
|
|
85
|
+
- Do not modify local ChatCCC configuration or transmit credentials elsewhere unless explicitly authorized.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-chatccc-feishu-app
|
|
3
|
+
description: Create, clone, or configure a Feishu enterprise self-built bot application for ChatCCC. Use when asked to create a ChatCCC Feishu app, imitate an existing lowercase `chatccc` app, prepare a bot for another computer, or configure its bot capability, `im:`/`cardkit:` permissions, events, credentials, version, and internal release from this repository's README.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a ChatCCC Feishu App
|
|
7
|
+
|
|
8
|
+
Use the logged-in Feishu developer console to create and completely configure a ChatCCC bot app. Treat this repository's README as the minimum requirement and the existing app named exactly lowercase `chatccc` as the configuration template.
|
|
9
|
+
|
|
10
|
+
## 1. Establish the target and authorization
|
|
11
|
+
|
|
12
|
+
- Read the `README.md` section `#### 飞书(推荐)` before changing the console.
|
|
13
|
+
- Determine the requested app name, target computer, description, icon, availability, and whether the user wants configuration only or a real release. Derive a concise description when one is not supplied; for example, `用飞书聊天控制<目标电脑>上的 Claude Code / Cursor / Codex。`.
|
|
14
|
+
- Use the browser surface the user requested. If the user specifies Chrome DevTools/CDP, connect directly to `chromeDevtools.port` or the README default `15166`; do not substitute extension-based Chrome control.
|
|
15
|
+
- Obtain action-time confirmation before creating the app, granting permissions, saving event/callback subscriptions, or publishing. One explicit instruction such as `完整配置并真正发布` may authorize the listed actions as a batch.
|
|
16
|
+
|
|
17
|
+
## 2. Inventory the lowercase template
|
|
18
|
+
|
|
19
|
+
1. Open `https://open.feishu.cn/app?from=devbotmenu` in the authenticated browser.
|
|
20
|
+
2. Open the app named exactly lowercase `chatccc` in a separate tab.
|
|
21
|
+
3. Record its non-secret configuration:
|
|
22
|
+
- app description and icon;
|
|
23
|
+
- application capabilities, especially `机器人`;
|
|
24
|
+
- every granted permission and its identity type;
|
|
25
|
+
- event and callback subscription modes;
|
|
26
|
+
- every event and callback identifier;
|
|
27
|
+
- version availability, external sharing choices, and release state.
|
|
28
|
+
4. Never reveal, log, export to chat, or persist the template App Secret.
|
|
29
|
+
|
|
30
|
+
Prefer current console data over a stale hard-coded list. At the time this workflow was verified, lowercase `chatccc` had 62 application-identity permissions, one user-identity permission, 12 application events, and one callback. Use those counts as a sanity check, not as a replacement for inspecting the template.
|
|
31
|
+
|
|
32
|
+
## 3. Create the target app
|
|
33
|
+
|
|
34
|
+
1. Choose `创建企业自建应用` and fill the requested name and description.
|
|
35
|
+
2. Reuse the lowercase `chatccc` icon only when imitation was requested, and verify its crop preview.
|
|
36
|
+
3. Click the final `创建` action only after confirmation.
|
|
37
|
+
4. Click once and wait for SPA navigation. Verify the exact app name, App ID, URL, and status before retrying; the console can temporarily show stale text after a successful mutation.
|
|
38
|
+
|
|
39
|
+
## 4. Clone the complete ChatCCC configuration
|
|
40
|
+
|
|
41
|
+
Perform the configuration in this order:
|
|
42
|
+
|
|
43
|
+
1. Add the `机器人` application capability and verify that the page offers `删除能力`, which means the capability is enabled.
|
|
44
|
+
2. Clone permissions with `权限管理` > `批量处理` > `批量导入/导出权限`:
|
|
45
|
+
- export from lowercase `chatccc`;
|
|
46
|
+
- import into the target app;
|
|
47
|
+
- confirm all additions and verify both application-identity and user-identity totals;
|
|
48
|
+
- do not save the export JSON in the repository or chat.
|
|
49
|
+
3. Under `事件与回调` > `事件配置`, set the subscription mode to `使用长连接接收事件`, then add every event present in the template. The verified baseline is:
|
|
50
|
+
- `im.chat.disbanded_v1`
|
|
51
|
+
- `im.chat.member.bot.added_v1`
|
|
52
|
+
- `im.chat.member.bot.deleted_v1`
|
|
53
|
+
- `im.chat.member.user.added_v1`
|
|
54
|
+
- `im.chat.member.user.deleted_v1`
|
|
55
|
+
- `im.chat.member.user.withdrawn_v1`
|
|
56
|
+
- `im.message.bot_muted_v1`
|
|
57
|
+
- `im.message.message_read_v1`
|
|
58
|
+
- `im.message.reaction.created_v1`
|
|
59
|
+
- `im.message.reaction.deleted_v1`
|
|
60
|
+
- `im.message.recalled_v1`
|
|
61
|
+
- `im.message.receive_v1`
|
|
62
|
+
4. Under `回调配置`, independently set `使用长连接接收回调` and add `card.action.trigger`. Configuring the event mode does not configure the callback mode.
|
|
63
|
+
5. Verify that every listed event shows a sufficient permission as `已开通`.
|
|
64
|
+
|
|
65
|
+
The README minimum is the bot capability, relevant `im:` and `cardkit:` permissions, `im.message.receive_v1`, and `card.action.trigger`; a clone request requires the complete current template configuration, not only that minimum.
|
|
66
|
+
|
|
67
|
+
## 5. Create and publish a version
|
|
68
|
+
|
|
69
|
+
1. Open `版本管理与发布` and create a version such as `1.0.0`.
|
|
70
|
+
2. Keep mobile and desktop default abilities set to `机器人` and write a meaningful update note.
|
|
71
|
+
3. Match the requested or template availability. For a personal computer, default to the owner-only internal range and leave external group/private-chat sharing disabled unless explicitly requested.
|
|
72
|
+
4. Save the version, then complete the separate `确认发布` action. A successful version creation alone is not a release.
|
|
73
|
+
5. Refresh the version list and require all of the following evidence before reporting success:
|
|
74
|
+
- application status `已启用`;
|
|
75
|
+
- banner `当前修改均已发布`;
|
|
76
|
+
- version status `已发布` with a publication time.
|
|
77
|
+
6. If administrator review is required, submit everything authorized and report the exact remaining approval state.
|
|
78
|
+
|
|
79
|
+
## 6. Verify and hand off safely
|
|
80
|
+
|
|
81
|
+
- Re-open the bot, permission, event/callback, and version pages after publication. Verify the bot is enabled, permission identity counts match, both subscription modes are long connection, event/callback identifiers match, and the version is published.
|
|
82
|
+
- Treat network responses and a refreshed page as stronger evidence than an immediately stale SPA view. Never repeat a create, add, or publish action only because the first page read is stale.
|
|
83
|
+
- Report the app name, App ID, capability, permission counts, event/callback counts, version, release state, and any remaining action.
|
|
84
|
+
- Never print the App Secret. If the user explicitly asks to configure ChatCCC, transfer the App ID and App Secret directly into the local private configuration without echoing the secret in commands, logs, screenshots, files tracked by Git, or chat.
|
|
85
|
+
- Do not modify local ChatCCC configuration or transmit credentials elsewhere unless explicitly authorized.
|
package/README.md
CHANGED
|
@@ -295,10 +295,11 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
295
295
|
},
|
|
296
296
|
"codex": {
|
|
297
297
|
"enabled": false,
|
|
298
|
-
"defaultAgent": false,
|
|
299
|
-
"path": "",
|
|
300
|
-
"model": "",
|
|
301
|
-
"effort": ""
|
|
298
|
+
"defaultAgent": false,
|
|
299
|
+
"path": "",
|
|
300
|
+
"model": "",
|
|
301
|
+
"effort": "",
|
|
302
|
+
"fastMode": false
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
```
|
|
@@ -319,7 +320,8 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
319
320
|
| `allowInterrupt` | 是否允许新消息中断正在运行的任务;默认 false |
|
|
320
321
|
| `*.enabled` | 是否启用对应 AI Agent |
|
|
321
322
|
| `*.defaultAgent` | `/new` 未指定 Agent 时使用哪个工具;飞书私聊会在下一条普通消息到达时跟随变化并创建新的空会话 |
|
|
322
|
-
| `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
|
|
323
|
+
| `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
|
|
324
|
+
| `codex.fastMode` | Codex Fast 模式的全局默认值;默认 `false`,每次调用都会显式覆盖 Codex CLI 的 service tier |
|
|
323
325
|
| `cursor.avatarBatteryMode` | Cursor 头像电量显示来源:`apiPercent` 或 `onDemandUse` |
|
|
324
326
|
| `cursor.onDemandMonthlyBudget` | `avatarBatteryMode=onDemandUse` 时用于计算电量的月预算 |
|
|
325
327
|
| `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
|
|
@@ -345,8 +347,9 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
345
347
|
| `/new cursor` | 创建 Cursor 会话;飞书中会创建新群 |
|
|
346
348
|
| `/new codex` | 创建 Codex 会话;飞书中会创建新群 |
|
|
347
349
|
| `/newh` | 在当前聊天原地重置会话;群聊保留工作目录,飞书私聊固定使用系统用户目录 |
|
|
348
|
-
| `/model` | 查看或切换当前会话的模型 |
|
|
349
|
-
| `/
|
|
350
|
+
| `/model` | 查看或切换当前会话的模型 |
|
|
351
|
+
| `/fast` | 查看当前 Codex 会话的 Fast 模式;使用 `/fast on` 或 `/fast off` 切换 |
|
|
352
|
+
| `/stop` | 停止当前回复 |
|
|
350
353
|
| `/cancel` | 取消当前会话里排队等待处理的消息 |
|
|
351
354
|
| `/state` | 查看当前会话状态 |
|
|
352
355
|
| `/cd` | 查看或设置后续新建会话的默认工作目录,不改变当前会话;飞书私聊自身始终使用系统用户目录 |
|
|
@@ -364,6 +367,8 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
364
367
|
`/update` 会在执行 npm 更新前把飞书消息或按钮事件 ID 原子写入 `~/.chatccc/state/update-command-guard.json`。同一 ID 跨重启重投时会静默忽略;用户主动发送的新 `/update` 因事件 ID 不同,仍可立即执行。该保护仅作用于 `/update`,普通消息与 `/restart` 的处理不变。
|
|
365
368
|
|
|
366
369
|
> **模型切换**:`/model` 查看当前会话 Agent 的可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。可选模型来自当前 Agent 的配置:Claude 使用 `claude.model` / `claude.subagentModel`,Cursor 使用 `cursor.model`,Codex 使用 `codex.model`。
|
|
370
|
+
|
|
371
|
+
> **Codex Fast 模式**:Web UI 中的“Fast 模式”设置新 Codex 会话的全局默认值,默认关闭。进入 Codex 会话后,`/fast` 查询当前状态,`/fast on` 和 `/fast off` 只覆盖当前会话并从下一条消息生效。ChatCCC 会显式向 Codex CLI 传入 `service_tier="fast"` 或 `service_tier="default"`,因此关闭时不会继承用户 `config.toml` 中可能开启的 Fast。
|
|
367
372
|
|
|
368
373
|
---
|
|
369
374
|
|
package/config.sample.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatccc",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.215",
|
|
4
4
|
"description": "Feishu bot bridge for Claude Code",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"demo/ilink_echo_probe.ts",
|
|
17
17
|
"agent-prompts/",
|
|
18
18
|
"im-skills/",
|
|
19
|
+
".agents/skills/create-chatccc-feishu-app/",
|
|
20
|
+
".claude/skills/create-chatccc-feishu-app/",
|
|
21
|
+
".cursor/skills/create-chatccc-feishu-app/",
|
|
19
22
|
"images/img_readme_*.jpg",
|
|
20
23
|
"images/img_readme_*.png",
|
|
21
24
|
"images/avatars/status_*.png",
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
buildStatusCard,
|
|
9
9
|
buildCodexUsageCard,
|
|
10
10
|
buildCodexResetConfirmCard,
|
|
11
|
+
buildFastModeCard,
|
|
12
|
+
buildModelCard,
|
|
11
13
|
buildButtons,
|
|
12
14
|
truncateContent,
|
|
13
15
|
getToolEmoji,
|
|
@@ -18,7 +20,7 @@ import { ABD_HELP_LINE } from "../shared-prefix.ts";
|
|
|
18
20
|
// truncateContent
|
|
19
21
|
// ---------------------------------------------------------------------------
|
|
20
22
|
|
|
21
|
-
describe("truncateContent", () => {
|
|
23
|
+
describe("truncateContent", () => {
|
|
22
24
|
it("returns original text when under limits", () => {
|
|
23
25
|
expect(truncateContent("hello")).toBe("hello");
|
|
24
26
|
});
|
|
@@ -180,6 +182,40 @@ describe("buildHelpCard", () => {
|
|
|
180
182
|
expect(text).not.toContain("CCC Agent");
|
|
181
183
|
});
|
|
182
184
|
});
|
|
185
|
+
|
|
186
|
+
describe("Codex Fast mode cards", () => {
|
|
187
|
+
it("shows the current mode and exposes ON/OFF commands", () => {
|
|
188
|
+
const card = JSON.parse(buildFastModeCard(false)) as {
|
|
189
|
+
header: { title: { content: string } };
|
|
190
|
+
elements: Array<{
|
|
191
|
+
tag: string;
|
|
192
|
+
text?: { content: string };
|
|
193
|
+
actions?: Array<{ text: { content: string }; value: string }>;
|
|
194
|
+
}>;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
expect(card.header.title.content).toBe("Codex Fast 模式");
|
|
198
|
+
expect(card.elements.find((element) => element.tag === "div")?.text?.content).toContain("OFF");
|
|
199
|
+
const actions = card.elements.find((element) => element.tag === "action")?.actions ?? [];
|
|
200
|
+
expect(actions.map((action) => JSON.parse(action.value).cmd)).toEqual([
|
|
201
|
+
"/fast on",
|
|
202
|
+
"/fast off",
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("places /fast help immediately after /model help for Codex", () => {
|
|
207
|
+
const card = JSON.parse(buildModelCard("gpt-5", ["gpt-5"], "codex")) as {
|
|
208
|
+
elements: Array<{ tag: string; text?: { content: string } }>;
|
|
209
|
+
};
|
|
210
|
+
const content = card.elements.find((element) => element.tag === "div")?.text?.content ?? "";
|
|
211
|
+
const modelHelpIndex = content.indexOf("/model");
|
|
212
|
+
const fastHelpIndex = content.indexOf("/fast");
|
|
213
|
+
|
|
214
|
+
expect(modelHelpIndex).toBeGreaterThanOrEqual(0);
|
|
215
|
+
expect(fastHelpIndex).toBeGreaterThan(modelHelpIndex);
|
|
216
|
+
expect(content.slice(modelHelpIndex, fastHelpIndex).split("\n")).toHaveLength(2);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
183
219
|
|
|
184
220
|
// ---------------------------------------------------------------------------
|
|
185
221
|
// buildCdContent
|
|
@@ -2,8 +2,9 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
buildCodexInvocationArgs,
|
|
7
|
+
normalizeCodexMessage,
|
|
7
8
|
createCodexAdapter,
|
|
8
9
|
type CreateCodexAdapterOptions,
|
|
9
10
|
} from "../adapters/codex-adapter.ts";
|
|
@@ -14,7 +15,21 @@ import {
|
|
|
14
15
|
} from "../adapters/codex-session-meta-store.ts";
|
|
15
16
|
import { accumulateBlockContent, pickFinalReply, type AccumulatorState } from "../session.ts";
|
|
16
17
|
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
describe("buildCodexInvocationArgs", () => {
|
|
21
|
+
it("forces the priority service tier when Fast mode is on", () => {
|
|
22
|
+
expect(buildCodexInvocationArgs(["exec", "--json"], "", "", true)).toContain(
|
|
23
|
+
'service_tier="fast"',
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("forces the standard service tier when Fast mode is off", () => {
|
|
28
|
+
const args = buildCodexInvocationArgs(["exec", "--json"], "", "", false);
|
|
29
|
+
expect(args).toContain('service_tier="default"');
|
|
30
|
+
expect(args).not.toContain('service_tier="fast"');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
18
33
|
|
|
19
34
|
function readFixture(name: string): unknown[] {
|
|
20
35
|
const raw = readFileSync(join(__dirname, "fixtures", name), "utf-8");
|
|
@@ -71,7 +71,7 @@ const baseAppConfig: AppConfig = {
|
|
|
71
71
|
avatarBatteryMode: "apiPercent",
|
|
72
72
|
onDemandMonthlyBudget: 1000,
|
|
73
73
|
},
|
|
74
|
-
codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort" },
|
|
74
|
+
codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort", fastMode: false },
|
|
75
75
|
ccc: { DEEPSEEK_API_KEY: "initial-ccc-key", DEEPSEEK_BASE_URL: "https://initial.deepseek.test/v1", model: "initial-ccc-model" },
|
|
76
76
|
};
|
|
77
77
|
|
|
@@ -207,15 +207,16 @@ describe("applyLoadedConfig — config 对象引用契约", () => {
|
|
|
207
207
|
applyLoadedConfig({
|
|
208
208
|
...structuredClone(baseAppConfig),
|
|
209
209
|
feishu: { appId: "REF_TEST_APP", appSecret: "REF_TEST_SECRET" },
|
|
210
|
-
codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low" },
|
|
210
|
+
codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low", fastMode: true },
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
// 必须是同一个引用:codex-adapter 等下游模块"直接 import config",
|
|
214
214
|
// 替换引用会破坏它们对 config.codex.* 的访问。
|
|
215
215
|
expect(config).toBe(refBefore);
|
|
216
|
-
expect(config.feishu.appId).toBe("REF_TEST_APP");
|
|
217
|
-
expect(config.codex.path).toBe("/refresh/codex");
|
|
218
|
-
|
|
216
|
+
expect(config.feishu.appId).toBe("REF_TEST_APP");
|
|
217
|
+
expect(config.codex.path).toBe("/refresh/codex");
|
|
218
|
+
expect(config.codex.fastMode).toBe(true);
|
|
219
|
+
});
|
|
219
220
|
|
|
220
221
|
it("刷新 chromeDevtools 配置但保持 config 引用", () => {
|
|
221
222
|
const refBefore = config;
|
|
@@ -28,15 +28,16 @@ describe("config.sample.json", () => {
|
|
|
28
28
|
expect(sample.claude?.subagentModel).toBe("");
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it("leaves
|
|
32
|
-
const configSamplePath = join(process.cwd(), "config.sample.json");
|
|
33
|
-
const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
|
|
34
|
-
cursor?: { alternativeModel?: unknown };
|
|
35
|
-
codex?: { alternativeModel?: unknown };
|
|
36
|
-
};
|
|
37
|
-
|
|
31
|
+
it("leaves alternative models empty and Codex Fast mode off by default", () => {
|
|
32
|
+
const configSamplePath = join(process.cwd(), "config.sample.json");
|
|
33
|
+
const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
|
|
34
|
+
cursor?: { alternativeModel?: unknown };
|
|
35
|
+
codex?: { alternativeModel?: unknown; fastMode?: unknown };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
38
|
expect(sample.cursor?.alternativeModel).toBe("");
|
|
39
39
|
expect(sample.codex?.alternativeModel).toBe("");
|
|
40
|
+
expect(sample.codex?.fastMode).toBe(false);
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
it("sets ccc agent DeepSeek defaults in the sample config", () => {
|
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
recordSessionRegistry,
|
|
87
87
|
resetState,
|
|
88
88
|
sessionInfoMap,
|
|
89
|
+
getEffectiveFastModeForTool,
|
|
89
90
|
} from "../session.ts";
|
|
90
91
|
import {
|
|
91
92
|
activePrompts,
|
|
@@ -146,6 +147,7 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
146
147
|
config.claude.defaultAgent = true;
|
|
147
148
|
config.cursor.defaultAgent = false;
|
|
148
149
|
config.codex.defaultAgent = false;
|
|
150
|
+
config.codex.fastMode = false;
|
|
149
151
|
mockStreamStates.clear();
|
|
150
152
|
mockGetCodexUsageSummary.mockReset();
|
|
151
153
|
mockGetCursorUsageSummary.mockReset();
|
|
@@ -660,6 +662,55 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
660
662
|
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
661
663
|
});
|
|
662
664
|
|
|
665
|
+
it("queries and switches Fast mode for the current Codex session", async () => {
|
|
666
|
+
const platform = mockPlatform("feishu");
|
|
667
|
+
await recordSessionRegistry({
|
|
668
|
+
chatId: "feishu-codex",
|
|
669
|
+
sessionId: "sid-codex-fast",
|
|
670
|
+
tool: "codex",
|
|
671
|
+
chatType: "p2p",
|
|
672
|
+
chatName: "Codex",
|
|
673
|
+
running: false,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
await handleCommand(platform, "/fast", "feishu-codex", "ou-user", Date.now(), "p2p");
|
|
677
|
+
let card = JSON.parse(
|
|
678
|
+
vi.mocked(platform.sendRawCard).mock.calls.at(-1)?.[1] ?? "{}",
|
|
679
|
+
) as { elements?: Array<{ tag: string; text?: { content: string } }> };
|
|
680
|
+
expect(card.elements?.find((element) => element.tag === "div")?.text?.content).toContain("OFF");
|
|
681
|
+
|
|
682
|
+
await handleCommand(platform, "/fast on", "feishu-codex", "ou-user", Date.now(), "p2p");
|
|
683
|
+
expect(getEffectiveFastModeForTool("codex", "sid-codex-fast")).toBe(true);
|
|
684
|
+
|
|
685
|
+
await handleCommand(platform, "/fast off", "feishu-codex", "ou-user", Date.now(), "p2p");
|
|
686
|
+
expect(getEffectiveFastModeForTool("codex", "sid-codex-fast")).toBe(false);
|
|
687
|
+
card = JSON.parse(
|
|
688
|
+
vi.mocked(platform.sendRawCard).mock.calls.at(-1)?.[1] ?? "{}",
|
|
689
|
+
) as { elements?: Array<{ tag: string; text?: { content: string } }> };
|
|
690
|
+
expect(card.elements?.find((element) => element.tag === "div")?.text?.content).toContain("OFF");
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("uses a text fallback for Fast mode commands on WeChat", async () => {
|
|
694
|
+
const platform = mockPlatform("wechat");
|
|
695
|
+
await recordSessionRegistry({
|
|
696
|
+
chatId: "wechat-codex",
|
|
697
|
+
sessionId: "sid-wechat-codex-fast",
|
|
698
|
+
tool: "codex",
|
|
699
|
+
chatType: "p2p",
|
|
700
|
+
chatName: "Codex",
|
|
701
|
+
running: false,
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
await handleCommand(platform, "/fast on", "wechat-codex", "wx-user", Date.now(), "p2p");
|
|
705
|
+
|
|
706
|
+
expect(getEffectiveFastModeForTool("codex", "sid-wechat-codex-fast")).toBe(true);
|
|
707
|
+
expect(platform.sendText).toHaveBeenCalledWith(
|
|
708
|
+
"wechat-codex",
|
|
709
|
+
expect.stringContaining("ON (Fast)"),
|
|
710
|
+
);
|
|
711
|
+
expect(platform.sendRawCard).not.toHaveBeenCalled();
|
|
712
|
+
});
|
|
713
|
+
|
|
663
714
|
it("keeps the Feishu p2p session bound when /new creates a separate group", async () => {
|
|
664
715
|
const platform = mockPlatform("feishu");
|
|
665
716
|
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-group" }));
|
|
@@ -1023,7 +1074,15 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
1023
1074
|
"green",
|
|
1024
1075
|
);
|
|
1025
1076
|
|
|
1026
|
-
const
|
|
1077
|
+
const codexReadyCall = vi.mocked(codexPlatform.sendCard).mock.calls.find(
|
|
1078
|
+
([chatId, title]) => chatId === "feishu-group" && title === "Codex Session Ready",
|
|
1079
|
+
);
|
|
1080
|
+
const modelHelpIndex = codexReadyCall?.[2].indexOf("/model") ?? -1;
|
|
1081
|
+
const fastHelpIndex = codexReadyCall?.[2].indexOf("/fast") ?? -1;
|
|
1082
|
+
expect(modelHelpIndex).toBeGreaterThanOrEqual(0);
|
|
1083
|
+
expect(fastHelpIndex).toBeGreaterThan(modelHelpIndex);
|
|
1084
|
+
|
|
1085
|
+
const cursorPlatform = mockPlatform("feishu");
|
|
1027
1086
|
_setAdapterForToolForTest("cursor", mockAdapter("sid-cursor"));
|
|
1028
1087
|
|
|
1029
1088
|
await handleCommand(cursorPlatform, "/new cursor", "feishu-p2p-cursor", "ou-user", Date.now(), "p2p");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
const CREATE_APP_SKILL_DIRS = [
|
|
7
|
+
".agents/skills/create-chatccc-feishu-app/",
|
|
8
|
+
".claude/skills/create-chatccc-feishu-app/",
|
|
9
|
+
".cursor/skills/create-chatccc-feishu-app/",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
describe("npm package files", () => {
|
|
13
|
+
it("ships the Feishu app creation skill for every supported agent", () => {
|
|
14
|
+
const root = process.cwd();
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
|
16
|
+
files?: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
for (const skillDir of CREATE_APP_SKILL_DIRS) {
|
|
20
|
+
expect(packageJson.files).toContain(skillDir);
|
|
21
|
+
expect(existsSync(join(root, skillDir, "SKILL.md"))).toBe(true);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|