





















@@ -27,31 +27,30 @@ import {
27272828const { createSessionStoreDir, seedActiveMainSession } = setupGatewaySessionsTestHarness();
292930+type ResetAcpState = {
31+backend?: string;
32+agent?: string;
33+runtimeSessionName?: string;
34+identity?: {
35+state?: string;
36+acpxRecordId?: string;
37+acpxSessionId?: string;
38+};
39+mode?: string;
40+runtimeOptions?: {
41+runtimeMode?: string;
42+timeoutSeconds?: number;
43+};
44+cwd?: string;
45+state?: string;
46+};
47+type ConfigFilePatch = Parameters<(typeof import("../config/config.js"))["writeConfigFile"]>[0];
48+3049afterEach(() => {
3150closeOpenClawStateDatabaseForTest();
3251});
335234-function expectResetAcpState(
35-acp:
36-| {
37-backend?: string;
38-agent?: string;
39-runtimeSessionName?: string;
40-identity?: {
41-state?: string;
42-acpxRecordId?: string;
43-acpxSessionId?: string;
44-};
45-mode?: string;
46-runtimeOptions?: {
47-runtimeMode?: string;
48-timeoutSeconds?: number;
49-};
50-cwd?: string;
51-state?: string;
52-}
53-| undefined,
54-) {
53+function expectResetAcpState(acp: ResetAcpState | undefined) {
5554expect(acp?.backend).toBe("acpx");
5655expect(acp?.agent).toBe("codex");
5756expect(acp?.runtimeSessionName).toBe("runtime:reset");
@@ -65,25 +64,86 @@ function expectResetAcpState(
6564expect(acp?.state).toBe("idle");
6665}
676668-test("sessions.reset aborts active runs and clears queues", async () => {
67+async function seedWaitingActiveMainSession() {
6968await seedActiveMainSession();
70-enqueueSystemEvent("stale event via alias", { sessionKey: "main" });
71-enqueueSystemEvent("stale event via canonical key", { sessionKey: "agent:main:main" });
72-enqueueSystemEvent("stale event via session id", { sessionKey: "sess-main" });
73-const waitCallCountAtSnapshotClear: number[] = [];
74-bootstrapCacheMocks.clearBootstrapSnapshot.mockImplementation(() => {
75-waitCallCountAtSnapshotClear.push(embeddedRunMock.waitCalls.length);
76-});
77-7869embeddedRunMock.activeIds.add("sess-main");
7970embeddedRunMock.waitResults.set("sess-main", true);
71+}
807281-const reset = await directSessionReq<{ ok: true; key: string; entry: { sessionId: string } }>(
73+async function resetMainSession() {
74+return await directSessionReq<{ ok: true; key: string; entry: { sessionId: string } }>(
8275"sessions.reset",
8376{
8477key: "main",
8578},
8679);
80+}
81+82+function installAcpRuntimeBackendWithFreshSession() {
83+const prepareFreshSession = vi.fn(async () => {});
84+acpRuntimeMocks.getAcpRuntimeBackend.mockReturnValue({
85+id: "acpx",
86+runtime: {
87+ prepareFreshSession,
88+},
89+});
90+return prepareFreshSession;
91+}
92+93+function resolvedAcpMeta(params: {
94+recordId: string;
95+backendSessionId: string;
96+runtimeSessionName?: string;
97+mode?: SessionAcpMeta["mode"];
98+runtimeOptions?: SessionAcpMeta["runtimeOptions"];
99+}): SessionAcpMeta {
100+const meta: SessionAcpMeta = {
101+backend: "acpx",
102+agent: "codex",
103+runtimeSessionName: params.runtimeSessionName ?? "runtime:reset",
104+identity: {
105+state: "resolved",
106+acpxRecordId: params.recordId,
107+acpxSessionId: params.backendSessionId,
108+source: "status",
109+lastUpdatedAt: Date.now(),
110+},
111+mode: params.mode ?? "persistent",
112+cwd: "/tmp/acp-session",
113+state: "idle",
114+lastActivityAt: Date.now(),
115+};
116+if (params.runtimeOptions) {
117+meta.runtimeOptions = params.runtimeOptions;
118+}
119+return meta;
120+}
121+122+async function expectResetWithConfigSkipsBrowserCleanup(config: ConfigFilePatch) {
123+const { writeConfigFile } = await import("../config/config.js");
124+await writeConfigFile(config);
125+try {
126+await seedWaitingActiveMainSession();
127+const reset = await resetMainSession();
128+129+expect(reset.ok).toBe(true);
130+expect(browserSessionTabMocks.closeTrackedBrowserTabsForSessions).not.toHaveBeenCalled();
131+} finally {
132+await writeConfigFile({});
133+}
134+}
135+136+test("sessions.reset aborts active runs and clears queues", async () => {
137+await seedWaitingActiveMainSession();
138+enqueueSystemEvent("stale event via alias", { sessionKey: "main" });
139+enqueueSystemEvent("stale event via canonical key", { sessionKey: "agent:main:main" });
140+enqueueSystemEvent("stale event via session id", { sessionKey: "sess-main" });
141+const waitCallCountAtSnapshotClear: number[] = [];
142+bootstrapCacheMocks.clearBootstrapSnapshot.mockImplementation(() => {
143+waitCallCountAtSnapshotClear.push(embeddedRunMock.waitCalls.length);
144+});
145+146+const reset = await resetMainSession();
87147expect(reset.ok).toBe(true);
88148expect(reset.payload?.key).toBe("agent:main:main");
89149expect(reset.payload?.entry.sessionId).not.toBe("sess-main");
@@ -120,59 +180,19 @@ test("sessions.reset aborts active runs and clears queues", async () => {
120180});
121181122182test("sessions.reset skips browser cleanup when root browser support is disabled", async () => {
123-const { writeConfigFile } = await import("../config/config.js");
124-await writeConfigFile({ browser: { enabled: false } });
125-try {
126-await seedActiveMainSession();
127-embeddedRunMock.activeIds.add("sess-main");
128-embeddedRunMock.waitResults.set("sess-main", true);
129-130-const reset = await directSessionReq<{ ok: true; key: string; entry: { sessionId: string } }>(
131-"sessions.reset",
132-{
133-key: "main",
134-},
135-);
136-137-expect(reset.ok).toBe(true);
138-expect(browserSessionTabMocks.closeTrackedBrowserTabsForSessions).not.toHaveBeenCalled();
139-} finally {
140-await writeConfigFile({});
141-}
183+await expectResetWithConfigSkipsBrowserCleanup({ browser: { enabled: false } });
142184});
143185144186test("sessions.reset skips browser cleanup when the browser plugin entry is disabled", async () => {
145-const { writeConfigFile } = await import("../config/config.js");
146-await writeConfigFile({ plugins: { entries: { browser: { enabled: false } } } });
147-try {
148-await seedActiveMainSession();
149-embeddedRunMock.activeIds.add("sess-main");
150-embeddedRunMock.waitResults.set("sess-main", true);
151-152-const reset = await directSessionReq<{ ok: true; key: string; entry: { sessionId: string } }>(
153-"sessions.reset",
154-{
155-key: "main",
156-},
157-);
158-159-expect(reset.ok).toBe(true);
160-expect(browserSessionTabMocks.closeTrackedBrowserTabsForSessions).not.toHaveBeenCalled();
161-} finally {
162-await writeConfigFile({});
163-}
187+await expectResetWithConfigSkipsBrowserCleanup({
188+plugins: { entries: { browser: { enabled: false } } },
189+});
164190});
165191166192test("sessions.reset closes ACP runtime handles for ACP sessions", async () => {
167193const { dir, storePath } = await createSessionStoreDir();
168194await writeSingleLineSession(dir, "sess-main", "hello");
169-const prepareFreshSession = vi.fn(async () => {});
170-acpRuntimeMocks.getAcpRuntimeBackend.mockReturnValue({
171-id: "acpx",
172-runtime: {
173- prepareFreshSession,
174-},
175-});
195+const prepareFreshSession = installAcpRuntimeBackendWithFreshSession();
176196177197await writeSessionStore({
178198entries: {
@@ -181,26 +201,14 @@ test("sessions.reset closes ACP runtime handles for ACP sessions", async () => {
181201});
182202writeAcpSessionMetaForMigration({
183203sessionKey: "agent:main:main",
184-meta: {
185-backend: "acpx",
186-agent: "codex",
187-runtimeSessionName: "runtime:reset",
188-identity: {
189-state: "resolved",
190-acpxRecordId: "agent:main:main",
191-acpxSessionId: "backend-session-1",
192-source: "status",
193-lastUpdatedAt: Date.now(),
194-},
195-mode: "persistent",
204+meta: resolvedAcpMeta({
205+recordId: "agent:main:main",
206+backendSessionId: "backend-session-1",
196207runtimeOptions: {
197208runtimeMode: "auto",
198209timeoutSeconds: 30,
199210},
200-cwd: "/tmp/acp-session",
201-state: "idle",
202-lastActivityAt: Date.now(),
203-},
211+}),
204212});
205213const reset = await directSessionReq<{
206214ok: true;
@@ -248,25 +256,7 @@ test("sessions.reset closes ACP runtime handles for ACP sessions", async () => {
248256});
249257const store = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
250258string,
251-{
252-acp?: {
253-backend?: string;
254-agent?: string;
255-runtimeSessionName?: string;
256-identity?: {
257-state?: string;
258-acpxRecordId?: string;
259-acpxSessionId?: string;
260-};
261-mode?: string;
262-runtimeOptions?: {
263-runtimeMode?: string;
264-timeoutSeconds?: number;
265-};
266-cwd?: string;
267-state?: string;
268-};
269-}
259+{ acp?: ResetAcpState }
270260>;
271261expect(store["agent:main:main"]).not.toHaveProperty("acp");
272262expectResetAcpState(readAcpSessionMeta({ sessionKey: "agent:main:main" }));
@@ -275,13 +265,7 @@ test("sessions.reset closes ACP runtime handles for ACP sessions", async () => {
275265test("sessions.reset closes child ACP runtime handles spawned from the parent", async () => {
276266const { dir } = await createSessionStoreDir();
277267await writeSingleLineSession(dir, "sess-main", "hello");
278-const prepareFreshSession = vi.fn(async () => {});
279-acpRuntimeMocks.getAcpRuntimeBackend.mockReturnValue({
280-id: "acpx",
281-runtime: {
282- prepareFreshSession,
283-},
284-});
268+installAcpRuntimeBackendWithFreshSession();
285269286270await writeSessionStore({
287271entries: {
@@ -299,41 +283,19 @@ test("sessions.reset closes child ACP runtime handles spawned from the parent",
299283});
300284writeAcpSessionMetaForMigration({
301285sessionKey: "agent:main:main",
302-meta: {
303-backend: "acpx",
304-agent: "codex",
305-runtimeSessionName: "runtime:reset",
306-identity: {
307-state: "resolved",
308-acpxRecordId: "agent:main:main",
309-acpxSessionId: "backend-session-main",
310-source: "status",
311-lastUpdatedAt: Date.now(),
312-},
313-mode: "persistent",
314-cwd: "/tmp/acp-session",
315-state: "idle",
316-lastActivityAt: Date.now(),
317-},
286+meta: resolvedAcpMeta({
287+recordId: "agent:main:main",
288+backendSessionId: "backend-session-main",
289+}),
318290});
319291writeAcpSessionMetaForMigration({
320292sessionKey: "agent:main:acp-child-1",
321-meta: {
322-backend: "acpx",
323-agent: "codex",
293+meta: resolvedAcpMeta({
294+recordId: "agent:main:acp-child-1",
295+backendSessionId: "backend-session-child-1",
324296runtimeSessionName: "runtime:child-1",
325-identity: {
326-state: "resolved",
327-acpxRecordId: "agent:main:acp-child-1",
328-acpxSessionId: "backend-session-child-1",
329-source: "status",
330-lastUpdatedAt: Date.now(),
331-},
332297mode: "oneshot",
333-cwd: "/tmp/acp-session",
334-state: "idle",
335-lastActivityAt: Date.now(),
336-},
298+}),
337299});
338300writeAcpSessionMetaForMigration({
339301sessionKey: "agent:main:unrelated-acp-child",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。