feat: split agent message ingress surfaces

This commit is contained in:
Shakker 2026-05-01 16:23:11 +01:00
parent 82ff7591be
commit 2254df72f9
No known key found for this signature in database
36 changed files with 882 additions and 32 deletions

View File

@ -2,6 +2,6 @@
"id": "agent-cli",
"title": "Agent CLI",
"description": "User-facing OpenClaw agent command invocations that send messages or manage local sessions.",
"commandPatterns": ["agent --local", "agent --session-id"],
"commandPatterns": ["agent --local", "agent --session-id", " -- agent ", "run-concurrent-agent-turns.mjs"],
"processPatterns": ["(^|\\s|/)openclaw\\s+.*\\bagent\\b", "(^|\\s|/)openclaw-agent(\\s|$)"]
}

View File

@ -2,6 +2,6 @@
"id": "dashboard-cli",
"title": "Dashboard CLI",
"description": "OpenClaw dashboard command paths that produce or validate dashboard URLs and websocket configuration.",
"commandPatterns": ["dashboard", "dashboard --no-open"],
"processPatterns": ["openclaw.*dashboard"]
"commandPatterns": ["dashboard", "dashboard --no-open", "run-dashboard-session-send-turn.mjs", "sessions.send"],
"processPatterns": ["openclaw.*dashboard", "node\\s+.*run-dashboard-session-send-turn\\.mjs"]
}

View File

@ -0,0 +1,7 @@
{
"id": "openai-compatible-client",
"title": "OpenAI-Compatible Client",
"description": "Kova client process that drives OpenClaw's OpenAI-compatible HTTP API.",
"commandPatterns": ["run-openai-compatible-turn.mjs", "/v1/chat/completions"],
"processPatterns": ["node\\s+.*run-openai-compatible-turn\\.mjs"]
}

View File

@ -2,6 +2,6 @@
"id": "tui-cli",
"title": "TUI CLI",
"description": "OpenClaw terminal UI attachment, rendering, input, and shutdown paths.",
"commandPatterns": ["tui", "support/tui-smoke.mjs"],
"processPatterns": ["openclaw.*tui"]
"commandPatterns": ["tui", "support/tui-smoke.mjs", "run-tui-message-turn.mjs"],
"processPatterns": ["openclaw.*tui", "node\\s+.*run-tui-message-turn\\.mjs"]
}

View File

@ -37,7 +37,7 @@
"openclawSlowestSpanMs": 45000
}
},
"agent-message": {
"agent-cli-local-turn": {
"thresholds": {
"coldAgentTurnMs": 60000,
"warmAgentTurnMs": 15000,
@ -47,6 +47,42 @@
"openclawSlowestSpanMs": 45000
}
},
"agent-gateway-rpc-turn": {
"thresholds": {
"agentTurnMs": 60000,
"preProviderMs": 15000,
"providerFinalMs": 3000,
"agentCleanupMs": 5000,
"openclawSlowestSpanMs": 45000
}
},
"dashboard-session-send-turn": {
"thresholds": {
"agentTurnMs": 60000,
"preProviderMs": 15000,
"providerFinalMs": 3000,
"agentCleanupMs": 5000,
"openclawSlowestSpanMs": 45000
}
},
"tui-message-turn": {
"thresholds": {
"agentTurnMs": 60000,
"preProviderMs": 15000,
"providerFinalMs": 3000,
"agentCleanupMs": 5000,
"openclawSlowestSpanMs": 45000
}
},
"openai-compatible-turn": {
"thresholds": {
"agentTurnMs": 60000,
"preProviderMs": 15000,
"providerFinalMs": 3000,
"agentCleanupMs": 5000,
"openclawSlowestSpanMs": 45000
}
},
"bundled-runtime-deps": {
"thresholds": {
"runtimeDepsStagingMs": 45000,
@ -68,7 +104,7 @@
"id": "openclaw-diagnostic",
"coverage": {
"surfaces": {
"blocking": ["release-runtime-startup", "gateway-performance", "bundled-runtime-deps", "agent-message"]
"blocking": ["release-runtime-startup", "gateway-performance", "bundled-runtime-deps", "agent-cli-local-turn", "agent-gateway-rpc-turn", "dashboard-session-send-turn", "tui-message-turn", "openai-compatible-turn"]
},
"states": {
"blocking": ["fresh", "missing-plugin-index", "many-bundled-plugins", "mock-openai-provider"]
@ -78,18 +114,26 @@
"release-runtime-startup:fresh",
"gateway-performance:many-bundled-plugins",
"bundled-runtime-deps:missing-plugin-index",
"agent-message:mock-openai-provider"
"agent-cli-local-turn:mock-openai-provider",
"agent-gateway-rpc-turn:mock-openai-provider",
"dashboard-session-send-turn:mock-openai-provider",
"tui-message-turn:mock-openai-provider",
"openai-compatible-turn:mock-openai-provider"
]
},
"scenarios": {
"blocking": ["release-runtime-startup", "gateway-performance", "bundled-runtime-deps", "agent-cold-warm-message"]
"blocking": ["release-runtime-startup", "gateway-performance", "bundled-runtime-deps", "agent-cold-warm-message", "agent-gateway-rpc-turn", "dashboard-session-send-turn", "tui-message-turn", "openai-compatible-turn"]
}
},
"blocking": [
{ "scenario": "release-runtime-startup", "state": "fresh" },
{ "scenario": "gateway-performance", "state": "many-bundled-plugins" },
{ "scenario": "bundled-runtime-deps", "state": "missing-plugin-index" },
{ "scenario": "agent-cold-warm-message", "state": "mock-openai-provider" }
{ "scenario": "agent-cold-warm-message", "state": "mock-openai-provider" },
{ "scenario": "agent-gateway-rpc-turn", "state": "mock-openai-provider" },
{ "scenario": "dashboard-session-send-turn", "state": "mock-openai-provider" },
{ "scenario": "tui-message-turn", "state": "mock-openai-provider" },
{ "scenario": "openai-compatible-turn", "state": "mock-openai-provider" }
]
},
"entries": [
@ -112,6 +156,26 @@
"scenario": "agent-cold-warm-message",
"state": "mock-openai-provider",
"timeoutMs": 240000
},
{
"scenario": "agent-gateway-rpc-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "dashboard-session-send-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "tui-message-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "openai-compatible-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
}
]
}

