mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
fix(acp): separate bridge session namespace
This commit is contained in:
+6
-4
@@ -63,7 +63,7 @@ Quick rule:
|
||||
fallback and do not reconstruct historic tool calls or system notices.
|
||||
- If multiple ACP clients share the same Gateway session key, event and cancel
|
||||
routing are best-effort rather than strictly isolated per client. Prefer the
|
||||
default isolated `acp:<uuid>` sessions when you need clean editor-local
|
||||
default isolated `acp-bridge:<uuid>` sessions when you need clean editor-local
|
||||
turns.
|
||||
- Gateway stop states are translated into ACP stop reasons, but that mapping is
|
||||
less expressive than a fully ACP-native runtime.
|
||||
@@ -206,7 +206,7 @@ openclaw acp --session agent:qa:bug-123
|
||||
```
|
||||
|
||||
Each ACP session maps to a single Gateway session key. One agent can have many
|
||||
sessions; ACP defaults to an isolated `acp:<uuid>` session unless you override
|
||||
sessions; ACP defaults to an isolated `acp-bridge:<uuid>` session unless you override
|
||||
the key or label.
|
||||
|
||||
Per-session `mcpServers` are not supported in bridge mode. If an ACP client
|
||||
@@ -309,8 +309,10 @@ In Zed, open the Agent panel and select "OpenClaw ACP" to start a thread.
|
||||
|
||||
## Session mapping
|
||||
|
||||
By default, ACP sessions get an isolated Gateway session key with an `acp:` prefix.
|
||||
To reuse a known session, pass a session key or label:
|
||||
By default, ACP bridge sessions get an isolated Gateway session key with an
|
||||
`acp-bridge:` prefix. These normal-model bridge sessions are synthetic and
|
||||
subject to stale-entry pruning and entry-count caps. To reuse a known session,
|
||||
pass a session key or label:
|
||||
|
||||
- `--session <key>`: use a specific Gateway session key.
|
||||
- `--session-label <label>`: resolve an existing session by label.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createInMemorySessionStore } from "@openclaw/acp-core/session";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
import { isAcpSessionKey } from "../sessions/session-key-utils.js";
|
||||
import {
|
||||
createNewSessionRequest,
|
||||
createLoadSessionRequest,
|
||||
@@ -59,6 +60,21 @@ describe("acp unsupported bridge session setup", () => {
|
||||
});
|
||||
|
||||
describe("acp session UX bridge behavior", () => {
|
||||
it("uses a non-runtime namespace for generated bridge sessions", async () => {
|
||||
const sessionStore = createInMemorySessionStore();
|
||||
const agent = new AcpGatewayAgent(createAcpConnection(), createAcpGateway(), {
|
||||
sessionStore,
|
||||
});
|
||||
|
||||
const result = await agent.newSession(createNewSessionRequest());
|
||||
const sessionKey = sessionStore.getSession(result.sessionId)?.sessionKey;
|
||||
|
||||
expect(sessionKey).toMatch(/^acp-bridge:/);
|
||||
expect(isAcpSessionKey(sessionKey)).toBe(false);
|
||||
|
||||
sessionStore.clearAllSessionsForTest();
|
||||
});
|
||||
|
||||
it("returns initial modes and thought-level config options for new sessions", async () => {
|
||||
const sessionStore = createInMemorySessionStore();
|
||||
const agent = new AcpGatewayAgent(createAcpConnection(), createAcpGateway(), {
|
||||
|
||||
@@ -374,7 +374,7 @@ export class AcpGatewayAgent implements Agent {
|
||||
const meta = parseSessionMeta(params["_meta"]);
|
||||
const sessionKey = await this.resolveSessionKeyFromMeta({
|
||||
meta,
|
||||
fallbackKey: `acp:${sessionId}`,
|
||||
fallbackKey: `acp-bridge:${sessionId}`,
|
||||
});
|
||||
|
||||
const session = this.sessionStore.createSession({
|
||||
|
||||
@@ -308,10 +308,12 @@ function getEntryUpdatedAt(entry?: SessionEntry): number {
|
||||
function isSyntheticSessionMaintenanceKey(sessionKey: string): boolean {
|
||||
const parsed = parseAgentSessionKey(sessionKey);
|
||||
const rest = normalizeLowercaseStringOrEmpty(parsed?.rest ?? sessionKey);
|
||||
// ACP bridge sessions use normal model dispatch, but remain synthetic and disposable.
|
||||
return (
|
||||
isSubagentSessionKey(sessionKey) ||
|
||||
isAcpSessionKey(sessionKey) ||
|
||||
isCronSessionKey(sessionKey) ||
|
||||
rest.startsWith("acp-bridge:") ||
|
||||
rest.startsWith("hook:") ||
|
||||
rest.startsWith("node:") ||
|
||||
rest === "heartbeat" ||
|
||||
|
||||
@@ -266,6 +266,15 @@ describe("capEntryCount", () => {
|
||||
});
|
||||
|
||||
describe("isProtectedSessionMaintenanceEntry", () => {
|
||||
it("treats generated ACP bridge sessions as disposable", () => {
|
||||
expect(
|
||||
isProtectedSessionMaintenanceEntry("agent:main:acp-bridge:session-1", {
|
||||
...makeEntry(Date.now()),
|
||||
chatType: "group",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not protect synthetic sessions just because they carry group metadata", () => {
|
||||
expect(
|
||||
isProtectedSessionMaintenanceEntry("agent:main:subagent:worker", {
|
||||
|
||||
Reference in New Issue
Block a user