docs: document agent catalog helpers

This commit is contained in:
Peter Steinberger
2026-06-03 21:59:26 -04:00
parent 7e5ea598c5
commit e1e095d020
5 changed files with 35 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ import {
type SessionEntry as AgentSessionEntry,
} from "./sessions/session-manager.js";
/** Resolves the persisted transcript file for a BTW session handoff. */
export function resolveBtwSessionTranscriptPath(params: {
sessionId: string;
sessionEntry?: StoredSessionEntry;
@@ -33,6 +34,8 @@ export function resolveBtwSessionTranscriptPath(params: {
}
}
// Session entries can come from older transcript formats, so id fields are
// narrowed at this boundary before branch reconstruction trusts them.
function readSessionEntryId(entry: AgentSessionEntry): string | undefined {
const id = (entry as { id?: unknown }).id;
return typeof id === "string" && id.trim().length > 0 ? id : undefined;
@@ -46,10 +49,14 @@ function readSessionEntryParentId(entry: AgentSessionEntry): string | null | und
return typeof parentId === "string" && parentId.trim().length > 0 ? parentId : undefined;
}
// Parent links mark fork-aware transcripts. Without them, the flat session
// context builder preserves the legacy append-only transcript behavior.
function hasParentLinkedEntries(entries: AgentSessionEntry[]): boolean {
return entries.some((entry) => Boolean(readSessionEntryId(entry) && "parentId" in entry));
}
// Reconstructs the selected branch from leaf to root. Missing links or cycles
// mean the snapshot cannot be trusted, so callers fall back to a safe branch.
function buildSessionBranchEntries(
entries: AgentSessionEntry[],
leafId: string | undefined,
@@ -99,6 +106,13 @@ function isTrailingUserMessage(entry: AgentSessionEntry | undefined): boolean {
);
}
/**
* Reads prior messages for BTW continuation.
*
* When a transcript has fork links, this returns the selected snapshot branch
* instead of the full file so a resumed agent does not inherit sibling-branch
* messages.
*/
export async function readBtwTranscriptMessages(params: {
sessionFile: string;
sessionId: string;
@@ -124,6 +138,8 @@ export async function readBtwTranscriptMessages(params: {
}
branchEntries ??= buildSessionBranchEntries(sessionEntries, readDefaultLeafId(sessionEntries));
if (!params.snapshotLeafId && isTrailingUserMessage(branchEntries?.at(-1))) {
// Auto-selecting the newest branch must not include the current user turn
// that triggered BTW handoff; the subagent should continue from its parent.
const parentId = readSessionEntryParentId(branchEntries!.at(-1)!);
branchEntries = parentId ? (buildSessionBranchEntries(sessionEntries, parentId) ?? []) : [];
}

View File

@@ -1,8 +1,11 @@
import { normalizeStructuredPromptSection } from "./prompt-cache-stability.js";
// Labels plugin-provided system context so harness prompt compaction and user-facing
// transcript views can distinguish it from real workspace files or chat content.
const HOOK_SYSTEM_CONTEXT_HEADER =
"OpenClaw plugin-injected system context. This block is not workspace file content.";
/** Normalizes and fences plugin-injected system context before it enters prompts. */
export function wrapPluginSystemContextSection(value?: string): string | undefined {
if (typeof value !== "string") {
return undefined;

View File

@@ -1,6 +1,7 @@
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { OpenClawConfig } from "../config/types.openclaw.js";
/** Builds deterministic prompt lines for configured model aliases. */
export function buildModelAliasLines(cfg?: OpenClawConfig) {
const models = cfg?.agents?.defaults?.models ?? {};
const entries: Array<{ alias: string; model: string }> = [];

View File

@@ -3,6 +3,8 @@ import path from "node:path";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
// Generated catalog files live under each agent profile so provider model
// discovery can reuse plugin-owned catalogs without loading plugin runtimes.
export const PLUGIN_MODEL_CATALOG_FILE = "catalog.json";
export const PLUGIN_MODEL_CATALOG_GENERATED_BY = "openclaw-plugin-model-catalog-v1";
@@ -22,10 +24,12 @@ export type PluginModelCatalogFile = {
relativePath: string;
};
/** Encodes the profile-relative path for a plugin-owned generated model catalog. */
export function encodePluginModelCatalogRelativePath(pluginId: string): string {
return `plugins/${encodeURIComponent(pluginId)}/${PLUGIN_MODEL_CATALOG_FILE}`;
}
/** Returns true only for canonical profile-relative generated catalog paths. */
export function isPluginModelCatalogRelativePath(relativePath: string): boolean {
const parts = relativePath.split(/[\\/]/);
return (
@@ -39,6 +43,7 @@ export function isPluginModelCatalogRelativePath(relativePath: string): boolean
);
}
/** Decodes the plugin id from a canonical generated catalog path. */
export function decodePluginModelCatalogRelativePathPluginId(
relativePath: string,
): string | undefined {
@@ -53,6 +58,7 @@ export function decodePluginModelCatalogRelativePathPluginId(
}
}
/** Lists deterministic generated catalog paths present in an agent profile. */
export function listPluginModelCatalogRelativePaths(agentDir: string): string[] {
const pluginsDir = path.join(agentDir, "plugins");
let pluginDirs: Array<import("node:fs").Dirent>;
@@ -68,6 +74,7 @@ export function listPluginModelCatalogRelativePaths(agentDir: string): string[]
.toSorted((left, right) => left.localeCompare(right));
}
/** Lists existing generated catalog files with decoded plugin ownership. */
export function listPluginModelCatalogFiles(agentDir: string): PluginModelCatalogFile[] {
return listPluginModelCatalogRelativePaths(agentDir)
.map((relativePath) => {
@@ -84,6 +91,7 @@ export function listPluginModelCatalogFiles(agentDir: string): PluginModelCatalo
.filter((entry) => existsSync(entry.path));
}
/** Detects model catalogs generated by OpenClaw rather than user-authored JSON. */
export function isGeneratedPluginModelCatalog(value: unknown): boolean {
return (
typeof value === "object" &&
@@ -93,6 +101,7 @@ export function isGeneratedPluginModelCatalog(value: unknown): boolean {
);
}
/** Resolves the sole enabled plugin that owns a provider's model catalog. */
export function resolvePluginModelCatalogOwnerPluginId(params: {
providerId: string;
pluginMetadataSnapshot?: PluginModelCatalogMetadataSnapshot;
@@ -123,6 +132,7 @@ export function resolvePluginModelCatalogOwnerPluginId(params: {
: undefined;
}
/** Keeps generated catalog providers only when the catalog plugin still owns them. */
export function filterGeneratedPluginModelCatalogProviders<T>(params: {
catalogPluginId?: string;
parsedCatalog?: unknown;

View File

@@ -1,8 +1,13 @@
// Shared spawn mode enums used by the session tool, registry, and announce
// delivery code. Keep these narrow so tool schemas and persisted registry rows
// agree on the same literals.
export const SUBAGENT_SPAWN_MODES = ["run", "session"] as const;
export type SpawnSubagentMode = (typeof SUBAGENT_SPAWN_MODES)[number];
/** Sandbox escalation policy requested for a spawned subagent. */
export const SUBAGENT_SPAWN_SANDBOX_MODES = ["inherit", "require"] as const;
export type SpawnSubagentSandboxMode = (typeof SUBAGENT_SPAWN_SANDBOX_MODES)[number];
/** Prompt context relationship between the parent session and spawned subagent. */
export const SUBAGENT_SPAWN_CONTEXT_MODES = ["isolated", "fork"] as const;
export type SpawnSubagentContextMode = (typeof SUBAGENT_SPAWN_CONTEXT_MODES)[number];