Skip to content

Commit 4ebbf1d

Browse files
Galin IlievGalin Iliev
authored andcommitted
fix(sessions): gate ACP runtime overlay on session metadata
1 parent f9a30cc commit 4ebbf1d

7 files changed

Lines changed: 44 additions & 12 deletions

File tree

src/agents/acp-runtime-overlay.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@ export type AgentRuntimeMetadata = {
1313
};
1414

1515
/**
16-
* When a session key unambiguously identifies an ACP session (contains the `:acp:`
17-
* segment), override the resolved runtime metadata to report the ACP runtime id
18-
* with a "session-key" source — regardless of what the agent-config policy
19-
* resolved to. Without this overlay, callers that only have agent-config context
20-
* (no model/provider) fall back to `id: "pi"` even though the session key makes
21-
* the actual runtime unambiguous.
16+
* When a session key and persisted session metadata identify an ACP
17+
* control-plane session, override the resolved runtime metadata to report the
18+
* ACP runtime id with a "session-key" source — regardless of what the
19+
* agent-config policy resolved to.
2220
*
2321
* Callers that already have model/provider context (resolveModelAgentRuntimeMetadata)
2422
* still benefit here because the model-runtime policy chain does not inspect session
2523
* keys for the ACP indicator.
2624
*
25+
* Key shape alone is not sufficient: ACP bridge sessions may use ACP-shaped
26+
* keys without persisted SessionAcpMeta and still run the configured model.
27+
*
2728
* When `acpBackend` is provided and non-empty, it is used as the runtime id so that
2829
* sessions backed by a configured non-default ACP backend (e.g. a custom registered
2930
* backend) are reported faithfully instead of always being labelled "acpx".
@@ -32,9 +33,10 @@ export type AgentRuntimeMetadata = {
3233
export function applyAcpRuntimeOverlay(
3334
meta: AgentRuntimeMetadata,
3435
sessionKey: string | undefined | null,
36+
acpRuntime: boolean | undefined,
3537
acpBackend?: string,
3638
): AgentRuntimeMetadata {
37-
if (isAcpSessionKey(sessionKey)) {
39+
if (acpRuntime === true && isAcpSessionKey(sessionKey)) {
3840
const id = acpBackend && acpBackend.length > 0 ? acpBackend : "acpx";
3941
return { id, source: "session-key" };
4042
}

src/agents/agent-runtime-metadata.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export function resolveModelAgentRuntimeMetadata(params: {
2222
provider?: string;
2323
model?: string;
2424
sessionKey?: string;
25+
/**
26+
* True when the loaded session entry has persisted ACP metadata. ACP-shaped
27+
* keys without this marker can be bridge sessions that use the configured
28+
* model/runtime.
29+
*/
30+
acpRuntime?: boolean;
2531
/**
2632
* The ACP backend identifier stored on the session entry (`entry.acp.backend`).
2733
* When provided for an ACP-keyed session, the overlay reports this value as the
@@ -45,5 +51,5 @@ export function resolveModelAgentRuntimeMetadata(params: {
4551
id: policy.runtime,
4652
source: policy.runtimeSource ?? "implicit",
4753
};
48-
return applyAcpRuntimeOverlay(meta, params.sessionKey, params.acpBackend);
54+
return applyAcpRuntimeOverlay(meta, params.sessionKey, params.acpRuntime, params.acpBackend);
4955
}

src/commands/sessions.acp-runtime-metadata.test.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ function computeSessionAgentRuntime(params: {
8686
cfg: OpenClawConfig;
8787
sessionKey: string;
8888
fallbackAgentId: string;
89+
/** Mirrors `entry?.acp != null` passed from loaded session rows. */
90+
acpRuntime?: boolean;
8991
/** Mirrors `entry?.acp?.backend` passed from the session store entry. */
9092
acpBackend?: string;
9193
}): ReturnType<typeof resolveModelAgentRuntimeMetadata> {
@@ -94,6 +96,7 @@ function computeSessionAgentRuntime(params: {
9496
cfg: params.cfg,
9597
agentId,
9698
sessionKey: params.sessionKey,
99+
acpRuntime: params.acpRuntime,
97100
acpBackend: params.acpBackend,
98101
});
99102
}
@@ -105,6 +108,7 @@ describe("sessions --json agentRuntime classifier (catalog #18)", () => {
105108
cfg,
106109
sessionKey: ACP_SESSION_KEY,
107110
fallbackAgentId: "copilot",
111+
acpRuntime: true,
108112
});
109113