View File

@ -26,6 +26,10 @@
{ "scenario": "provider-models", "state": "model-auth-configured" },
{ "scenario": "provider-models", "state": "model-auth-missing" },
{ "scenario": "agent-cold-warm-message", "state": "mock-openai-provider", "timeoutMs": 180000 },
{ "scenario": "agent-gateway-rpc-turn", "state": "mock-openai-provider", "timeoutMs": 180000 },
{ "scenario": "dashboard-session-send-turn", "state": "mock-openai-provider", "timeoutMs": 180000 },
{ "scenario": "tui-message-turn", "state": "mock-openai-provider", "timeoutMs": 180000 },
{ "scenario": "openai-compatible-turn", "state": "mock-openai-provider", "timeoutMs": 180000 },
{ "scenario": "agent-auth-missing", "state": "agent-auth-missing", "timeoutMs": 180000 },
{ "scenario": "agent-long-session", "state": "mock-openai-provider", "timeoutMs": 360000 },
{ "scenario": "agent-provider-slow", "state": "mock-openai-provider", "timeoutMs": 180000 },

View File

@ -15,6 +15,7 @@
"doctor-cli": { "peakRssMb": 700, "maxCpuPercent": 300 },
"tui-cli": { "peakRssMb": 650, "maxCpuPercent": 250 },
"dashboard-cli": { "peakRssMb": 650, "maxCpuPercent": 250 },
"openai-compatible-client": { "peakRssMb": 650, "maxCpuPercent": 250 },
"mcp-runtime": { "peakRssMb": 500, "maxCpuPercent": 200 },
"browser-sidecar": { "peakRssMb": 700, "maxCpuPercent": 250 },
"mock-provider": { "peakRssMb": 300, "maxCpuPercent": 150 }
@ -39,7 +40,7 @@
"pluginLoadFailures": 0
}
},
"agent-message": {
"agent-cli-local-turn": {
"thresholds": {
"coldAgentTurnMs": 45000,
"warmAgentTurnMs": 15000,
@ -50,6 +51,38 @@
"agentProcessLeaks": 0
}
},
"agent-gateway-rpc-turn": {
"thresholds": {
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"agentProcessLeaks": 0
}
},
"dashboard-session-send-turn": {
"thresholds": {
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"agentProcessLeaks": 0
}
},
"tui-message-turn": {
"thresholds": {
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"agentProcessLeaks": 0
}
},
"openai-compatible-turn": {
"thresholds": {
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"agentProcessLeaks": 0
}
},
"bundled-runtime-deps": {
"thresholds": {
"runtimeDepsStagingMs": 30000,
@ -159,7 +192,11 @@
"plugin-lifecycle:plugin-index",
"plugin-lifecycle:external-plugin",
"gateway-performance:many-bundled-plugins",
"agent-message:mock-openai-provider",
"agent-cli-local-turn:mock-openai-provider",
"agent-gateway-rpc-turn:mock-openai-provider",
"dashboard-session-send-turn:mock-openai-provider",
"tui-message-turn:mock-openai-provider",
"openai-compatible-turn:mock-openai-provider",
"provider-models:model-auth-missing",
"dashboard:fresh",
"tui:fresh"
@ -189,7 +226,11 @@
"plugin-missing-runtime-deps",
"bundled-plugin-startup",
"provider-models",
"agent-message",
"agent-cli-local-turn",
"agent-gateway-rpc-turn",
"dashboard-session-send-turn",
"tui-message-turn",
"openai-compatible-turn",
"dashboard",
"tui",
"gateway-performance"
@ -206,6 +247,11 @@
"bundled-runtime-deps",
"plugin-lifecycle",
"provider-models",
"agent-cold-warm-message",
"agent-gateway-rpc-turn",
"dashboard-session-send-turn",
"tui-message-turn",
"openai-compatible-turn",
"dashboard-readiness",
"tui-responsiveness",
"gateway-performance"
@ -237,6 +283,10 @@
{ "scenario": "plugin-lifecycle", "state": "external-plugin" },
{ "scenario": "provider-models", "state": "model-auth-missing" },
{ "scenario": "agent-cold-warm-message", "state": "mock-openai-provider" },
{ "scenario": "agent-gateway-rpc-turn", "state": "mock-openai-provider" },
{ "scenario": "dashboard-session-send-turn", "state": "mock-openai-provider" },
{ "scenario": "tui-message-turn", "state": "mock-openai-provider" },
{ "scenario": "openai-compatible-turn", "state": "mock-openai-provider" },
{ "scenario": "dashboard-readiness", "state": "fresh" },
{ "scenario": "tui-responsiveness", "state": "fresh" },
{ "scenario": "gateway-performance", "state": "many-bundled-plugins" }
@ -333,6 +383,26 @@
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "agent-gateway-rpc-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "dashboard-session-send-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "tui-message-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "openai-compatible-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "agent-provider-slow",
"state": "mock-openai-provider",

View File

@ -31,6 +31,21 @@
"scenario": "agent-cold-warm-message",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "agent-gateway-rpc-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "dashboard-session-send-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "openai-compatible-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
}
]
}

