From e1e095d020632254064ee8557d21eb6ef8a42d1a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 3 Jun 2026 21:59:26 -0400 Subject: [PATCH] docs: document agent catalog helpers --- src/agents/btw-transcript.ts | 16 ++++++++++++++++ src/agents/hook-system-context-boundary.ts | 3 +++ src/agents/model-alias-lines.ts | 1 + src/agents/plugin-model-catalog.ts | 10 ++++++++++ src/agents/subagent-spawn.types.ts | 5 +++++ 5 files changed, 35 insertions(+) diff --git a/src/agents/btw-transcript.ts b/src/agents/btw-transcript.ts index 42639469a8c..0bf7796afdf 100644 --- a/src/agents/btw-transcript.ts +++ b/src/agents/btw-transcript.ts @@ -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) ?? []) : []; } diff --git a/src/agents/hook-system-context-boundary.ts b/src/agents/hook-system-context-boundary.ts index 17642a2f796..b3788170245 100644 --- a/src/agents/hook-system-context-boundary.ts +++ b/src/agents/hook-system-context-boundary.ts @@ -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; diff --git a/src/agents/model-alias-lines.ts b/src/agents/model-alias-lines.ts index f612395ac00..6f937ba8be0 100644 --- a/src/agents/model-alias-lines.ts +++ b/src/agents/model-alias-lines.ts @@ -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 }> = []; diff --git a/src/agents/plugin-model-catalog.ts b/src/agents/plugin-model-catalog.ts index cc747b219e3..da143de3eda 100644 --- a/src/agents/plugin-model-catalog.ts +++ b/src/agents/plugin-model-catalog.ts @@ -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; @@ -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(params: { catalogPluginId?: string; parsedCatalog?: unknown; diff --git a/src/agents/subagent-spawn.types.ts b/src/agents/subagent-spawn.types.ts index 215102cebfa..d9a85391ca3 100644 --- a/src/agents/subagent-spawn.types.ts +++ b/src/agents/subagent-spawn.types.ts @@ -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];