Files
openclaw/src/acp/session-interaction-mode.test.ts
T
scotthuang 8a7c21407a fix(agents): gate sessions_send A2A skip on requester ownership
Greptile/Codex review follow-ups on #69817:

- Narrow skipA2AFlow from target-only detection to a combined check that
  the caller is the parent of the target (new
  isRequesterParentOfBackgroundAcpSession helper). Under
  tools.sessions.visibility=all a non-parent sender can see the same
  oneshot ACP session; the previous guard would have suppressed their
  only follow-up delivery path. With requester ownership required, those
  senders continue through the normal A2A flow.
- When the A2A flow is skipped, return delivery.status="skipped" instead
  of "pending" so the parent LLM does not wait for a second result that
  will never arrive.
- Add unit tests for resolveAcpSessionInteractionMode and
  isRequesterParentOfBackgroundAcpSession covering both the new
  ownership gate and the existing target-type branches.
2026-04-21 22:17:28 +01:00

99 lines
3.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
isParentOwnedBackgroundAcpSession,
isRequesterParentOfBackgroundAcpSession,
resolveAcpSessionInteractionMode,
} from "./session-interaction-mode.js";
const parentKey = "agent:main:main";
const otherKey = "agent:peer:some-other";
describe("resolveAcpSessionInteractionMode", () => {
it("returns interactive when entry is undefined", () => {
expect(resolveAcpSessionInteractionMode(undefined)).toBe("interactive");
});
it("returns interactive for non-oneshot ACP sessions", () => {
expect(
resolveAcpSessionInteractionMode({
acp: { mode: "persistent" } as never,
spawnedBy: parentKey,
}),
).toBe("interactive");
});
it("returns parent-owned-background for oneshot sessions with spawnedBy set", () => {
expect(
resolveAcpSessionInteractionMode({
acp: { mode: "oneshot" } as never,
spawnedBy: parentKey,
}),
).toBe("parent-owned-background");
});
it("returns parent-owned-background for oneshot sessions with parentSessionKey set", () => {
expect(
resolveAcpSessionInteractionMode({
acp: { mode: "oneshot" } as never,
parentSessionKey: parentKey,
}),
).toBe("parent-owned-background");
});
it("returns interactive for a oneshot session without any parent linkage", () => {
expect(
resolveAcpSessionInteractionMode({
acp: { mode: "oneshot" } as never,
}),
).toBe("interactive");
});
});
describe("isRequesterParentOfBackgroundAcpSession", () => {
const backgroundEntry = {
acp: { mode: "oneshot" } as never,
spawnedBy: parentKey,
parentSessionKey: parentKey,
};
it("returns true when requester matches spawnedBy", () => {
expect(
isRequesterParentOfBackgroundAcpSession(
{ acp: { mode: "oneshot" } as never, spawnedBy: parentKey },
parentKey,
),
).toBe(true);
});
it("returns true when requester matches parentSessionKey", () => {
expect(
isRequesterParentOfBackgroundAcpSession(
{ acp: { mode: "oneshot" } as never, parentSessionKey: parentKey },
parentKey,
),
).toBe(true);
});
it("returns false when requester is a different session (not the parent)", () => {
expect(isRequesterParentOfBackgroundAcpSession(backgroundEntry, otherKey)).toBe(false);
});
it("returns false when requester key is missing", () => {
expect(isRequesterParentOfBackgroundAcpSession(backgroundEntry, undefined)).toBe(false);
expect(isRequesterParentOfBackgroundAcpSession(backgroundEntry, "")).toBe(false);
});
it("returns false when target is not a parent-owned background ACP session", () => {
expect(
isRequesterParentOfBackgroundAcpSession(
{ acp: { mode: "persistent" } as never, spawnedBy: parentKey },
parentKey,
),
).toBe(false);
});
it("delegates to isParentOwnedBackgroundAcpSession for target-only checks", () => {
expect(isParentOwnedBackgroundAcpSession(backgroundEntry)).toBe(true);
});
});