chatccc 0.2.171 → 0.2.172
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/package.json
CHANGED
|
@@ -169,6 +169,26 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
169
169
|
expect(platform.sendText).toHaveBeenCalledWith("wx-chat", "生成中...");
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
+
it("does not send the stopped success text until the running prompt really exits", async () => {
|
|
173
|
+
const platform = mockPlatform();
|
|
174
|
+
await recordSessionRegistry({
|
|
175
|
+
chatId: "wx-chat",
|
|
176
|
+
sessionId: "sid-wechat",
|
|
177
|
+
tool: "claude",
|
|
178
|
+
chatName: "busy-session",
|
|
179
|
+
running: true,
|
|
180
|
+
});
|
|
181
|
+
activePrompts.set("sid-wechat", {
|
|
182
|
+
controller: new AbortController(),
|
|
183
|
+
stopped: false,
|
|
184
|
+
startTime: Date.now(),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await handleCommand(platform, "/stop", "wx-chat", "wx-user", Date.now(), "p2p");
|
|
188
|
+
|
|
189
|
+
expect(platform.sendText).not.toHaveBeenCalledWith("wx-chat", "会话已停止。");
|
|
190
|
+
});
|
|
191
|
+
|
|
172
192
|
it("cleans stale Feishu p2p binding, creates a group, and sends the private message as first prompt", async () => {
|
|
173
193
|
const platform = mockPlatform("feishu");
|
|
174
194
|
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
@@ -177,7 +197,7 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
177
197
|
blocks: [{ type: "text" as const, text: `收到: ${userText}` }],
|
|
178
198
|
};
|
|
179
199
|
});
|
|
180
|
-
_setAdapterForToolForTest("claude", {
|
|
200
|
+
_setAdapterForToolForTest("claude", {
|
|
181
201
|
displayName: "Claude",
|
|
182
202
|
sessionDescPrefix: "Claude Session:",
|
|
183
203
|
createSession: vi.fn(async () => ({ sessionId: "sid-feishu-new" })),
|
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
recordChatPlatform,
|
|
89
89
|
_getPlatformForChatForTest,
|
|
90
90
|
runAgentSession,
|
|
91
|
+
stopSession,
|
|
91
92
|
startUnifiedDisplayLoop,
|
|
92
93
|
stopUnifiedDisplayLoop,
|
|
93
94
|
_setProcessAliveForTest,
|
|
@@ -396,6 +397,66 @@ describe("runAgentSession process monitor", () => {
|
|
|
396
397
|
expect.stringContaining("进程异常结束"),
|
|
397
398
|
);
|
|
398
399
|
});
|
|
400
|
+
|
|
401
|
+
it("sends the stopped notice only after the prompt generator exits", async () => {
|
|
402
|
+
const platform = mockPlatform("feishu");
|
|
403
|
+
setSessionPlatform(platform);
|
|
404
|
+
bindChatToSession("sid-stop-notice", "chat-stop-notice");
|
|
405
|
+
recordLastActiveChat("sid-stop-notice", "chat-stop-notice");
|
|
406
|
+
|
|
407
|
+
const closeSession = vi.fn();
|
|
408
|
+
let releasePrompt: (() => void) | undefined;
|
|
409
|
+
const adapter: ToolAdapter = {
|
|
410
|
+
displayName: "Claude Code",
|
|
411
|
+
sessionDescPrefix: "Claude Code Session:",
|
|
412
|
+
createSession: async () => ({ sessionId: "sid-stop-notice" }),
|
|
413
|
+
getSessionInfo: async (sid) => ({ sessionId: sid, cwd: "F:\\repo" }),
|
|
414
|
+
closeSession: async () => {},
|
|
415
|
+
prompt: async function* (
|
|
416
|
+
_sid: string,
|
|
417
|
+
_text: string,
|
|
418
|
+
_cwd: string,
|
|
419
|
+
signal?: AbortSignal,
|
|
420
|
+
options?: ToolPromptOptions,
|
|
421
|
+
) {
|
|
422
|
+
const waitForStop = new Promise<void>((resolve) => {
|
|
423
|
+
releasePrompt = resolve;
|
|
424
|
+
signal?.addEventListener("abort", () => resolve(), { once: true });
|
|
425
|
+
});
|
|
426
|
+
options?.onSessionCreated?.(() => {
|
|
427
|
+
closeSession();
|
|
428
|
+
releasePrompt?.();
|
|
429
|
+
});
|
|
430
|
+
yield { type: "assistant", blocks: [{ type: "text", text: "partial answer" }] };
|
|
431
|
+
await waitForStop;
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
_setAdapterForToolForTest("claude", adapter);
|
|
435
|
+
|
|
436
|
+
const runPromise = runAgentSession(
|
|
437
|
+
"sid-stop-notice",
|
|
438
|
+
"prompt",
|
|
439
|
+
platform,
|
|
440
|
+
"chat-stop-notice",
|
|
441
|
+
Date.now(),
|
|
442
|
+
"claude",
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
await vi.waitFor(() => {
|
|
446
|
+
expect(activePrompts.get("sid-stop-notice")?.closeSession).toBeTypeOf("function");
|
|
447
|
+
});
|
|
448
|
+
expect(platform.sendText).not.toHaveBeenCalledWith("chat-stop-notice", "会话已停止。");
|
|
449
|
+
|
|
450
|
+
const ok = stopSession("sid-stop-notice");
|
|
451
|
+
expect(ok).toBe(true);
|
|
452
|
+
expect(closeSession).toHaveBeenCalledTimes(1);
|
|
453
|
+
|
|
454
|
+
await runPromise;
|
|
455
|
+
|
|
456
|
+
expect(platform.sendText).toHaveBeenCalledWith("chat-stop-notice", "会话已停止。");
|
|
457
|
+
expect(activePrompts.has("sid-stop-notice")).toBe(false);
|
|
458
|
+
});
|
|
459
|
+
|
|
399
460
|
it("re-injects IM skill capabilities for each resumed Claude prompt", async () => {
|
|
400
461
|
const platform = mockPlatform("feishu");
|
|
401
462
|
setSessionPlatform(platform);
|
|
@@ -41,9 +41,13 @@ vi.mock("../stream-state.ts", () => ({
|
|
|
41
41
|
import { stopSession } from "../session.ts";
|
|
42
42
|
import { activePrompts } from "../session-chat-binding.ts";
|
|
43
43
|
|
|
44
|
-
function seedRunningSession(
|
|
44
|
+
function seedRunningSession(
|
|
45
|
+
sid: string,
|
|
46
|
+
accumulated = "partial output",
|
|
47
|
+
closeSession?: () => void,
|
|
48
|
+
): AbortController {
|
|
45
49
|
const controller = new AbortController();
|
|
46
|
-
activePrompts.set(sid, { controller, stopped: false, startTime: Date.now() });
|
|
50
|
+
activePrompts.set(sid, { controller, stopped: false, startTime: Date.now(), closeSession });
|
|
47
51
|
stateStore.set(sid, {
|
|
48
52
|
sessionId: sid,
|
|
49
53
|
status: "running",
|
|
@@ -123,4 +127,14 @@ describe("stopSession 行为护栏", () => {
|
|
|
123
127
|
stopSession("sid-C");
|
|
124
128
|
expect(activePrompts.get("sid-C")?.stopped).toBe(true);
|
|
125
129
|
});
|
|
130
|
+
|
|
131
|
+
it("调用 adapter 提供的 closeSession 以主动关闭底层 SDK session", () => {
|
|
132
|
+
const closeSession = vi.fn();
|
|
133
|
+
seedRunningSession("sid-D", "partial output", closeSession);
|
|
134
|
+
|
|
135
|
+
const ok = stopSession("sid-D");
|
|
136
|
+
|
|
137
|
+
expect(ok).toBe(true);
|
|
138
|
+
expect(closeSession).toHaveBeenCalledTimes(1);
|
|
139
|
+
});
|
|
126
140
|
});
|
package/src/orchestrator.ts
CHANGED
|
@@ -778,8 +778,7 @@ export async function handleCommand(
|
|
|
778
778
|
logTrace(tid, "BRANCH", { cmd: "/stop" });
|
|
779
779
|
if (stopSession(sessionId)) {
|
|
780
780
|
console.log(`[${ts()}] [STOP] User sent /stop, session=${sessionId}`);
|
|
781
|
-
|
|
782
|
-
logTrace(tid, "DONE", { outcome: "stopped" });
|
|
781
|
+
logTrace(tid, "DONE", { outcome: "stop_requested" });
|
|
783
782
|
} else {
|
|
784
783
|
await platform
|
|
785
784
|
.sendText(chatId, "当前没有正在进行的会话。")
|
package/src/session.ts
CHANGED
|
@@ -1247,7 +1247,10 @@ export async function runAgentSession(
|
|
|
1247
1247
|
});
|
|
1248
1248
|
}
|
|
1249
1249
|
const active1 = getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
|
|
1250
|
-
if (active1)
|
|
1250
|
+
if (active1) {
|
|
1251
|
+
await platform.sendText(active1, "会话已停止。").catch(() => {});
|
|
1252
|
+
platform.setChatAvatar(active1, tool, "idle").catch(() => {});
|
|
1253
|
+
}
|
|
1251
1254
|
console.log(`[${ts()}] Session ${sessionId} stopped (content chunks: ${state.chunkCount})`);
|
|
1252
1255
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "stopped", chunks: state.chunkCount });
|
|
1253
1256
|
} else if (wasAbnormalExit) {
|
|
@@ -1623,6 +1626,11 @@ export function stopSession(sessionId: string): boolean {
|
|
|
1623
1626
|
prompt.stopped = true;
|
|
1624
1627
|
clearPromptProcessMonitor(sessionId);
|
|
1625
1628
|
cancelQueuedMessage(sessionId);
|
|
1629
|
+
try {
|
|
1630
|
+
prompt.closeSession?.();
|
|
1631
|
+
} catch (err) {
|
|
1632
|
+
console.warn(`[${ts()}] [STOP] closeSession failed for ${sessionId}: ${(err as Error).message}`);
|
|
1633
|
+
}
|
|
1626
1634
|
prompt.controller.abort();
|
|
1627
1635
|
|
|
1628
1636
|
// 强制杀死 CLI 子进程。controller.abort() 只在 for-await 收到下一条
|