docs: document embedded subscribe lifecycle

This commit is contained in:
Peter Steinberger
2026-06-04 15:03:07 -04:00
parent a5880a3747
commit e0fe08ccce
5 changed files with 28 additions and 0 deletions
@@ -1,3 +1,5 @@
// Soft chunking tests cover paragraph-preferred block reply splits and fenced
// code preservation during streamed assistant output.
import { describe, expect, it, vi } from "vitest";
import {
createParagraphChunkedBlockReplyHarness,
@@ -5,6 +7,7 @@ import {
} from "./embedded-agent-subscribe.e2e-harness.js";
function blockReplyTexts(onBlockReply: ReturnType<typeof vi.fn>): string[] {
// Helper extracts just user-visible text from emitted block reply payloads.
return onBlockReply.mock.calls.map(([payload]) => (payload as { text?: string }).text ?? "");
}
@@ -1,3 +1,5 @@
// End-to-end subscription tests cover usage, lifecycle, tool logging,
// messaging/media side effects, and replay-state behavior for embedded runs.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -22,6 +24,8 @@ import { makeZeroUsageSnapshot } from "./usage.js";
describe("subscribeEmbeddedAgentSession", () => {
async function flushBlockReplyCallbacks(): Promise<void> {
// Block replies can schedule nested microtasks; drain twice before checking
// delivery state in broad subscription tests.
await Promise.resolve();
await Promise.resolve();
}
@@ -54,6 +58,8 @@ describe("subscribeEmbeddedAgentSession", () => {
function createSubscribedHarness(
options: Omit<Parameters<typeof subscribeEmbeddedAgentSession>[0], "session">,
) {
// Default trusted media tools to built-ins so tests that opt into custom
// builtin sets get matching local media trust behavior.
const { session, emit } = createStubSessionHarness();
subscribeEmbeddedAgentSession({
session,
@@ -135,6 +141,8 @@ describe("subscribeEmbeddedAgentSession", () => {
}
async function captureToolLifecycleLogSubsystems(messageChannel?: string): Promise<string[]> {
// Use a temporary file-backed logger so subsystem attribution is verified
// against real serialized log lines.
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-tool-log-attribution-"));
const logFile = path.join(tempDir, "openclaw.log");
try {
@@ -1,8 +1,12 @@
// Commentary suppression tests ensure commentary-phase assistant messages do
// not become replies or assistantTexts before tool use.
import type { AssistantMessage } from "openclaw/plugin-sdk/llm";
import { describe, expect, it, vi } from "vitest";
import { createSubscribedSessionHarness } from "./embedded-agent-subscribe.e2e-harness.js";
type AssistantMessageWithPhase = AssistantMessage & {
// Some providers expose phase on the assistant message, while others only
// include it in textSignature metadata.
phase?: "commentary" | "final_answer";
};
@@ -1,3 +1,5 @@
// Message-tool suppression tests cover delivery tracking, source-reply mirrors,
// and duplicate reply prevention after message tool sends.
import type { AssistantMessage } from "openclaw/plugin-sdk/llm";
import { describe, expect, it, vi } from "vitest";
import {
@@ -9,6 +11,8 @@ import {
import { subscribeEmbeddedAgentSession } from "./embedded-agent-subscribe.js";
function createBlockReplyHarness(blockReplyBreak: "message_end" | "text_end") {
// Harness exposes both emitted block replies and subscription state so tests
// can distinguish suppression from missing delivery tracking.
const { session, emit } = createStubSessionHarness();
const onBlockReply = vi.fn();
const subscription = subscribeEmbeddedAgentSession({
@@ -27,6 +31,8 @@ async function emitMessageToolLifecycle(params: {
media?: string;
result: unknown;
}) {
// Message tool sends are modeled as normal tool start/end events because the
// subscription records pending send text at start and delivery at end.
params.emit({
type: "tool_execution_start",
toolName: "message",
@@ -97,6 +103,8 @@ describe("subscribeEmbeddedAgentSession", () => {
});
it("tracks internal-ui source replies for message-tool-only final payloads", async () => {
// internal-ui source replies are not ordinary channel sends; they are stored
// for terminal payload mirroring in message_tool_only mode.
const { emit, subscription } = createBlockReplyHarness("message_end");
await emitMessageToolLifecycle({
@@ -1,3 +1,5 @@
// Compaction retry subscription tests cover retry wait accounting, compaction
// event emission, abort-on-unsubscribe, and verbose tool summary behavior.
import { describe, expect, it, vi } from "vitest";
import { onAgentEvent } from "../infra/agent-events.js";
import { createSubscribedSessionHarness } from "./embedded-agent-subscribe.e2e-harness.js";
@@ -8,6 +10,7 @@ function toolResultPayloadAt(
): {
text?: string;
} {
// Tool summary assertions only need text payloads from onToolResult calls.
const [payload] = onToolResult.mock.calls[index] ?? [];
if (!payload || typeof payload !== "object") {
throw new Error(`expected tool result payload for call ${index + 1}`);
@@ -17,6 +20,8 @@ function toolResultPayloadAt(
describe("subscribeEmbeddedAgentSession", () => {
it("waits for multiple compaction retries before resolving", async () => {
// Each retrying compaction requires a matching agent_end before waiters are
// released, preventing early continuation during repeated overflow repairs.
const { emit, subscription } = createSubscribedSessionHarness({
runId: "run-3",
});