docs: document agent helper contracts

This commit is contained in:
Peter Steinberger
2026-06-04 06:23:32 -04:00
parent d2d2dfd9f2
commit 53d08d4aef
17 changed files with 102 additions and 8 deletions

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for per-session workspace bootstrap caching.
* Verifies reuse, refresh, pruning, and explicit cache clears.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { WorkspaceBootstrapFile } from "./workspace.js";

View File

@@ -1,3 +1,8 @@
/**
* Per-session workspace bootstrap snapshot cache.
* Reuses unchanged bootstrap file arrays while refreshing each turn so edits
* become visible to long-lived agent sessions.
*/
import { loadWorkspaceBootstrapFiles, type WorkspaceBootstrapFile } from "./workspace.js";
type BootstrapSnapshot = {
@@ -38,6 +43,7 @@ function pruneOldestBootstrapSnapshots(): void {
}
}
/** Load bootstrap files for a session, reusing the prior snapshot when content is unchanged. */
export async function getOrLoadBootstrapFiles(params: {
workspaceDir: string;
sessionKey: string;
@@ -62,18 +68,22 @@ export async function getOrLoadBootstrapFiles(params: {
return files;
}
/** Test helper exposing the bounded snapshot cache size. */
export function getBootstrapSnapshotCacheSizeForTest(): number {
return cache.size;
}
/** Test helper for asserting one session snapshot is cached. */
export function hasBootstrapSnapshotForTest(sessionKey: string): boolean {
return cache.has(sessionKey);
}
/** Drop one cached bootstrap snapshot. */
export function clearBootstrapSnapshot(sessionKey: string): void {
cache.delete(sessionKey);
}
/** Clear bootstrap state when a visible session rolls over to a new backing session. */
export function clearBootstrapSnapshotOnSessionRollover(params: {
sessionKey?: string;
previousSessionId?: string;
@@ -85,6 +95,7 @@ export function clearBootstrapSnapshotOnSessionRollover(params: {
clearBootstrapSnapshot(params.sessionKey);
}
/** Clear all cached bootstrap snapshots. */
export function clearAllBootstrapSnapshots(): void {
cache.clear();
}

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for CLI session persistence helpers.
* Verifies provider-keyed bindings, legacy Claude state, and reuse invalidation.
*/
import { describe, expect, it } from "vitest";
import type { SessionEntry } from "../config/sessions.js";
import {

View File

@@ -1,10 +1,13 @@
/**
* CLI session persistence helpers.
* Keeps provider-keyed session bindings, reuse fingerprints, and legacy
* Claude CLI state in one normalized session-store contract.
*/
import crypto from "node:crypto";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { CliSessionBinding, SessionEntry } from "../config/sessions.js";
import { normalizeProviderId } from "./model-selection.js";
// CLI-backed agents persist reusable provider session IDs in the session store.
// These helpers keep legacy Claude-only state and provider-keyed bindings aligned.
const CLAUDE_CLI_BACKEND_ID = "claude-cli";
/** Hash CLI session-sensitive text so reuse checks can compare stable fingerprints. */

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for IDENTITY.md parsing and merging.
* Ensures placeholders are ignored and rich identity fields stay stable.
*/
import { describe, expect, it } from "vitest";
import { mergeIdentityMarkdownContent, parseIdentityMarkdown } from "./identity-file.js";

View File

@@ -1,10 +1,14 @@
/**
* IDENTITY.md parsing and writing support.
* The parser accepts human-authored markdown, while the writer only updates
* stable rich identity fields.
*/
import fs from "node:fs";
import path from "node:path";
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
import { DEFAULT_IDENTITY_FILENAME } from "./workspace.js";
// IDENTITY.md parsing/writing support. The parser accepts human-authored
// markdown, while the writer only updates stable rich identity fields.
/** Parsed rich identity values from a workspace `IDENTITY.md` file. */
export type AgentIdentityFile = {
name?: string;
emoji?: string;
@@ -52,6 +56,7 @@ function isIdentityPlaceholder(value: string): boolean {
return IDENTITY_PLACEHOLDER_VALUES.has(normalized);
}
/** Parse rich identity fields from human-authored markdown content. */
export function parseIdentityMarkdown(content: string): AgentIdentityFile {
const identity: AgentIdentityFile = {};
const lines = content.split(/\r?\n/);

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for identity-driven acknowledgement reactions.
* Confirms account, channel, global, identity, and explicit-empty precedence.
*/
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { resolveAckReaction } from "./identity.js";

View File

@@ -1,9 +1,15 @@
/**
* Agent identity and message-prefix resolution.
* Applies account, channel, global, and per-agent precedence for reactions,
* prefixes, and human-delay settings.
*/
import type { HumanDelayConfig, IdentityConfig } from "../config/types.base.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveAgentConfig } from "./agent-scope.js";
const DEFAULT_ACK_REACTION = "👀";
/** Resolve the configured identity block for one agent. */
export function resolveAgentIdentity(
cfg: OpenClawConfig,
agentId: string,
@@ -11,6 +17,7 @@ export function resolveAgentIdentity(
return resolveAgentConfig(cfg, agentId)?.identity;
}
/** Resolve the acknowledgement reaction using account, channel, global, then identity fallback. */
export function resolveAckReaction(
cfg: OpenClawConfig,
agentId: string,
@@ -46,6 +53,7 @@ export function resolveAckReaction(
return emoji || DEFAULT_ACK_REACTION;
}
/** Build the automatic `[name]` prefix for an agent identity. */
export function resolveIdentityNamePrefix(
cfg: OpenClawConfig,
agentId: string,
@@ -57,6 +65,7 @@ export function resolveIdentityNamePrefix(
return `[${name}]`;
}
/** Resolve the outbound message prefix, preserving explicit empty prefixes. */
export function resolveMessagePrefix(
cfg: OpenClawConfig,
agentId: string,
@@ -87,6 +96,7 @@ function getChannelConfig(
: undefined;
}
/** Resolve the optional response prefix, expanding `auto` to the identity name prefix. */
export function resolveResponsePrefix(
cfg: OpenClawConfig,
agentId: string,
@@ -128,6 +138,7 @@ export function resolveResponsePrefix(
return undefined;
}
/** Resolve message and response prefix values together for channel delivery. */
export function resolveEffectiveMessagesConfig(
cfg: OpenClawConfig,
agentId: string,
@@ -150,6 +161,7 @@ export function resolveEffectiveMessagesConfig(
};
}
/** Resolve per-agent human-delay settings over global agent defaults. */
export function resolveHumanDelayConfig(
cfg: OpenClawConfig,
agentId: string,

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for model catalog browsing.
* Verifies filtered catalog output and pending load behavior.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for model catalog visibility filtering.
* Keeps provider/model allow and hide rules aligned with catalog row metadata.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveVisibleModelCatalog } from "./model-catalog-visibility.js";

View File

@@ -1,7 +1,11 @@
/**
* Shared model catalog row types.
* Used by discovery, browsing, visibility, and provider-auth code so renderers
* and filters agree on stable model metadata.
*/
import type { ModelApi, ModelCompatConfig, ModelMediaInputConfig } from "../config/types.models.js";
// Public catalog row shape shared by browse/search/provider-auth code. Keep this
// narrow: fields here are the stable model facts consumers can render or filter.
/** Input modalities a catalog entry can advertise. */
export type ModelInputType = "text" | "image" | "audio" | "video" | "document";
/** Normalized model metadata exposed by the agent model catalog. */

View File

@@ -1,3 +1,8 @@
/**
* Shared context resolvers for model discovery.
* Keeps callers from reaching into runtime config or plugin metadata snapshot
* plumbing directly.
*/
import { getRuntimeConfig } from "../config/config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
@@ -5,8 +10,6 @@ import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapsh
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js";
import type { PluginModelCatalogMetadataSnapshot } from "./plugin-model-catalog.js";
// Shared context resolvers for model discovery. They keep callers from reaching
// into runtime config or plugin metadata snapshot plumbing directly.
/** Resolve the workspace directory model discovery should use for agent scope. */
export function resolveModelWorkspaceDir(
cfg: OpenClawConfig | undefined,

View File

@@ -1,3 +1,8 @@
/**
* Terminal device-status-report helpers.
* Intercepts cursor-position requests from PTY output and generates compact
* responses when a real terminal cannot answer them.
*/
const ESC = String.fromCharCode(0x1b);
const DSR_PATTERN = new RegExp(`${ESC}\\[\\??6n`, "g");

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for PTY key encoding and DSR stripping.
* Protects terminal control bytes used by process send-keys and PTY sessions.
*/
import { expect, test } from "vitest";
import { buildCursorPositionResponse, stripDsrRequests } from "./pty-dsr.js";
import {

View File

@@ -1,3 +1,7 @@
/**
* Regression coverage for gateway-backed agent run waiting.
* Exercises timeout normalization, reply snapshots, and dynamic drain loops.
*/
import {
addTimerTimeoutGraceMs,
MAX_DATE_TIMESTAMP_MS,

View File

@@ -1,3 +1,8 @@
/**
* Gateway-backed agent run wait helpers.
* Normalizes run wait responses, reads the latest assistant reply, and drains
* pending run sets for tools that need synchronous completion semantics.
*/
import {
addTimerTimeoutGraceMs,
asDateTimestampMs,
@@ -44,11 +49,13 @@ function resolveRunWaitDeadlineAtMs(params: { deadlineAtMs?: number; timeoutMs?:
);
}
/** Latest assistant reply plus a stable fingerprint for baseline comparisons. */
export type AssistantReplySnapshot = {
text?: string;
fingerprint?: string;
};
/** Normalized terminal or pending state returned by `agent.wait`. */
export type AgentWaitResult = {
status: "ok" | "timeout" | "error" | "pending";
error?: string;
@@ -62,6 +69,7 @@ export type AgentWaitResult = {
providerStarted?: boolean;
};
/** Summary returned after waiting for a dynamic set of pending runs to drain. */
export type AgentRunsDrainResult = {
timedOut: boolean;
pendingRunIds: string[];
@@ -128,6 +136,7 @@ const RECOVERABLE_AGENT_WAIT_ERROR_PATTERNS: readonly RegExp[] = [
/\b(ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|EHOSTUNREACH|ENETUNREACH)\b/i,
];
/** Return true for transient gateway/transport failures that callers may retry. */
export function isRecoverableAgentWaitError(error: string | undefined): boolean {
const message = error?.trim();
if (!message) {
@@ -175,6 +184,7 @@ function resolveLatestAssistantReplySnapshot(messages: unknown[]): AssistantRepl
return {};
}
/** Read the latest non-tool assistant message for a session. */
export async function readLatestAssistantReplySnapshot(params: {
sessionKey: string;
limit?: number;
@@ -191,6 +201,7 @@ export async function readLatestAssistantReplySnapshot(params: {
);
}
/** Read only the latest assistant text for call sites that do not need fingerprints. */
export async function readLatestAssistantReply(params: {
sessionKey: string;
limit?: number;
@@ -205,6 +216,7 @@ export async function readLatestAssistantReply(params: {
).text;
}
/** Wait for one agent run through the gateway and normalize timeout/error states. */
export async function waitForAgentRun(params: {
runId: string;
timeoutMs: number;
@@ -239,6 +251,7 @@ export async function waitForAgentRun(params: {
}
}
/** Wait for a run and return a reply only when it differs from the supplied baseline. */
export async function waitForAgentRunAndReadUpdatedAssistantReply(params: {
runId: string;
sessionKey: string;
@@ -272,6 +285,7 @@ export async function waitForAgentRunAndReadUpdatedAssistantReply(params: {
};
}
/** Wait until the current and newly spawned pending run IDs are drained or timed out. */
export async function waitForAgentRunsToDrain(params: {
getPendingRunIds: () => Iterable<string>;
initialPendingRunIds?: Iterable<string>;
@@ -307,6 +321,7 @@ export async function waitForAgentRunsToDrain(params: {
};
}
/** Test-only dependency injection for gateway calls. */
export const testing = {
setDepsForTest(overrides?: Partial<{ callGateway: GatewayCaller }>) {
runWaitDeps = overrides

View File

@@ -1,3 +1,7 @@
/**
* Integration coverage for workspace bootstrap cache reads.
* Uses temp workspaces to verify real file loading through the cache layer.
*/
import fs from "node:fs/promises";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";