chatccc 0.2.199 → 0.2.200
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 +1 -1
- 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/config.sample.json +27 -27
- 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-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 +277 -277
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +33 -33
- package/src/__tests__/builtin-context.test.ts +163 -163
- package/src/__tests__/builtin-file-tools.test.ts +224 -224
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/card-plain-text.test.ts +5 -5
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/cards.test.ts +109 -109
- package/src/__tests__/ccc-adapter.test.ts +114 -114
- 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 +592 -592
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
- package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/config-reload.test.ts +10 -10
- package/src/__tests__/config-sample.test.ts +18 -18
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +87 -45
- 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__/orchestrator.test.ts +227 -202
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +40 -40
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/sim-platform.test.ts +12 -12
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/web-ui.test.ts +209 -209
- package/src/adapters/ccc-adapter.ts +121 -121
- package/src/adapters/claude-adapter.ts +603 -603
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +380 -380
- 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-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +81 -81
- package/src/agent-reload-config-rpc.ts +34 -34
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +473 -473
- package/src/builtin/context.ts +323 -323
- package/src/builtin/file-tools.ts +1072 -1072
- package/src/builtin/index.ts +404 -404
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/cards.ts +195 -195
- 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/config.ts +125 -125
- package/src/feishu-api.ts +86 -63
- 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 +183 -183
- package/src/privacy.ts +118 -118
- package/src/runtime-reload.ts +34 -34
- package/src/session-chat-binding.ts +6 -6
- package/src/session-name.ts +8 -8
- package/src/session.ts +141 -141
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/turn-cards.ts +117 -117
- package/src/web-ui.ts +205 -205
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
import { Readable } from "node:stream";
|
|
2
|
-
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
3
|
-
|
|
4
|
-
import type { PlatformAdapter } from "../platform-adapter.ts";
|
|
5
|
-
|
|
6
|
-
const mockDelegateAgentTask = vi.hoisted(() => vi.fn());
|
|
7
|
-
|
|
8
|
-
vi.mock("../agent-delegate-task.ts", () => ({
|
|
9
|
-
delegateAgentTask: mockDelegateAgentTask,
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
AGENT_DELEGATE_TASK_PATH,
|
|
14
|
-
buildAgentDelegateTaskCapabilityPrompt,
|
|
15
|
-
handleAgentDelegateTaskRequest,
|
|
16
|
-
} from "../agent-delegate-task-rpc.ts";
|
|
17
|
-
import { ABD_APPEND_PROMPT } from "../shared-prefix.ts";
|
|
18
|
-
|
|
19
|
-
function request(body: unknown, path = AGENT_DELEGATE_TASK_PATH, method = "POST"): Readable & {
|
|
20
|
-
url?: string;
|
|
21
|
-
method?: string;
|
|
22
|
-
headers: Record<string, string>;
|
|
23
|
-
} {
|
|
24
|
-
const req = Readable.from([Buffer.from(JSON.stringify(body), "utf8")]) as Readable & {
|
|
25
|
-
url?: string;
|
|
26
|
-
method?: string;
|
|
27
|
-
headers: Record<string, string>;
|
|
28
|
-
};
|
|
29
|
-
req.url = path;
|
|
30
|
-
req.method = method;
|
|
31
|
-
req.headers = { "content-type": "application/json; charset=utf-8" };
|
|
32
|
-
return req;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function response() {
|
|
36
|
-
const res = {
|
|
37
|
-
statusCode: 0,
|
|
38
|
-
headers: {} as Record<string, string>,
|
|
39
|
-
body: "",
|
|
40
|
-
writeHead(status: number, headers: Record<string, string>) {
|
|
41
|
-
this.statusCode = status;
|
|
42
|
-
this.headers = headers;
|
|
43
|
-
return this;
|
|
44
|
-
},
|
|
45
|
-
end(chunk?: string) {
|
|
46
|
-
this.body += chunk ?? "";
|
|
47
|
-
return this;
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
return res;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function platform(kind: "feishu" | "wechat" = "feishu"): PlatformAdapter {
|
|
54
|
-
return {
|
|
55
|
-
kind,
|
|
56
|
-
sendText: vi.fn(async () => true),
|
|
57
|
-
sendCard: vi.fn(async () => true),
|
|
58
|
-
sendRawCard: vi.fn(async () => true),
|
|
59
|
-
createGroup: vi.fn(async () => "chat-id"),
|
|
60
|
-
updateChatInfo: vi.fn(async () => {}),
|
|
61
|
-
getChatInfo: vi.fn(async () => ({ name: "chat", description: "" })),
|
|
62
|
-
disbandChat: vi.fn(async () => {}),
|
|
63
|
-
setChatAvatar: vi.fn(async () => {}),
|
|
64
|
-
extractSessionInfo: vi.fn(() => null),
|
|
65
|
-
cardCreate: vi.fn(async () => "card-id"),
|
|
66
|
-
cardSend: vi.fn(async () => "message-id"),
|
|
67
|
-
cardUpdate: vi.fn(async () => {}),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
describe("agent delegate task RPC", () => {
|
|
72
|
-
beforeEach(() => {
|
|
73
|
-
mockDelegateAgentTask.mockReset();
|
|
74
|
-
mockDelegateAgentTask.mockResolvedValue({
|
|
75
|
-
chatId: "chat-new",
|
|
76
|
-
sessionId: "sid-new",
|
|
77
|
-
tool: "codex",
|
|
78
|
-
cwd: "F:\\repo",
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("delegates a task through the local API", async () => {
|
|
83
|
-
const req = request({
|
|
84
|
-
tool: "codex",
|
|
85
|
-
cwd: "F:\\repo",
|
|
86
|
-
open_id: "ou-user",
|
|
87
|
-
prompt: "帮我分析日志",
|
|
88
|
-
});
|
|
89
|
-
const res = response();
|
|
90
|
-
|
|
91
|
-
await handleAgentDelegateTaskRequest(req as never, res as never, platform());
|
|
92
|
-
|
|
93
|
-
expect(res.statusCode).toBe(200);
|
|
94
|
-
expect(JSON.parse(res.body)).toMatchObject({
|
|
95
|
-
ok: true,
|
|
96
|
-
chat_id: "chat-new",
|
|
97
|
-
session_id: "sid-new",
|
|
98
|
-
tool: "codex",
|
|
99
|
-
});
|
|
100
|
-
expect(mockDelegateAgentTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
101
|
-
tool: "codex",
|
|
102
|
-
cwd: expect.stringContaining("repo"),
|
|
103
|
-
promptText: "帮我分析日志",
|
|
104
|
-
openIds: ["ou-user"],
|
|
105
|
-
chatNamePrefix: "帮我分析日志",
|
|
106
|
-
}));
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("applies the shared ABD prefix to API prompts", async () => {
|
|
110
|
-
const req = request({
|
|
111
|
-
tool: "claude",
|
|
112
|
-
cwd: "F:\\repo",
|
|
113
|
-
openIds: ["ou-user"],
|
|
114
|
-
prompt: "/abd帮我分析",
|
|
115
|
-
});
|
|
116
|
-
const res = response();
|
|
117
|
-
|
|
118
|
-
await handleAgentDelegateTaskRequest(req as never, res as never, platform());
|
|
119
|
-
|
|
120
|
-
expect(res.statusCode).toBe(200);
|
|
121
|
-
expect(mockDelegateAgentTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
122
|
-
promptText: `帮我分析\n\n---\n${ABD_APPEND_PROMPT}`,
|
|
123
|
-
chatNamePrefix: "帮我分析",
|
|
124
|
-
}));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("rejects non-Feishu platforms", async () => {
|
|
128
|
-
const req = request({
|
|
129
|
-
tool: "codex",
|
|
130
|
-
cwd: "F:\\repo",
|
|
131
|
-
open_id: "ou-user",
|
|
132
|
-
prompt: "task",
|
|
133
|
-
});
|
|
134
|
-
const res = response();
|
|
135
|
-
|
|
136
|
-
await handleAgentDelegateTaskRequest(req as never, res as never, platform("wechat"));
|
|
137
|
-
|
|
138
|
-
expect(res.statusCode).toBe(409);
|
|
139
|
-
expect(mockDelegateAgentTask).not.toHaveBeenCalled();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("rejects missing required parameters", async () => {
|
|
143
|
-
const req = request({ tool: "codex", cwd: "F:\\repo", prompt: "task" });
|
|
144
|
-
const res = response();
|
|
145
|
-
|
|
146
|
-
await handleAgentDelegateTaskRequest(req as never, res as never, platform());
|
|
147
|
-
|
|
148
|
-
expect(res.statusCode).toBe(400);
|
|
149
|
-
expect(JSON.parse(res.body).error).toContain("open_id");
|
|
150
|
-
expect(mockDelegateAgentTask).not.toHaveBeenCalled();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("builds prompt instructions for the delegate endpoint", () => {
|
|
154
|
-
const prompt = buildAgentDelegateTaskCapabilityPrompt({
|
|
155
|
-
url: `http://127.0.0.1:18080${AGENT_DELEGATE_TASK_PATH}`,
|
|
156
|
-
cwd: "F:/repo",
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/delegate-task");
|
|
160
|
-
expect(prompt).toContain('"tool":"codex|claude|cursor"');
|
|
161
|
-
expect(prompt).toContain('"cwd":"absolute working directory"');
|
|
162
|
-
expect(prompt).toContain("project prompt injection and IM skills still apply");
|
|
163
|
-
expect(prompt).toContain("Current working directory: F:/repo");
|
|
164
|
-
});
|
|
165
|
-
});
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
3
|
+
|
|
4
|
+
import type { PlatformAdapter } from "../platform-adapter.ts";
|
|
5
|
+
|
|
6
|
+
const mockDelegateAgentTask = vi.hoisted(() => vi.fn());
|
|
7
|
+
|
|
8
|
+
vi.mock("../agent-delegate-task.ts", () => ({
|
|
9
|
+
delegateAgentTask: mockDelegateAgentTask,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
AGENT_DELEGATE_TASK_PATH,
|
|
14
|
+
buildAgentDelegateTaskCapabilityPrompt,
|
|
15
|
+
handleAgentDelegateTaskRequest,
|
|
16
|
+
} from "../agent-delegate-task-rpc.ts";
|
|
17
|
+
import { ABD_APPEND_PROMPT } from "../shared-prefix.ts";
|
|
18
|
+
|
|
19
|
+
function request(body: unknown, path = AGENT_DELEGATE_TASK_PATH, method = "POST"): Readable & {
|
|
20
|
+
url?: string;
|
|
21
|
+
method?: string;
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
} {
|
|
24
|
+
const req = Readable.from([Buffer.from(JSON.stringify(body), "utf8")]) as Readable & {
|
|
25
|
+
url?: string;
|
|
26
|
+
method?: string;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
};
|
|
29
|
+
req.url = path;
|
|
30
|
+
req.method = method;
|
|
31
|
+
req.headers = { "content-type": "application/json; charset=utf-8" };
|
|
32
|
+
return req;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function response() {
|
|
36
|
+
const res = {
|
|
37
|
+
statusCode: 0,
|
|
38
|
+
headers: {} as Record<string, string>,
|
|
39
|
+
body: "",
|
|
40
|
+
writeHead(status: number, headers: Record<string, string>) {
|
|
41
|
+
this.statusCode = status;
|
|
42
|
+
this.headers = headers;
|
|
43
|
+
return this;
|
|
44
|
+
},
|
|
45
|
+
end(chunk?: string) {
|
|
46
|
+
this.body += chunk ?? "";
|
|
47
|
+
return this;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return res;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function platform(kind: "feishu" | "wechat" = "feishu"): PlatformAdapter {
|
|
54
|
+
return {
|
|
55
|
+
kind,
|
|
56
|
+
sendText: vi.fn(async () => true),
|
|
57
|
+
sendCard: vi.fn(async () => true),
|
|
58
|
+
sendRawCard: vi.fn(async () => true),
|
|
59
|
+
createGroup: vi.fn(async () => "chat-id"),
|
|
60
|
+
updateChatInfo: vi.fn(async () => {}),
|
|
61
|
+
getChatInfo: vi.fn(async () => ({ name: "chat", description: "" })),
|
|
62
|
+
disbandChat: vi.fn(async () => {}),
|
|
63
|
+
setChatAvatar: vi.fn(async () => {}),
|
|
64
|
+
extractSessionInfo: vi.fn(() => null),
|
|
65
|
+
cardCreate: vi.fn(async () => "card-id"),
|
|
66
|
+
cardSend: vi.fn(async () => "message-id"),
|
|
67
|
+
cardUpdate: vi.fn(async () => {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("agent delegate task RPC", () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
mockDelegateAgentTask.mockReset();
|
|
74
|
+
mockDelegateAgentTask.mockResolvedValue({
|
|
75
|
+
chatId: "chat-new",
|
|
76
|
+
sessionId: "sid-new",
|
|
77
|
+
tool: "codex",
|
|
78
|
+
cwd: "F:\\repo",
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("delegates a task through the local API", async () => {
|
|
83
|
+
const req = request({
|
|
84
|
+
tool: "codex",
|
|
85
|
+
cwd: "F:\\repo",
|
|
86
|
+
open_id: "ou-user",
|
|
87
|
+
prompt: "帮我分析日志",
|
|
88
|
+
});
|
|
89
|
+
const res = response();
|
|
90
|
+
|
|
91
|
+
await handleAgentDelegateTaskRequest(req as never, res as never, platform());
|
|
92
|
+
|
|
93
|
+
expect(res.statusCode).toBe(200);
|
|
94
|
+
expect(JSON.parse(res.body)).toMatchObject({
|
|
95
|
+
ok: true,
|
|
96
|
+
chat_id: "chat-new",
|
|
97
|
+
session_id: "sid-new",
|
|
98
|
+
tool: "codex",
|
|
99
|
+
});
|
|
100
|
+
expect(mockDelegateAgentTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
101
|
+
tool: "codex",
|
|
102
|
+
cwd: expect.stringContaining("repo"),
|
|
103
|
+
promptText: "帮我分析日志",
|
|
104
|
+
openIds: ["ou-user"],
|
|
105
|
+
chatNamePrefix: "帮我分析日志",
|
|
106
|
+
}));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("applies the shared ABD prefix to API prompts", async () => {
|
|
110
|
+
const req = request({
|
|
111
|
+
tool: "claude",
|
|
112
|
+
cwd: "F:\\repo",
|
|
113
|
+
openIds: ["ou-user"],
|
|
114
|
+
prompt: "/abd帮我分析",
|
|
115
|
+
});
|
|
116
|
+
const res = response();
|
|
117
|
+
|
|
118
|
+
await handleAgentDelegateTaskRequest(req as never, res as never, platform());
|
|
119
|
+
|
|
120
|
+
expect(res.statusCode).toBe(200);
|
|
121
|
+
expect(mockDelegateAgentTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
122
|
+
promptText: `帮我分析\n\n---\n${ABD_APPEND_PROMPT}`,
|
|
123
|
+
chatNamePrefix: "帮我分析",
|
|
124
|
+
}));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("rejects non-Feishu platforms", async () => {
|
|
128
|
+
const req = request({
|
|
129
|
+
tool: "codex",
|
|
130
|
+
cwd: "F:\\repo",
|
|
131
|
+
open_id: "ou-user",
|
|
132
|
+
prompt: "task",
|
|
133
|
+
});
|
|
134
|
+
const res = response();
|
|
135
|
+
|
|
136
|
+
await handleAgentDelegateTaskRequest(req as never, res as never, platform("wechat"));
|
|
137
|
+
|
|
138
|
+
expect(res.statusCode).toBe(409);
|
|
139
|
+
expect(mockDelegateAgentTask).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("rejects missing required parameters", async () => {
|
|
143
|
+
const req = request({ tool: "codex", cwd: "F:\\repo", prompt: "task" });
|
|
144
|
+
const res = response();
|
|
145
|
+
|
|
146
|
+
await handleAgentDelegateTaskRequest(req as never, res as never, platform());
|
|
147
|
+
|
|
148
|
+
expect(res.statusCode).toBe(400);
|
|
149
|
+
expect(JSON.parse(res.body).error).toContain("open_id");
|
|
150
|
+
expect(mockDelegateAgentTask).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("builds prompt instructions for the delegate endpoint", () => {
|
|
154
|
+
const prompt = buildAgentDelegateTaskCapabilityPrompt({
|
|
155
|
+
url: `http://127.0.0.1:18080${AGENT_DELEGATE_TASK_PATH}`,
|
|
156
|
+
cwd: "F:/repo",
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/delegate-task");
|
|
160
|
+
expect(prompt).toContain('"tool":"codex|claude|cursor"');
|
|
161
|
+
expect(prompt).toContain('"cwd":"absolute working directory"');
|
|
162
|
+
expect(prompt).toContain("project prompt injection and IM skills still apply");
|
|
163
|
+
expect(prompt).toContain("Current working directory: F:/repo");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
import { Readable } from "node:stream";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
AGENT_RELOAD_CONFIG_PATH,
|
|
6
|
-
handleAgentReloadConfigRequest,
|
|
7
|
-
} from "../agent-reload-config-rpc.ts";
|
|
8
|
-
|
|
9
|
-
function request(path = AGENT_RELOAD_CONFIG_PATH, method = "POST"): Readable & {
|
|
10
|
-
url?: string;
|
|
11
|
-
method?: string;
|
|
12
|
-
headers: Record<string, string>;
|
|
13
|
-
} {
|
|
14
|
-
const req = Readable.from([]) as Readable & {
|
|
15
|
-
url?: string;
|
|
16
|
-
method?: string;
|
|
17
|
-
headers: Record<string, string>;
|
|
18
|
-
};
|
|
19
|
-
req.url = path;
|
|
20
|
-
req.method = method;
|
|
21
|
-
req.headers = {};
|
|
22
|
-
return req;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function response() {
|
|
26
|
-
const res = {
|
|
27
|
-
statusCode: 0,
|
|
28
|
-
headers: {} as Record<string, string>,
|
|
29
|
-
body: "",
|
|
30
|
-
writeHead(status: number, headers: Record<string, string>) {
|
|
31
|
-
this.statusCode = status;
|
|
32
|
-
this.headers = headers;
|
|
33
|
-
return this;
|
|
34
|
-
},
|
|
35
|
-
end(chunk?: string) {
|
|
36
|
-
this.body += chunk ?? "";
|
|
37
|
-
return this;
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
return res;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe("agent reload config RPC", () => {
|
|
44
|
-
it("reloads runtime config for POST requests", async () => {
|
|
45
|
-
const reload = vi.fn(async () => ({
|
|
46
|
-
configPath: "C:\\Users\\me\\.chatccc\\config.json",
|
|
47
|
-
defaultAgent: "codex" as const,
|
|
48
|
-
reloadedAt: "2026-07-02T05:00:00.000Z",
|
|
49
|
-
}));
|
|
50
|
-
const res = response();
|
|
51
|
-
|
|
52
|
-
await expect(handleAgentReloadConfigRequest(request() as never, res as never, reload)).resolves.toBe(true);
|
|
53
|
-
|
|
54
|
-
expect(reload).toHaveBeenCalledWith("agent-reload-api");
|
|
55
|
-
expect(res.statusCode).toBe(200);
|
|
56
|
-
expect(JSON.parse(res.body)).toEqual({
|
|
57
|
-
ok: true,
|
|
58
|
-
configPath: "C:\\Users\\me\\.chatccc\\config.json",
|
|
59
|
-
defaultAgent: "codex",
|
|
60
|
-
reloadedAt: "2026-07-02T05:00:00.000Z",
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("rejects non-POST requests without reloading", async () => {
|
|
65
|
-
const reload = vi.fn();
|
|
66
|
-
const res = response();
|
|
67
|
-
|
|
68
|
-
await expect(handleAgentReloadConfigRequest(request(AGENT_RELOAD_CONFIG_PATH, "GET") as never, res as never, reload)).resolves.toBe(true);
|
|
69
|
-
|
|
70
|
-
expect(reload).not.toHaveBeenCalled();
|
|
71
|
-
expect(res.statusCode).toBe(405);
|
|
72
|
-
expect(JSON.parse(res.body)).toMatchObject({ ok: false, error: "Method not allowed" });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("does not handle other paths", async () => {
|
|
76
|
-
const res = response();
|
|
77
|
-
|
|
78
|
-
await expect(handleAgentReloadConfigRequest(request("/api/other") as never, res as never, vi.fn())).resolves.toBe(false);
|
|
79
|
-
|
|
80
|
-
expect(res.body).toBe("");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("returns a visible error when reload fails", async () => {
|
|
84
|
-
const res = response();
|
|
85
|
-
|
|
86
|
-
await expect(
|
|
87
|
-
handleAgentReloadConfigRequest(
|
|
88
|
-
request() as never,
|
|
89
|
-
res as never,
|
|
90
|
-
vi.fn(async () => {
|
|
91
|
-
throw new Error("bad config");
|
|
92
|
-
}),
|
|
93
|
-
),
|
|
94
|
-
).resolves.toBe(true);
|
|
95
|
-
|
|
96
|
-
expect(res.statusCode).toBe(500);
|
|
97
|
-
expect(JSON.parse(res.body)).toEqual({ ok: false, error: "bad config" });
|
|
98
|
-
});
|
|
99
|
-
});
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AGENT_RELOAD_CONFIG_PATH,
|
|
6
|
+
handleAgentReloadConfigRequest,
|
|
7
|
+
} from "../agent-reload-config-rpc.ts";
|
|
8
|
+
|
|
9
|
+
function request(path = AGENT_RELOAD_CONFIG_PATH, method = "POST"): Readable & {
|
|
10
|
+
url?: string;
|
|
11
|
+
method?: string;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
} {
|
|
14
|
+
const req = Readable.from([]) as Readable & {
|
|
15
|
+
url?: string;
|
|
16
|
+
method?: string;
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
req.url = path;
|
|
20
|
+
req.method = method;
|
|
21
|
+
req.headers = {};
|
|
22
|
+
return req;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function response() {
|
|
26
|
+
const res = {
|
|
27
|
+
statusCode: 0,
|
|
28
|
+
headers: {} as Record<string, string>,
|
|
29
|
+
body: "",
|
|
30
|
+
writeHead(status: number, headers: Record<string, string>) {
|
|
31
|
+
this.statusCode = status;
|
|
32
|
+
this.headers = headers;
|
|
33
|
+
return this;
|
|
34
|
+
},
|
|
35
|
+
end(chunk?: string) {
|
|
36
|
+
this.body += chunk ?? "";
|
|
37
|
+
return this;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("agent reload config RPC", () => {
|
|
44
|
+
it("reloads runtime config for POST requests", async () => {
|
|
45
|
+
const reload = vi.fn(async () => ({
|
|
46
|
+
configPath: "C:\\Users\\me\\.chatccc\\config.json",
|
|
47
|
+
defaultAgent: "codex" as const,
|
|
48
|
+
reloadedAt: "2026-07-02T05:00:00.000Z",
|
|
49
|
+
}));
|
|
50
|
+
const res = response();
|
|
51
|
+
|
|
52
|
+
await expect(handleAgentReloadConfigRequest(request() as never, res as never, reload)).resolves.toBe(true);
|
|
53
|
+
|
|
54
|
+
expect(reload).toHaveBeenCalledWith("agent-reload-api");
|
|
55
|
+
expect(res.statusCode).toBe(200);
|
|
56
|
+
expect(JSON.parse(res.body)).toEqual({
|
|
57
|
+
ok: true,
|
|
58
|
+
configPath: "C:\\Users\\me\\.chatccc\\config.json",
|
|
59
|
+
defaultAgent: "codex",
|
|
60
|
+
reloadedAt: "2026-07-02T05:00:00.000Z",
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("rejects non-POST requests without reloading", async () => {
|
|
65
|
+
const reload = vi.fn();
|
|
66
|
+
const res = response();
|
|
67
|
+
|
|
68
|
+
await expect(handleAgentReloadConfigRequest(request(AGENT_RELOAD_CONFIG_PATH, "GET") as never, res as never, reload)).resolves.toBe(true);
|
|
69
|
+
|
|
70
|
+
expect(reload).not.toHaveBeenCalled();
|
|
71
|
+
expect(res.statusCode).toBe(405);
|
|
72
|
+
expect(JSON.parse(res.body)).toMatchObject({ ok: false, error: "Method not allowed" });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("does not handle other paths", async () => {
|
|
76
|
+
const res = response();
|
|
77
|
+
|
|
78
|
+
await expect(handleAgentReloadConfigRequest(request("/api/other") as never, res as never, vi.fn())).resolves.toBe(false);
|
|
79
|
+
|
|
80
|
+
expect(res.body).toBe("");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns a visible error when reload fails", async () => {
|
|
84
|
+
const res = response();
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
handleAgentReloadConfigRequest(
|
|
88
|
+
request() as never,
|
|
89
|
+
res as never,
|
|
90
|
+
vi.fn(async () => {
|
|
91
|
+
throw new Error("bad config");
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
).resolves.toBe(true);
|
|
95
|
+
|
|
96
|
+
expect(res.statusCode).toBe(500);
|
|
97
|
+
expect(JSON.parse(res.body)).toEqual({ ok: false, error: "bad config" });
|
|
98
|
+
});
|
|
99
|
+
});
|