mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
docs: document subagent registry lifecycle tests
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
// Subagent registry lifecycle tests cover completion, cleanup, announce retry,
|
||||
// detached task status, and resource retirement around child-run endings.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CallGatewayOptions } from "../gateway/call.js";
|
||||
import {
|
||||
@@ -203,6 +205,8 @@ async function runNoReplyMirrorScenario(params: {
|
||||
idempotencyKey?: string;
|
||||
idempotencyKeyForEntry?: (entry: SubagentRunRecord) => string;
|
||||
}): Promise<SubagentRunRecord> {
|
||||
// A failed direct announce can still be mirrored from the requester history;
|
||||
// the idempotency key prevents stale or unrelated assistant text from winning.
|
||||
const entry = createRunEntry({
|
||||
endedAt: 4_000,
|
||||
expectsCompletionMessage: true,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Subagent registry query tests cover liveness, descendant counting, requester
|
||||
// lookup, and stale-row handling for in-memory run snapshots.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
countActiveRunsForSessionFromRuns,
|
||||
@@ -223,7 +225,7 @@ describe("subagent registry query regressions", () => {
|
||||
});
|
||||
|
||||
it("regression nested parallel counting, traversal includes child and grandchildren pending states", () => {
|
||||
// Regression guard: nested fan-out once under-counted grandchildren and announced too early.
|
||||
// Nested fan-out once under-counted grandchildren and announced too early.
|
||||
const parentSessionKey = "agent:main:subagent:parent-nested";
|
||||
const middleSessionKey = `${parentSessionKey}:subagent:middle`;
|
||||
const runs = toRunMap([
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Subagent registry read-context tests cover the indexed snapshot used by hot
|
||||
// prompt/control paths instead of repeatedly scanning the run map.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildSubagentRunReadIndexFromRuns,
|
||||
@@ -160,6 +162,8 @@ describe("subagent registry read index", () => {
|
||||
});
|
||||
|
||||
it("keeps one snapshot stable for the lifetime of the context", () => {
|
||||
// Read indexes are process-local snapshots; callers can reuse them through
|
||||
// one prompt assembly without observing later registry mutations.
|
||||
const root = "agent:main:main";
|
||||
const runs = toRunMap([
|
||||
makeRun({
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
// Announce loop-guard tests prove deferred subagent delivery eventually gives
|
||||
// up instead of retrying forever after gateway delivery keeps returning false.
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import type { SubagentRunRecord } from "./subagent-registry.types.js";
|
||||
|
||||
/**
|
||||
* Regression test for #18264: Gateway announcement delivery loop.
|
||||
*
|
||||
* When `runSubagentAnnounceFlow` repeatedly returns `false` (deferred),
|
||||
* `finalizeSubagentCleanup` must eventually give up rather than retrying
|
||||
* forever via the max-retry and expiration guards.
|
||||
*/
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getRuntimeConfig: vi.fn(() => ({
|
||||
session: { store: "/tmp/test-store", mainKey: "main" },
|
||||
@@ -200,7 +194,8 @@ describe("announce loop guard (#18264)", () => {
|
||||
const entry = createEntry(Date.now());
|
||||
mocks.loadSubagentRegistryFromSqlite.mockReturnValue(new Map([[entry.runId, entry]]));
|
||||
|
||||
// Initialization attempts resume once, then gives up for exhausted entries.
|
||||
// Initialization attempts one resume, then relies on expiry/retry-budget
|
||||
// guards so old pending rows do not loop after restart.
|
||||
const beforeInit = Date.now();
|
||||
registry.initSubagentRegistry();
|
||||
await flushAsync();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Lifecycle retry-grace e2e tests cover completion delivery retry behavior when
|
||||
// lifecycle events race gateway waits or transient announce failures.
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { testing as subagentAnnounceDeliveryTesting } from "./subagent-announce-delivery.js";
|
||||
import { testing as subagentAnnounceOutputTesting } from "./subagent-announce-output.js";
|
||||
@@ -220,6 +222,8 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
};
|
||||
|
||||
const waitForCleanupHandledFalse = async (runId: string) => {
|
||||
// Cleanup can be released asynchronously after announce failure; poll fake
|
||||
// time until the retry-grace state is observable.
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester(MAIN_REQUESTER_SESSION_KEY)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Nested subagent registry e2e tests cover requester/controller relationships
|
||||
// across orchestrator and leaf child sessions.
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import "./subagent-registry.mocks.shared.js";
|
||||
|
||||
@@ -76,7 +78,7 @@ describe("subagent registry nested agent tracking", () => {
|
||||
|
||||
it("announce uses requesterSessionKey to route to the correct parent", () => {
|
||||
const { registerSubagentRun } = subagentRegistry;
|
||||
// Register a sub-sub-agent whose parent is a sub-agent
|
||||
// Register a sub-sub-agent whose parent is a sub-agent.
|
||||
registerSubagentRun({
|
||||
runId: "run-subsub",
|
||||
childSessionKey: "agent:main:subagent:orch:subagent:child",
|
||||
@@ -87,9 +89,8 @@ describe("subagent registry nested agent tracking", () => {
|
||||
label: "nested-leaf",
|
||||
});
|
||||
|
||||
// When announce fires for the sub-sub-agent, it should target the sub-agent (depth-1),
|
||||
// NOT the main session. The registry entry's requesterSessionKey ensures this.
|
||||
// We verify the registry entry has the correct requesterSessionKey.
|
||||
// Announce should target the depth-1 parent, not the main session. The
|
||||
// registry entry's requesterSessionKey carries that routing boundary.
|
||||
const { listSubagentRunsForRequester } = subagentRegistry;
|
||||
const orchRuns = listSubagentRunsForRequester("agent:main:subagent:orch");
|
||||
expect(orchRuns).toHaveLength(1);
|
||||
|
||||
Reference in New Issue
Block a user