110114
// The bug was: the session key plainly contains `:acp:` and yet the
@@ -158,6 +162,7 @@ describe("sessions --json agentRuntime classifier (catalog #18)", () => {
158162
cfg,
159163
sessionKey: ACP_SESSION_KEY,
160164
fallbackAgentId: "copilot",
165+
acpRuntime: true,
161166
});
162167

163168
expect(
@@ -178,26 +183,40 @@ describe("sessions --json agentRuntime classifier (catalog #18)", () => {
178183
cfg,
179184
sessionKey: ACP_SESSION_KEY,
180185
fallbackAgentId: "copilot",
186+
acpRuntime: true,
181187
acpBackend: "custom-backend",
182188
});
183189

184190
expect(agentRuntime.id).toBe("custom-backend");
185191
expect(agentRuntime.source).toBe("session-key");
186192
});
187193

188-
it("backend fallback: ACP session with no entry.acp.backend falls back to 'acpx'", () => {
189-
// When the session entry has no acp.backend (entry.acp is absent or backend
190-
// is not set), the overlay must fall back to the canonical "acpx" id so
191-
// existing behaviour is preserved.
194+
it("backend fallback: ACP session with entry.acp but no backend falls back to 'acpx'", () => {
195+
// When the session entry has ACP metadata but no acp.backend, the overlay
196+
// must fall back to the canonical "acpx" id.
192197
const cfg = buildConfigWithoutAgentRuntimePolicy();
193198
const agentRuntime = computeSessionAgentRuntime({
194199
cfg,
195200
sessionKey: ACP_SESSION_KEY,
196201
fallbackAgentId: "copilot",
202+
acpRuntime: true,
197203
// acpBackend intentionally omitted — mirrors entry with no acp.backend
198204
});
199205

200206
expect(agentRuntime.id).toBe("acpx");
201207
expect(agentRuntime.source).toBe("session-key");
202208
});
209+
210+
it("GREEN control: ACP-shaped bridge session without entry.acp is NOT overridden", () => {
211+
const cfg = buildConfigWithoutAgentRuntimePolicy();
212+
const agentRuntime = computeSessionAgentRuntime({
213+
cfg,
214+
sessionKey: ACP_SESSION_KEY,
215+
fallbackAgentId: "copilot",
216+
acpRuntime: false,
217+
});
218+
219+
expect(agentRuntime.id).not.toBe("acpx");
220+
expect(agentRuntime.source).not.toBe("session-key");
221+
});
203222
});

src/commands/sessions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ export async function sessionsCommand(
295295
provider: modelRef.provider,
296296
model: modelRef.model,
297297
sessionKey: row.key,
298+
acpRuntime,
298299
acpBackend: entry?.acp?.backend,
299300
});
300301
return Object.assign({}, row, {

src/commands/status.summary.runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ function resolveSessionRuntimeLabel(params: {
164164
provider: params.provider,
165165
model: params.model,
166166
sessionKey: params.sessionKey,
167+
acpRuntime: params.entry?.acp != null,
167168
acpBackend: params.entry?.acp?.backend,
168169
});
169170
const id = normalizeOptionalLowercaseString(runtime.id);

src/gateway/server-methods/sessions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
18171817
provider: resolvedDisplayModel.provider,
18181818
model: resolvedDisplayModel.model,
18191819
sessionKey: target.canonicalKey ?? key,
1820+
acpRuntime: applied.entry?.acp != null,
18201821
acpBackend: applied.entry?.acp?.backend,
18211822
});
18221823
const result: SessionsPatchResult = {

src/gateway/session-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,7 @@ export function listAgentsForGateway(cfg: OpenClawConfig): {
10241024
provider: resolvedModel.provider,
10251025
model: resolvedModel.model,
10261026
sessionKey: resolveAgentMainSessionKey({ cfg, agentId: id }),
1027+
acpRuntime: false,
10271028
}),
10281029
},
10291030
model ? { model } : {},
@@ -1735,6 +1736,7 @@ export function buildGatewaySessionRow(params: {
17351736
provider: rowModelProvider,
17361737
model: rowModel,
17371738
sessionKey: key,
1739+
acpRuntime: entry?.acp != null,
17381740
acpBackend: entry?.acp?.backend,
17391741
});
17401742
const estimatedCostUsd = lightweight

0 commit comments

Comments
 (0)