chatccc 0.2.6 → 0.2.7
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/README.md +50 -11
- package/package.json +1 -1
- package/src/__tests__/adapter-interface.test.ts +151 -151
- package/src/__tests__/cards.test.ts +417 -413
- package/src/__tests__/claude-adapter.test.ts +528 -528
- package/src/__tests__/config.test.ts +123 -0
- package/src/__tests__/cursor-adapter.test.ts +662 -249
- package/src/__tests__/cursor-session-meta-store.test.ts +212 -0
- package/src/__tests__/fixtures/cursor_partial_only.jsonl +5 -0
- package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +13 -0
- package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +12 -0
- package/src/__tests__/git-command.test.ts +288 -0
- package/src/__tests__/session.test.ts +475 -296
- package/src/adapters/adapter-interface.ts +151 -126
- package/src/adapters/claude-adapter.ts +257 -257
- package/src/adapters/cursor-adapter.ts +401 -228
- package/src/adapters/cursor-session-meta-store.ts +154 -0
- package/src/cards.ts +33 -21
- package/src/config.ts +92 -2
- package/src/feishu-api.ts +1 -1
- package/src/git-command.ts +202 -0
- package/src/index.ts +58 -4
- package/src/session.ts +620 -513
package/README.md
CHANGED
|
@@ -63,24 +63,44 @@ npm run dev
|
|
|
63
63
|
|
|
64
64
|
#### Cursor Agent CLI(使用 Cursor 会话时需要)
|
|
65
65
|
|
|
66
|
-
ChatCCC **不捆绑** Cursor Agent CLI,需要用户自行安装。
|
|
66
|
+
ChatCCC **不捆绑** Cursor Agent CLI,需要用户自行安装。
|
|
67
|
+
|
|
68
|
+
**推荐安装方式(Windows):**
|
|
69
|
+
|
|
70
|
+
在 PowerShell 中执行:
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
irm 'https://cursor.com/install?win32=true' | iex
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
安装完成后 `agent` 命令会出现在系统 PATH 中。
|
|
77
|
+
|
|
78
|
+
**其他安装方式:**
|
|
67
79
|
|
|
68
80
|
1. **Cursor IDE 自带**:安装 [Cursor IDE](https://cursor.com) 后,部分版本会自带 `agent` 或 `cursor-agent` 命令
|
|
69
|
-
2. **独立安装**:Cursor 正在逐步提供独立的 CLI
|
|
81
|
+
2. **独立安装**:Cursor 正在逐步提供独立的 CLI 安装方式
|
|
70
82
|
|
|
71
|
-
|
|
83
|
+
验证是否已安装(任一可用即可):
|
|
72
84
|
|
|
73
85
|
```bash
|
|
74
86
|
agent --version
|
|
87
|
+
cursor-agent --version
|
|
75
88
|
```
|
|
76
89
|
|
|
77
|
-
|
|
90
|
+
ChatCCC 启动时会按以下顺序探测 Cursor Agent CLI:
|
|
91
|
+
|
|
92
|
+
1. 环境变量 `CHATCCC_CURSOR_COMMAND` 指定的路径(若已设置)
|
|
93
|
+
2. Windows 上 Cursor IDE 默认安装位置 `%LOCALAPPDATA%\cursor-agent\agent.cmd`(自动识别)
|
|
94
|
+
3. PATH 中的 `agent` 命令
|
|
95
|
+
|
|
96
|
+
若以上都找不到,或你想用一个非默认的可执行文件(例如 `cursor-agent`),在 `.env` 中显式指定:
|
|
78
97
|
|
|
79
98
|
```env
|
|
80
99
|
CHATCCC_CURSOR_COMMAND=/path/to/agent
|
|
81
|
-
CHATCCC_CURSOR_ARGS=-p --output-format stream-json --stream-partial-output
|
|
82
100
|
```
|
|
83
101
|
|
|
102
|
+
> 高级用户如需覆盖默认 CLI 参数,可设置 `CHATCCC_CURSOR_ARGS`,一般无需修改。
|
|
103
|
+
|
|
84
104
|
> **说明**:只使用 Claude Code(`/new claude` 或 `/new`)的用户无需安装 Cursor CLI。
|
|
85
105
|
|
|
86
106
|
### 2. 创建飞书应用
|
|
@@ -132,14 +152,25 @@ CHATCCC_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
|
132
152
|
|
|
133
153
|
若你曾使用旧版变量名 `FEISHU_CLAUDER_APP_ID` / `FEISHU_CLAUDER_APP_SECRET`,请改为上表中的 `CHATCCC_APP_ID` / `CHATCCC_APP_SECRET`。
|
|
134
154
|
|
|
135
|
-
可选:Claude
|
|
155
|
+
可选:Claude 模型与思考深度:
|
|
136
156
|
|
|
137
157
|
```env
|
|
138
|
-
# 网关或服务商文档中的 model;设为 default(任意大小写)或不写则交给 SDK/CLI
|
|
158
|
+
# 网关或服务商文档中的 model;设为 default(任意大小写)或不写则交给 SDK/CLI 默认
|
|
139
159
|
CHATCCC_ANTHROPIC_MODEL=default
|
|
140
160
|
|
|
141
|
-
# 如 low / medium / high / max;设为 default(任意大小写)或不写则不向 SDK 传入 effort
|
|
142
|
-
CHATCCC_ANTHROPIC_EFFORT=
|
|
161
|
+
# 如 low / medium / high / max;设为 default(任意大小写)或不写则不向 SDK 传入 effort
|
|
162
|
+
CHATCCC_ANTHROPIC_EFFORT=max
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Cursor Agent CLI 模型(仅 Cursor 会话相关,省略或 `default` 时不传 `--model`):
|
|
166
|
+
|
|
167
|
+
```env
|
|
168
|
+
# Cursor 会话实际使用的模型 ID(如 claude-opus-4-7-max / claude-opus-4-7-thinking-max 等)
|
|
169
|
+
# 可用 `agent --list-models` 查看所有支持的模型 ID
|
|
170
|
+
CHATCCC_CURSOR_MODEL=claude-opus-4-7-max
|
|
171
|
+
|
|
172
|
+
# 高级用户可覆盖默认 CLI 参数(一般无需修改)
|
|
173
|
+
# CHATCCC_CURSOR_ARGS=-p --force --output-format stream-json --stream-partial-output
|
|
143
174
|
```
|
|
144
175
|
|
|
145
176
|
#### 注意!第三方 API(如 DeepSeek)与鉴权 403
|
|
@@ -172,7 +203,7 @@ CHATCCC_ANTHROPIC_EFFORT=default
|
|
|
172
203
|
|
|
173
204
|
> **说明**:项目根目录的 `.env` 若通过 `tsx --env-file=.env` 加载,其中的变量会进入 Node 进程环境,**多数情况下**会随进程继承给 SDK 子进程;但各家网关与 Claude Code 版本组合较多,**仍推荐**将 **`ANTHROPIC_API_KEY`** 与 **`ANTHROPIC_BASE_URL`** 放在系统/用户环境变量中,与 `settings.json` 形成双保险,避免仅依赖 `.claude/settings.json` 时在 SDK 场景下踩坑。
|
|
174
205
|
|
|
175
|
-
|
|
206
|
+
**`CHATCCC_PORT`(可选)**:默认端口为 `18080`。若在一台机器上同时运行多个 ChatCCC 实例,可在各自的 `.env` 中设置不同端口:
|
|
176
207
|
|
|
177
208
|
```env
|
|
178
209
|
# 实例 A(项目1)的 .env
|
|
@@ -184,6 +215,13 @@ CHATCCC_PORT=18081
|
|
|
184
215
|
|
|
185
216
|
不同端口的实例互不冲突,启动时会自动清理各自端口上的旧进程。
|
|
186
217
|
|
|
218
|
+
**`CHATCCC_GIT_TIMEOUT_SECONDS`(可选)**:`/git <子命令>` 在会话工作目录执行 git 命令时的单次超时秒数,默认 `180`,允许范围 `1–3600`。配置非法(非整数、越界等)时启动日志会标红提示并回退为默认值;命令超时则会被 `SIGKILL` 强制终止,并把已收集到的输出(带「⏱️ 命令超时被强制终止」标记)回发到群里。
|
|
219
|
+
|
|
220
|
+
```env
|
|
221
|
+
# 调高到 600 秒,适合 git fsck、git gc 等耗时操作
|
|
222
|
+
CHATCCC_GIT_TIMEOUT_SECONDS=600
|
|
223
|
+
```
|
|
224
|
+
|
|
187
225
|
> **权限说明**:目前 ChatCCC 以 `bypassPermissions` 模式运行,即跳过所有权限确认、允许所有操作。后续会考虑引入细粒度权限控制,让你可以按需放行特定操作。
|
|
188
226
|
|
|
189
227
|
> **Linux 用户注意**:不能将项目装在 `/root` 目录下运行。
|
|
@@ -204,6 +242,7 @@ CHATCCC_PORT=18081
|
|
|
204
242
|
| `/status` | 查看当前会话的状态(轮数、模型、上下文 token 等) |
|
|
205
243
|
| `/cd` | 查看/切换工作目录 |
|
|
206
244
|
| `/sessions` | 查看所有会话状态 |
|
|
245
|
+
| `/git <子命令>` | 在**当前会话工作目录**执行 `git ...` 并把 stdout/stderr 回发到群里(仅会话群内可用,超时见 `CHATCCC_GIT_TIMEOUT_SECONDS`) |
|
|
207
246
|
| `/restart` | 重启机器人进程 |
|
|
208
247
|
|
|
209
248
|
|
|
@@ -211,4 +250,4 @@ CHATCCC_PORT=18081
|
|
|
211
250
|
|
|
212
251
|
## 技术栈
|
|
213
252
|
|
|
214
|
-
TypeScript / Node.js >= 20 / tsx / Anthropic Claude Agent SDK / 飞书 WebSocket API / CardKit
|
|
253
|
+
TypeScript / Node.js >= 20 / tsx / Anthropic Claude Agent SDK / Cursor Agent CLI / 飞书 WebSocket API / CardKit
|
package/package.json
CHANGED
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import type {
|
|
3
|
-
UnifiedBlock,
|
|
4
|
-
UnifiedStreamMessage,
|
|
5
|
-
UnifiedThinkingBlock,
|
|
6
|
-
UnifiedTextBlock,
|
|
7
|
-
UnifiedToolUseBlock,
|
|
8
|
-
UnifiedToolResultBlock,
|
|
9
|
-
UnifiedRedactedThinkingBlock,
|
|
10
|
-
UnifiedSearchResultBlock,
|
|
11
|
-
UnifiedCompactBoundaryBlock,
|
|
12
|
-
CreateSessionResult,
|
|
13
|
-
SessionInfo,
|
|
14
|
-
ToolAdapter,
|
|
15
|
-
} from "../adapters/adapter-interface.ts";
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// 编译期类型验证:确保 UnifiedBlock 联合类型覆盖了所有预期的 block 形状
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
describe("UnifiedBlock union type coverage", () => {
|
|
22
|
-
it("accepts thinking block", () => {
|
|
23
|
-
const b: UnifiedBlock = { type: "thinking", thinking: "Let me think..." };
|
|
24
|
-
expect(b.type).toBe("thinking");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("accepts text block", () => {
|
|
28
|
-
const b: UnifiedBlock = { type: "text", text: "Hello" };
|
|
29
|
-
expect(b.type).toBe("text");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("accepts tool_use block", () => {
|
|
33
|
-
const b: UnifiedBlock = { type: "tool_use", name: "Read", input: { file_path: "/tmp" } };
|
|
34
|
-
expect(b.type).toBe("tool_use");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("accepts tool_result block with string content", () => {
|
|
38
|
-
const b: UnifiedBlock = { type: "tool_result", tool_use_id: "abc123", content: "result" };
|
|
39
|
-
expect(b.type).toBe("tool_result");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("accepts tool_result block with error flag", () => {
|
|
43
|
-
const b: UnifiedBlock = { type: "tool_result", tool_use_id: "abc123", content: "error", is_error: true };
|
|
44
|
-
expect(b.is_error).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("accepts redacted_thinking block", () => {
|
|
48
|
-
const b: UnifiedBlock = { type: "redacted_thinking" };
|
|
49
|
-
expect(b.type).toBe("redacted_thinking");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("accepts search_result block", () => {
|
|
53
|
-
const b: UnifiedBlock = { type: "search_result", query: "what is TypeScript" };
|
|
54
|
-
expect(b.type).toBe("search_result");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("accepts compact_boundary block", () => {
|
|
58
|
-
const b: UnifiedBlock = { type: "compact_boundary", trigger: "auto", pre_tokens: 10000, post_tokens: 5000 };
|
|
59
|
-
expect(b.type).toBe("compact_boundary");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("accepts compact_boundary block without post_tokens", () => {
|
|
63
|
-
const b: UnifiedBlock = { type: "compact_boundary", trigger: "manual", pre_tokens: 20000 };
|
|
64
|
-
expect(b.type).toBe("compact_boundary");
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// UnifiedStreamMessage
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
describe("UnifiedStreamMessage", () => {
|
|
73
|
-
it("accepts assistant message with blocks", () => {
|
|
74
|
-
const m: UnifiedStreamMessage = {
|
|
75
|
-
type: "assistant",
|
|
76
|
-
blocks: [
|
|
77
|
-
{ type: "thinking", thinking: "..." },
|
|
78
|
-
{ type: "text", text: "answer" },
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
expect(m.type).toBe("assistant");
|
|
82
|
-
expect(m.blocks).toHaveLength(2);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("accepts user message with tool_result blocks", () => {
|
|
86
|
-
const m: UnifiedStreamMessage = {
|
|
87
|
-
type: "user",
|
|
88
|
-
blocks: [{ type: "tool_result", tool_use_id: "abc", content: "ok" }],
|
|
89
|
-
};
|
|
90
|
-
expect(m.type).toBe("user");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("accepts system message", () => {
|
|
94
|
-
const m: UnifiedStreamMessage = {
|
|
95
|
-
type: "system",
|
|
96
|
-
blocks: [{ type: "compact_boundary", trigger: "auto", pre_tokens: 100 }],
|
|
97
|
-
};
|
|
98
|
-
expect(m.type).toBe("system");
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
// CreateSessionResult
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
|
|
106
|
-
describe("CreateSessionResult", () => {
|
|
107
|
-
it("accepts valid result", () => {
|
|
108
|
-
const r: CreateSessionResult = { sessionId: "uuid-12345" };
|
|
109
|
-
expect(r.sessionId).toBe("uuid-12345");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
// SessionInfo
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
|
|
117
|
-
describe("SessionInfo", () => {
|
|
118
|
-
it("accepts full info", () => {
|
|
119
|
-
const info: SessionInfo = {
|
|
120
|
-
sessionId: "abc",
|
|
121
|
-
cwd: "/home/user",
|
|
122
|
-
summary: "A session",
|
|
123
|
-
lastModified: 1234567890,
|
|
124
|
-
};
|
|
125
|
-
expect(info.sessionId).toBe("abc");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("accepts minimal info (only sessionId)", () => {
|
|
129
|
-
const info: SessionInfo = { sessionId: "minimal" };
|
|
130
|
-
expect(info.cwd).toBeUndefined();
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
// ToolAdapter interface 结构断言
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
describe("ToolAdapter interface structure", () => {
|
|
139
|
-
it("has correct property names via mock object", () => {
|
|
140
|
-
// 如果 ToolAdapter 接口的字段名或函数签名变更,该对象字面量将无法编译
|
|
141
|
-
const mock: ToolAdapter = {
|
|
142
|
-
displayName: "Test",
|
|
143
|
-
sessionDescPrefix: "Test Session:",
|
|
144
|
-
createSession: async () => ({ sessionId: "s" }),
|
|
145
|
-
prompt: async function* () {},
|
|
146
|
-
getSessionInfo: async () => undefined,
|
|
147
|
-
closeSession: async () => {},
|
|
148
|
-
};
|
|
149
|
-
expect(mock.displayName).toBe("Test");
|
|
150
|
-
expect(mock.sessionDescPrefix).toBe("Test Session:");
|
|
151
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type {
|
|
3
|
+
UnifiedBlock,
|
|
4
|
+
UnifiedStreamMessage,
|
|
5
|
+
UnifiedThinkingBlock,
|
|
6
|
+
UnifiedTextBlock,
|
|
7
|
+
UnifiedToolUseBlock,
|
|
8
|
+
UnifiedToolResultBlock,
|
|
9
|
+
UnifiedRedactedThinkingBlock,
|
|
10
|
+
UnifiedSearchResultBlock,
|
|
11
|
+
UnifiedCompactBoundaryBlock,
|
|
12
|
+
CreateSessionResult,
|
|
13
|
+
SessionInfo,
|
|
14
|
+
ToolAdapter,
|
|
15
|
+
} from "../adapters/adapter-interface.ts";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// 编译期类型验证:确保 UnifiedBlock 联合类型覆盖了所有预期的 block 形状
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
describe("UnifiedBlock union type coverage", () => {
|
|
22
|
+
it("accepts thinking block", () => {
|
|
23
|
+
const b: UnifiedBlock = { type: "thinking", thinking: "Let me think..." };
|
|
24
|
+
expect(b.type).toBe("thinking");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("accepts text block", () => {
|
|
28
|
+
const b: UnifiedBlock = { type: "text", text: "Hello" };
|
|
29
|
+
expect(b.type).toBe("text");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("accepts tool_use block", () => {
|
|
33
|
+
const b: UnifiedBlock = { type: "tool_use", name: "Read", input: { file_path: "/tmp" } };
|
|
34
|
+
expect(b.type).toBe("tool_use");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("accepts tool_result block with string content", () => {
|
|
38
|
+
const b: UnifiedBlock = { type: "tool_result", tool_use_id: "abc123", content: "result" };
|
|
39
|
+
expect(b.type).toBe("tool_result");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("accepts tool_result block with error flag", () => {
|
|
43
|
+
const b: UnifiedBlock = { type: "tool_result", tool_use_id: "abc123", content: "error", is_error: true };
|
|
44
|
+
expect(b.is_error).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("accepts redacted_thinking block", () => {
|
|
48
|
+
const b: UnifiedBlock = { type: "redacted_thinking" };
|
|
49
|
+
expect(b.type).toBe("redacted_thinking");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("accepts search_result block", () => {
|
|
53
|
+
const b: UnifiedBlock = { type: "search_result", query: "what is TypeScript" };
|
|
54
|
+
expect(b.type).toBe("search_result");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("accepts compact_boundary block", () => {
|
|
58
|
+
const b: UnifiedBlock = { type: "compact_boundary", trigger: "auto", pre_tokens: 10000, post_tokens: 5000 };
|
|
59
|
+
expect(b.type).toBe("compact_boundary");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("accepts compact_boundary block without post_tokens", () => {
|
|
63
|
+
const b: UnifiedBlock = { type: "compact_boundary", trigger: "manual", pre_tokens: 20000 };
|
|
64
|
+
expect(b.type).toBe("compact_boundary");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// UnifiedStreamMessage
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
describe("UnifiedStreamMessage", () => {
|
|
73
|
+
it("accepts assistant message with blocks", () => {
|
|
74
|
+
const m: UnifiedStreamMessage = {
|
|
75
|
+
type: "assistant",
|
|
76
|
+
blocks: [
|
|
77
|
+
{ type: "thinking", thinking: "..." },
|
|
78
|
+
{ type: "text", text: "answer" },
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
expect(m.type).toBe("assistant");
|
|
82
|
+
expect(m.blocks).toHaveLength(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("accepts user message with tool_result blocks", () => {
|
|
86
|
+
const m: UnifiedStreamMessage = {
|
|
87
|
+
type: "user",
|
|
88
|
+
blocks: [{ type: "tool_result", tool_use_id: "abc", content: "ok" }],
|
|
89
|
+
};
|
|
90
|
+
expect(m.type).toBe("user");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("accepts system message", () => {
|
|
94
|
+
const m: UnifiedStreamMessage = {
|
|
95
|
+
type: "system",
|
|
96
|
+
blocks: [{ type: "compact_boundary", trigger: "auto", pre_tokens: 100 }],
|
|
97
|
+
};
|
|
98
|
+
expect(m.type).toBe("system");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// CreateSessionResult
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe("CreateSessionResult", () => {
|
|
107
|
+
it("accepts valid result", () => {
|
|
108
|
+
const r: CreateSessionResult = { sessionId: "uuid-12345" };
|
|
109
|
+
expect(r.sessionId).toBe("uuid-12345");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// SessionInfo
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
describe("SessionInfo", () => {
|
|
118
|
+
it("accepts full info", () => {
|
|
119
|
+
const info: SessionInfo = {
|
|
120
|
+
sessionId: "abc",
|
|
121
|
+
cwd: "/home/user",
|
|
122
|
+
summary: "A session",
|
|
123
|
+
lastModified: 1234567890,
|
|
124
|
+
};
|
|
125
|
+
expect(info.sessionId).toBe("abc");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("accepts minimal info (only sessionId)", () => {
|
|
129
|
+
const info: SessionInfo = { sessionId: "minimal" };
|
|
130
|
+
expect(info.cwd).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// ToolAdapter interface 结构断言
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
describe("ToolAdapter interface structure", () => {
|
|
139
|
+
it("has correct property names via mock object", () => {
|
|
140
|
+
// 如果 ToolAdapter 接口的字段名或函数签名变更,该对象字面量将无法编译
|
|
141
|
+
const mock: ToolAdapter = {
|
|
142
|
+
displayName: "Test",
|
|
143
|
+
sessionDescPrefix: "Test Session:",
|
|
144
|
+
createSession: async () => ({ sessionId: "s" }),
|
|
145
|
+
prompt: async function* () {},
|
|
146
|
+
getSessionInfo: async () => undefined,
|
|
147
|
+
closeSession: async () => {},
|
|
148
|
+
};
|
|
149
|
+
expect(mock.displayName).toBe("Test");
|
|
150
|
+
expect(mock.sessionDescPrefix).toBe("Test Session:");
|
|
151
|
+
});
|
|
152
152
|
});
|