View File

@ -38,6 +38,16 @@
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "agent-gateway-rpc-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "dashboard-session-send-turn",
"state": "mock-openai-provider",
"timeoutMs": 180000
},
{
"scenario": "agent-long-session",
"state": "mock-openai-provider",

View File

@ -1,6 +1,6 @@
{
"id": "agent-auth-missing",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Auth Missing",
"objective": "Prove OpenClaw fails a local agent turn clearly when model auth is missing, keeps the gateway responsive, and leaves no leaked child processes.",
"tags": ["agent", "message", "auth", "failure", "containment"],

View File

@ -1,8 +1,8 @@
{
"id": "agent-cold-warm-message",
"surface": "agent-message",
"title": "Agent Cold/Warm Message",
"objective": "Send cold and warm simple messages through OpenClaw's real local agent path, verify mock-provider responses, and attribute latency before, during, and after provider work.",
"surface": "agent-cli-local-turn",
"title": "Agent CLI Local Cold/Warm Message",
"objective": "Send cold and warm simple messages through `openclaw agent --local`, verify mock-provider responses, and attribute latency before, during, and after provider work.",
"tags": ["agent", "message", "latency", "providers", "gateway", "cold-warm"],
"timeoutMs": 240000,
"agent": {

View File

@ -0,0 +1,47 @@
{
"id": "agent-gateway-rpc-turn",
"surface": "agent-gateway-rpc-turn",
"title": "Agent Gateway RPC Turn",
"objective": "Send a simple user message through `openclaw agent` without `--local`, forcing the CLI to cross the Gateway agent RPC boundary and prove final response, provider timing, gateway health, and process containment.",
"tags": ["agent", "message", "gateway", "rpc", "providers"],
"timeoutMs": 180000,
"agent": {
"expectedText": "KOVA_AGENT_OK"
},
"thresholds": {
"gatewayReadyMs": 30000,
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"preProviderDominanceRatio": 0.8,
"statusMs": 10000,
"peakRssMb": 900,
"missingDependencyErrors": 0,
"pluginLoadFailures": 0
},
"phases": [
{
"id": "provision",
"title": "Provision Gateway Env",
"intent": "Start a disposable OpenClaw gateway before sending the Gateway RPC agent turn.",
"commands": ["ocm start {env} {startSelector} --json"],
"evidence": ["gateway port", "runtime binding", "startup readiness"]
},
{
"id": "gateway-agent-turn",
"title": "Gateway Agent Turn",
"intent": "Send a user message through the Gateway-backed OpenClaw agent CLI path.",
"commands": [
"ocm @{env} -- agent --agent main --session-id kova-agent-gateway-rpc --message 'Reply with exact ASCII text KOVA_AGENT_OK only.' --thinking off --timeout 120 --json"
],
"evidence": ["command duration", "final assistant text", "mock provider request timing", "gateway health after turn", "role resource samples"]
},
{
"id": "post-agent-health",
"title": "Post-Agent Gateway Health",
"intent": "Verify the gateway remains responsive after the Gateway RPC agent turn.",
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
"evidence": ["gateway status", "provider logs", "plugin errors", "memory after agent turn"]
}
]
}

View File

@ -1,6 +1,6 @@
{
"id": "agent-long-session",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Long Session",
"objective": "Send repeated simple messages through one OpenClaw session to catch latency drift, provider routing drift, resource growth, health degradation, and child-process leaks during normal assistant use.",
"tags": ["agent", "message", "latency", "providers", "soak", "long-session"],

View File

@ -1,6 +1,6 @@
{
"id": "agent-provider-concurrent",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Provider Concurrent Pressure",
"objective": "Prove OpenClaw can process multiple overlapping local agent turns through the provider path, keep the gateway healthy, return correct responses, and leave no leaked child processes.",
"tags": ["agent", "message", "provider-failure", "concurrency", "containment"],

View File

@ -1,6 +1,6 @@
{
"id": "agent-provider-malformed",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Provider Malformed",
"objective": "Prove OpenClaw handles malformed provider responses as contained agent failures without taking down the gateway.",
"tags": ["agent", "message", "provider-failure", "malformed", "containment"],

View File

@ -1,6 +1,6 @@
{
"id": "agent-provider-recovery",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Provider Recovery",
"objective": "Prove a transient provider failure is contained and provider recovery produces a normal assistant response in the same OpenClaw env/session lifecycle.",
"tags": ["agent", "message", "provider-failure", "recovery", "containment"],

View File

@ -1,6 +1,6 @@
{
"id": "agent-provider-slow",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Provider Slow",
"objective": "Prove Kova can attribute a slow assistant turn to provider latency while confirming OpenClaw still returns a valid response and keeps the gateway healthy.",
"tags": ["agent", "message", "provider-failure", "latency", "containment"],

View File

@ -1,6 +1,6 @@
{
"id": "agent-provider-streaming-stall",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Provider Streaming Stall",
"objective": "Prove OpenClaw contains a provider stream that starts but stalls, keeps the gateway responsive, and leaves no leaked agent/plugin child processes.",
"tags": ["agent", "message", "provider-failure", "streaming-stall", "containment"],

View File

@ -1,6 +1,6 @@
{
"id": "agent-provider-timeout",
"surface": "agent-message",
"surface": "agent-cli-local-turn",
"title": "Agent Provider Timeout",
"objective": "Prove OpenClaw surfaces provider timeout failure clearly, keeps the gateway healthy, and leaves no retained child process state after a failed agent turn.",
"tags": ["agent", "message", "provider-failure", "timeout", "containment"],

View File

@ -0,0 +1,47 @@
{
"id": "dashboard-session-send-turn",
"surface": "dashboard-session-send-turn",
"title": "Dashboard Session Send Turn",
"objective": "Create a dashboard session, send a user message through Gateway `sessions.send`, and wait for final assistant text in chat history.",
"tags": ["agent", "message", "dashboard", "sessions", "gateway", "providers"],
"timeoutMs": 180000,
"agent": {
"expectedText": "KOVA_AGENT_OK"
},
"thresholds": {
"gatewayReadyMs": 30000,
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"preProviderDominanceRatio": 0.8,
"statusMs": 10000,
"peakRssMb": 900,
"missingDependencyErrors": 0,
"pluginLoadFailures": 0
},
"phases": [
{
"id": "provision",
"title": "Provision Dashboard Env",
"intent": "Start a disposable OpenClaw gateway before sending a dashboard session message.",
"commands": ["ocm start {env} {startSelector} --json"],
"evidence": ["gateway port", "runtime binding", "startup readiness"]
},
{
"id": "dashboard-session-turn",
"title": "Dashboard Session Message",
"intent": "Exercise Gateway `sessions.send` and verify the final assistant response is present in chat history.",
"commands": [
"ocm @{env} -- node {kovaRoot}/support/run-dashboard-session-send-turn.mjs --session-key kova-dashboard-session-send --message 'Reply with exact ASCII text KOVA_AGENT_OK only.' --expected-text KOVA_AGENT_OK --timeout 120000"
],
"evidence": ["sessions.send command duration", "chat history final assistant text", "mock provider request timing", "gateway health after turn", "role resource samples"]
},
{
"id": "post-dashboard-health",
"title": "Post-Dashboard Gateway Health",
"intent": "Verify the gateway remains responsive after the dashboard-style message turn.",
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
"evidence": ["gateway status", "provider logs", "plugin errors", "memory after dashboard turn"]
}
]
}

View File

@ -0,0 +1,47 @@
{
"id": "openai-compatible-turn",
"surface": "openai-compatible-turn",
"title": "OpenAI-Compatible Turn",
"objective": "Send a chat-completions request to OpenClaw's OpenAI-compatible HTTP endpoint and verify the final assistant response, provider timing, auth handling, and gateway health.",
"tags": ["agent", "message", "openai-compatible", "http", "providers"],
"timeoutMs": 180000,
"agent": {
"expectedText": "KOVA_AGENT_OK"
},
"thresholds": {
"gatewayReadyMs": 30000,
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"preProviderDominanceRatio": 0.8,
"statusMs": 10000,
"peakRssMb": 900,
"missingDependencyErrors": 0,
"pluginLoadFailures": 0
},
"phases": [
{
"id": "provision",
"title": "Provision HTTP Env",
"intent": "Start a disposable OpenClaw gateway before sending an OpenAI-compatible HTTP request.",
"commands": ["ocm start {env} {startSelector} --json"],
"evidence": ["gateway port", "runtime binding", "startup readiness"]
},
{
"id": "openai-compatible-turn",
"title": "OpenAI-Compatible Message",
"intent": "Exercise the `/v1/chat/completions` user API and verify final assistant output.",
"commands": [
"ocm @{env} -- node {kovaRoot}/support/run-openai-compatible-turn.mjs --model openai/gpt-5.5 --message 'Reply with exact ASCII text KOVA_AGENT_OK only.' --expected-text KOVA_AGENT_OK --timeout 120000"
],
"evidence": ["HTTP status", "final assistant text", "mock provider request timing", "gateway health after turn", "role resource samples"]
},
{
"id": "post-http-health",
"title": "Post-HTTP Gateway Health",
"intent": "Verify the gateway remains responsive after the OpenAI-compatible request.",
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
"evidence": ["gateway status", "provider logs", "plugin errors", "memory after HTTP turn"]
}
]
}

View File

@ -0,0 +1,47 @@
{
"id": "tui-message-turn",
"surface": "tui-message-turn",
"title": "TUI Message Turn",
"objective": "Launch the TUI, send a real user message through stdin, and require the expected assistant response to appear in the TUI output.",
"tags": ["agent", "message", "tui", "input", "providers"],
"timeoutMs": 180000,
"agent": {
"expectedText": "KOVA_AGENT_OK"
},
"thresholds": {
"gatewayReadyMs": 30000,
"agentTurnMs": 45000,
"preProviderMs": 10000,
"providerFinalMs": 3000,
"preProviderDominanceRatio": 0.8,
"statusMs": 10000,
"peakRssMb": 900,
"missingDependencyErrors": 0,
"pluginLoadFailures": 0
},
"phases": [
{
"id": "provision",
"title": "Provision TUI Env",
"intent": "Start a disposable OpenClaw gateway before attaching the TUI.",
"commands": ["ocm start {env} {startSelector} --json"],
"evidence": ["gateway port", "runtime binding", "startup readiness"]
},
{
"id": "tui-message-turn",
"title": "TUI Message",
"intent": "Exercise the user-visible terminal input path and verify final assistant output.",
"commands": [
"node {kovaRoot}/support/run-tui-message-turn.mjs --env {env} --session kova-tui-message --message 'Reply with exact ASCII text KOVA_AGENT_OK only.' --expected-text KOVA_AGENT_OK --timeout 120000"
],
"evidence": ["TUI input accepted", "final assistant text rendered", "mock provider request timing", "gateway health after turn", "role resource samples"]
},
{
"id": "post-tui-health",
"title": "Post-TUI Gateway Health",
"intent": "Verify the gateway remains responsive after the TUI message turn.",
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
"evidence": ["gateway status", "provider logs", "plugin errors", "memory after TUI turn"]
}
]
}

View File

@ -1625,6 +1625,18 @@ function agentTurnLabel(phaseId, index) {
if (phaseId?.includes("warm")) {
return "warm";
}
if (phaseId?.includes("gateway")) {
return "gateway-rpc";
}
if (phaseId?.includes("dashboard")) {
return "dashboard-session";
}
if (phaseId?.includes("tui")) {
return "tui";
}
if (phaseId?.includes("openai")) {
return "openai-compatible";
}
return `turn-${index}`;
}
@ -2779,7 +2791,10 @@ function countDiagnosticMetric(record, key) {
function isAgentMessageCommand(command) {
return (command.includes(" -- agent ") && command.includes("--message")) ||
command.includes("run-concurrent-agent-turns.mjs");
command.includes("run-concurrent-agent-turns.mjs") ||
command.includes("run-dashboard-session-send-turn.mjs") ||
command.includes("run-tui-message-turn.mjs") ||
command.includes("run-openai-compatible-turn.mjs");
}
function extractAgentResponse(result) {

View File

@ -632,7 +632,10 @@ async function runScenarioCommand(command, context, envName, artifactDir, phaseI
function isAgentMessageCommand(command) {
return (command.includes(" -- agent ") && command.includes("--message")) ||
command.includes("run-concurrent-agent-turns.mjs");
command.includes("run-concurrent-agent-turns.mjs") ||
command.includes("run-dashboard-session-send-turn.mjs") ||
command.includes("run-tui-message-turn.mjs") ||
command.includes("run-openai-compatible-turn.mjs");
}
function agentLeakRoles() {

View File

@ -182,6 +182,22 @@ export async function runSelfCheck(flags = {}) {
throw new Error(`missing auth override should not inject auth phases: ${phaseIds.join(", ")}`);
}
}));
for (const item of [
["agent-gateway-rpc-turn", "agent-gateway-rpc-turn", "ocm @"],
["dashboard-session-send-turn", "dashboard-session-send-turn", "run-dashboard-session-send-turn.mjs"],
["tui-message-turn", "tui-message-turn", "run-tui-message-turn.mjs"],
["openai-compatible-turn", "openai-compatible-turn", "run-openai-compatible-turn.mjs"]
]) {
const [scenarioId, surfaceId, expectedCommand] = item;
checks.push(await jsonCommandCheck(`message-ingress-${scenarioId}-dry-run-json`, `node bin/kova.mjs run --target runtime:stable --scenario ${scenarioId} --state mock-openai-provider --report-dir ${quoteShell(tmp)} --json`, async (data) => {
const report = JSON.parse(await readFile(data.jsonPath, "utf8"));
const record = report.records?.[0];
assertEqual(record?.surface, surfaceId, `${scenarioId} surface`);
assertEqual(record?.auth?.mode, "mock", `${scenarioId} mock auth mode`);
const commands = record?.phases?.flatMap((phase) => phase.commands ?? []) ?? [];
assertEqual(commands.some((command) => command.includes(expectedCommand)), true, `${scenarioId} ingress command`);
}));
}
checks.push(await jsonCommandCheck("run-profiling-dry-run-json", `node bin/kova.mjs run --target runtime:stable --scenario fresh-install --node-profile --report-dir ${quoteShell(tmp)} --json`, async (data) => {
const report = JSON.parse(await readFile(data.jsonPath, "utf8"));
assertEqual(report.records?.[0]?.profiling?.enabled, true, "profiling marker");
@ -2985,7 +3001,7 @@ function syntheticCompareReport({ runId, target, timelineAvailable, preProviderM
summary: { statuses: { PASS: 1 } },
records: [{
scenario: "agent-cold-warm-message",
surface: "agent-message",
surface: "agent-cli-local-turn",
state: { id: "mock-openai-provider" },
status: "PASS",
measurements: {

View File

@ -20,7 +20,7 @@
"reason": "This state intentionally bypasses Kova's default mock provider auth to test missing model credentials."
},
"compatibleSurfaces": [
"agent-message"
"agent-cli-local-turn"
],
"incompatibleSurfaces": [],
"riskArea": "agent-provider-auth",

View File

@ -20,7 +20,11 @@
"agent-state"
],
"compatibleSurfaces": [
"agent-message"
"agent-cli-local-turn",
"agent-gateway-rpc-turn",
"dashboard-session-send-turn",
"tui-message-turn",
"openai-compatible-turn"
],
"incompatibleSurfaces": [],
"riskArea": "agent-provider-latency",

View File

@ -0,0 +1,77 @@
import { pathToFileURL } from "node:url";
import path from "node:path";
export async function importOpenClawDistModule(relativePath) {
const packageRoot = process.cwd();
const absolutePath = path.join(packageRoot, "dist", ...relativePath.split("/"));
try {
return await import(pathToFileURL(absolutePath).href);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(
`failed to import OpenClaw runtime module ${relativePath} from ${packageRoot}: ${message}. ` +
"Kova user-message scenarios require a built/release-shaped OpenClaw runtime."
);
}
}
export function parseSupportArgs(argv) {
const parsed = {};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (!arg.startsWith("--")) {
throw new Error(`unexpected argument: ${arg}`);
}
const key = arg.slice(2);
const value = argv[index + 1];
if (!value || value.startsWith("--")) {
throw new Error(`${arg} requires a value`);
}
parsed[key] = value;
index += 1;
}
return parsed;
}
export function readTimeoutMs(value, fallbackMs) {
if (value === undefined) {
return fallbackMs;
}
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0) {
throw new Error(`invalid timeout: ${value}`);
}
return parsed;
}
export function extractText(value) {
if (typeof value === "string") {
return value;
}
if (!value || typeof value !== "object") {
return "";
}
if (Array.isArray(value)) {
return value.map(extractText).filter(Boolean).join("\n");
}
for (const key of ["finalAssistantVisibleText", "finalAssistantRawText", "text", "content", "reply"]) {
if (typeof value[key] === "string") {
return value[key];
}
}
return Object.values(value).map(extractText).filter(Boolean).join("\n");
}
export async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
export function finishJson(payload) {
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
}
export function failJson(error, extra = {}) {
const message = error instanceof Error ? error.message : String(error);
process.stdout.write(`${JSON.stringify({ ok: false, error: message, ...extra }, null, 2)}\n`);
process.exit(1);
}

View File

@ -0,0 +1,111 @@
#!/usr/bin/env node
import { randomUUID } from "node:crypto";
import {
extractText,
failJson,
finishJson,
importOpenClawDistModule,
parseSupportArgs,
readTimeoutMs,
sleep
} from "./openclaw-runtime.mjs";
const startedAtEpochMs = Date.now();
try {
const args = parseSupportArgs(process.argv.slice(2));
const message = args.message ?? "Reply with exact ASCII text KOVA_AGENT_OK only.";
const expectedText = args["expected-text"] ?? "KOVA_AGENT_OK";
const timeoutMs = readTimeoutMs(args.timeout, 120000);
const sessionKey = args["session-key"] ?? `kova-dashboard-${randomUUID()}`;
const { callGateway } = await importOpenClawDistModule("gateway/call.js");
const created = await callGateway({
method: "sessions.create",
params: {
agentId: "main",
key: sessionKey,
label: "Kova Dashboard Session Send"
},
timeoutMs: Math.min(timeoutMs, 30000)
});
const canonicalKey = created?.key ?? sessionKey;
const sendStartedAtEpochMs = Date.now();
const sent = await callGateway({
method: "sessions.send",
params: {
key: canonicalKey,
message,
thinking: "off",
timeoutMs,
idempotencyKey: `kova-dashboard-${randomUUID()}`
},
timeoutMs: Math.min(timeoutMs, 30000)
});
const runId = typeof sent?.runId === "string" ? sent.runId : null;
const history = await waitForAssistantText({
callGateway,
sessionKey: canonicalKey,
expectedText,
timeoutMs,
minAssistantCount: 1
});
finishJson({
ok: true,
surface: "dashboard-session-send-turn",
method: "sessions.send",
sessionKey: canonicalKey,
runId,
startedAtEpochMs,
sendStartedAtEpochMs,
finishedAtEpochMs: Date.now(),
finalAssistantVisibleText: history.matchedAssistantText,
finalAssistantRawText: history.lastAssistantText,
assistantMessageCount: history.assistantTexts.length,
expectedTextPresent: history.matchedAssistantText.includes(expectedText)
});
} catch (error) {
failJson(error, { surface: "dashboard-session-send-turn", finishedAtEpochMs: Date.now() });
}
async function waitForAssistantText({ callGateway, sessionKey, expectedText, timeoutMs, minAssistantCount }) {
const deadline = Date.now() + timeoutMs;
let lastAssistantText = "";
let assistantTexts = [];
while (Date.now() < deadline) {
const history = await callGateway({
method: "chat.history",
params: { sessionKey, limit: 16 },
timeoutMs: 15000
});
assistantTexts = extractAssistantTexts(history?.messages ?? []);
lastAssistantText = assistantTexts.at(-1) ?? "";
const matchedAssistantText = assistantTexts
.slice(Math.max(0, minAssistantCount - 1))
.find((text) => text.includes(expectedText));
if (matchedAssistantText) {
return { assistantTexts, lastAssistantText, matchedAssistantText };
}
await sleep(500);
}
throw new Error(
`timed out waiting for dashboard assistant text ${JSON.stringify(expectedText)}; last=${JSON.stringify(lastAssistantText)}`
);
}
function extractAssistantTexts(messages) {
if (!Array.isArray(messages)) {
return [];
}
return messages
.filter((message) => {
const role = String(message?.role ?? message?.sender ?? message?.type ?? "").toLowerCase();
return role.includes("assistant") || role.includes("agent");
})
.map((message) => extractText(message))
.map((text) => text.trim())
.filter(Boolean);
}

View File

@ -0,0 +1,80 @@
#!/usr/bin/env node
import {
extractText,
failJson,
finishJson,
importOpenClawDistModule,
parseSupportArgs,
readTimeoutMs
} from "./openclaw-runtime.mjs";
const startedAtEpochMs = Date.now();
try {
const args = parseSupportArgs(process.argv.slice(2));
const message = args.message ?? "Reply with exact ASCII text KOVA_AGENT_OK only.";
const expectedText = args["expected-text"] ?? "KOVA_AGENT_OK";
const timeoutMs = readTimeoutMs(args.timeout, 120000);
const model = args.model ?? "openai/gpt-5.5";
const { getRuntimeConfig } = await importOpenClawDistModule("config/io.js");
const { resolveGatewayPort } = await importOpenClawDistModule("config/paths.js");
const cfg = getRuntimeConfig();
const port = resolveGatewayPort(cfg, process.env);
const token = readGatewayToken(cfg);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(new Error(`OpenAI-compatible request timed out after ${timeoutMs}ms`)), timeoutMs);
const requestStartedAtEpochMs = Date.now();
try {
const response = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: "POST",
headers: {
"content-type": "application/json",
...(token ? { authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify({
model,
messages: [{ role: "user", content: message }],
stream: false
}),
signal: controller.signal
});
const bodyText = await response.text();
let body = {};
try {
body = bodyText ? JSON.parse(bodyText) : {};
} catch {
body = { raw: bodyText };
}
if (!response.ok) {
throw new Error(`OpenAI-compatible HTTP ${response.status}: ${bodyText.slice(0, 1000)}`);
}
const finalText = extractText(body?.choices?.[0]?.message ?? body);
finishJson({
ok: true,
surface: "openai-compatible-turn",
method: "POST /v1/chat/completions",
model,
startedAtEpochMs,
requestStartedAtEpochMs,
finishedAtEpochMs: Date.now(),
status: response.status,
finalAssistantVisibleText: finalText,
finalAssistantRawText: finalText,
expectedTextPresent: finalText.includes(expectedText)
});
} finally {
clearTimeout(timer);
}
} catch (error) {
failJson(error, { surface: "openai-compatible-turn", finishedAtEpochMs: Date.now() });
}
function readGatewayToken(cfg) {
const candidates = [
process.env.OPENCLAW_GATEWAY_TOKEN,
cfg?.gateway?.auth?.token,
cfg?.gateway?.token
];
return candidates.find((value) => typeof value === "string" && value.trim().length > 0)?.trim() ?? "";
}

106
support/run-tui-message-turn.mjs Executable file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { failJson, finishJson, parseSupportArgs, readTimeoutMs } from "./openclaw-runtime.mjs";
const startedAtEpochMs = Date.now();
try {
const args = parseSupportArgs(process.argv.slice(2));
const envName = args.env;
if (!envName) {
throw new Error("--env is required");
}
const message = args.message ?? "Reply with exact ASCII text KOVA_AGENT_OK only.";
const expectedText = args["expected-text"] ?? "KOVA_AGENT_OK";
const timeoutMs = readTimeoutMs(args.timeout, 120000);
const session = args.session ?? "kova-tui-message";
const result = await runTuiTurn({ envName, message, expectedText, timeoutMs, session });
finishJson({
ok: true,
surface: "tui-message-turn",
method: "tui stdin/stdout",
session,
startedAtEpochMs,
inputAcceptedAtEpochMs: result.inputAcceptedAtEpochMs,
finishedAtEpochMs: Date.now(),
finalAssistantVisibleText: result.finalAssistantText,
finalAssistantRawText: result.finalAssistantText,
expectedTextPresent: result.finalAssistantText.includes(expectedText),
outputTail: result.outputTail
});
} catch (error) {
failJson(error, { surface: "tui-message-turn", finishedAtEpochMs: Date.now() });
}
function runTuiTurn({ envName, message, expectedText, timeoutMs, session }) {
return new Promise((resolve, reject) => {
const child = spawn("ocm", [
`@${envName}`,
"--",
"tui",
"--session",
session,
"--history-limit",
"5"
], {
stdio: ["pipe", "pipe", "pipe"],
shell: false
});
let output = "";
let inputSent = false;
let settled = false;
let inputAcceptedAtEpochMs = null;
const timer = setTimeout(() => {
finish(new Error(`TUI turn did not complete within ${timeoutMs}ms`));
}, timeoutMs);
child.stdout.on("data", onData);
child.stderr.on("data", onData);
child.on("error", finish);
child.on("exit", (code, signal) => {
if (!settled) {
finish(new Error(`TUI exited before message turn completed (code=${code}, signal=${signal ?? "none"})`));
}
});
function onData(chunk) {
output += chunk.toString("utf8");
if (!inputSent && /openclaw tui|local ready|agent\s+main|session\s+/i.test(output)) {
inputSent = true;
inputAcceptedAtEpochMs = Date.now();
child.stdin.write(`${message}\n`);
}
if (output.includes(expectedText)) {
finish(null, {
inputAcceptedAtEpochMs,
finalAssistantText: expectedText,
outputTail: output.slice(-4000)
});
}
}
function finish(error, value) {
if (settled) {
return;
}
settled = true;
clearTimeout(timer);
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGINT");
setTimeout(() => {
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
}
}, 1000).unref?.();
}
if (error) {
reject(new Error(`${error.message}; outputTail=${JSON.stringify(output.slice(-4000))}`));
} else {
resolve(value);
}
}
});
}

View File

@ -1,8 +1,8 @@
{
"id": "agent-message",
"title": "Agent Message",
"id": "agent-cli-local-turn",
"title": "Agent CLI Local Turn",
"ownerArea": "agent-runtime",
"description": "Send cold, warm, and repeated local OpenClaw agent messages and verify response latency, provider routing, gateway health, memory, and logs.",
"description": "Send cold, warm, repeated, and failure-mode messages through `openclaw agent --local`, then verify response latency, provider routing, gateway health, memory, and process containment.",
"requiredStates": ["mock-openai-provider"],
"targetKinds": ["npm", "channel", "runtime", "local-build"],
"requiredMetrics": ["agentTurnMs", "agentTurnP95Ms", "agentTurnMaxMs", "coldAgentTurnMs", "warmAgentTurnMs", "agentColdWarmDeltaMs", "coldPreProviderMs", "warmPreProviderMs", "agentPreProviderP95Ms", "agentCleanupMaxMs", "healthP95Ms", "peakRssMb", "providerTimeoutMentions", "pluginLoadFailures"],

View File

@ -0,0 +1,20 @@
{
"id": "agent-gateway-rpc-turn",
"title": "Agent Gateway RPC Turn",
"ownerArea": "gateway-agent-runtime",
"description": "Send messages through `openclaw agent` without `--local`, forcing the CLI to cross the Gateway agent RPC boundary before the agent turn runs.",
"requiredStates": ["mock-openai-provider"],
"targetKinds": ["npm", "channel", "runtime", "local-build"],
"requiredMetrics": ["agentTurnMs", "agentTurnP95Ms", "agentTurnMaxMs", "coldPreProviderMs", "healthP95Ms", "peakRssMb", "pluginLoadFailures"],
"processRoles": ["gateway", "command-tree", "agent-cli", "agent-process", "mock-provider"],
"thresholds": { "agentTurnMs": 45000, "preProviderMs": 10000, "providerFinalMs": 3000, "healthP95Ms": 1000, "peakRssMb": 900 },
"roleThresholds": {
"gateway": { "peakRssMb": 700, "maxCpuPercent": 250 },
"agent-cli": { "peakRssMb": 900, "maxCpuPercent": 300 },
"mock-provider": { "peakRssMb": 250, "maxCpuPercent": 150 }
},
"diagnostics": {
"timelineRequiredForSourceBuild": true,
"expectedSpans": ["agent.turn", "agent.prepare", "models.catalog", "provider.request", "agent.cleanup"]
}
}

View File

@ -0,0 +1,20 @@
{
"id": "dashboard-session-send-turn",
"title": "Dashboard Session Send Turn",
"ownerArea": "gateway-chat-session-runtime",
"description": "Create a dashboard session, call Gateway `sessions.send`, and wait for the assistant response in chat history to validate the same path dashboard users exercise.",
"requiredStates": ["mock-openai-provider"],
"targetKinds": ["npm", "channel", "runtime", "local-build"],
"requiredMetrics": ["agentTurnMs", "agentTurnP95Ms", "coldPreProviderMs", "healthP95Ms", "peakRssMb", "pluginLoadFailures"],
"processRoles": ["gateway", "command-tree", "dashboard-cli", "agent-process", "mock-provider"],
"thresholds": { "agentTurnMs": 45000, "preProviderMs": 10000, "providerFinalMs": 3000, "healthP95Ms": 1000, "peakRssMb": 900 },
"roleThresholds": {
"gateway": { "peakRssMb": 700, "maxCpuPercent": 250 },
"dashboard-cli": { "peakRssMb": 700, "maxCpuPercent": 250 },
"mock-provider": { "peakRssMb": 250, "maxCpuPercent": 150 }
},
"diagnostics": {
"timelineRequiredForSourceBuild": true,
"expectedSpans": ["agent.turn", "agent.prepare", "models.catalog", "provider.request", "agent.cleanup"]
}
}

View File

@ -0,0 +1,20 @@
{
"id": "openai-compatible-turn",
"title": "OpenAI-Compatible Turn",
"ownerArea": "gateway-openai-compatible-runtime",
"description": "POST a user message to the OpenAI-compatible chat completions endpoint and verify the final response, provider timing, auth behavior, and gateway health.",
"requiredStates": ["mock-openai-provider"],
"targetKinds": ["npm", "channel", "runtime", "local-build"],
"requiredMetrics": ["agentTurnMs", "agentTurnP95Ms", "coldPreProviderMs", "healthP95Ms", "peakRssMb", "pluginLoadFailures"],
"processRoles": ["gateway", "command-tree", "openai-compatible-client", "agent-process", "mock-provider"],
"thresholds": { "agentTurnMs": 45000, "preProviderMs": 10000, "providerFinalMs": 3000, "healthP95Ms": 1000, "peakRssMb": 900 },
"roleThresholds": {
"gateway": { "peakRssMb": 700, "maxCpuPercent": 250 },
"openai-compatible-client": { "peakRssMb": 700, "maxCpuPercent": 250 },
"mock-provider": { "peakRssMb": 250, "maxCpuPercent": 150 }
},
"diagnostics": {
"timelineRequiredForSourceBuild": true,
"expectedSpans": ["agent.turn", "agent.prepare", "models.catalog", "provider.request", "agent.cleanup"]
}
}

View File

@ -0,0 +1,20 @@
{
"id": "tui-message-turn",
"title": "TUI Message Turn",
"ownerArea": "tui-agent-runtime",
"description": "Launch the OpenClaw TUI, send a real stdin message, and require visible assistant output so Kova can catch TUI input freezes and delayed user-visible responses.",
"requiredStates": ["mock-openai-provider"],
"targetKinds": ["npm", "channel", "runtime", "local-build"],
"requiredMetrics": ["agentTurnMs", "agentTurnP95Ms", "coldPreProviderMs", "healthP95Ms", "peakRssMb", "pluginLoadFailures"],
"processRoles": ["gateway", "command-tree", "tui-cli", "agent-process", "mock-provider"],
"thresholds": { "agentTurnMs": 45000, "preProviderMs": 10000, "providerFinalMs": 3000, "healthP95Ms": 1000, "peakRssMb": 900 },
"roleThresholds": {
"gateway": { "peakRssMb": 700, "maxCpuPercent": 250 },
"tui-cli": { "peakRssMb": 900, "maxCpuPercent": 300 },
"mock-provider": { "peakRssMb": 250, "maxCpuPercent": 150 }
},
"diagnostics": {
"timelineRequiredForSourceBuild": true,
"expectedSpans": ["agent.turn", "agent.prepare", "models.catalog", "provider.request", "agent.cleanup"]
}
}