mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
fix(mattermost): accept streaming config
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Google Meet: grant Meet media permissions through the Playwright browser context when CDP grants do not affect the attached Chrome page, and report in-call microphone/speaker permission problems instead of marking realtime speech ready.
|
||||
- Control UI/WebChat: collapse duplicate in-flight internal text sends onto the active Gateway run so rapid repeat submits do not start fresh `agent:main:main` dispatches. Fixes #75737. Thanks @dsdsddd1 and @BunsDev.
|
||||
- Mattermost: accept the documented `channels.mattermost.streaming` config and honor `streaming: "off"` by disabling draft preview posts. Thanks @vincentkoc.
|
||||
- Channels/streaming: expose `streaming.progress.label`, `labels`, `maxLines`, and `toolProgress` in bundled channel config metadata so progress draft settings appear in config, docs, and control surfaces. Thanks @vincentkoc.
|
||||
- Channels/streaming: normalize whitespace and case for `streaming.progress.label: "auto"` so progress draft labels keep using the built-in label pool instead of rendering a literal `auto` title. Thanks @vincentkoc.
|
||||
- Gateway/install: prefer supported system Node over nvm/fnm/volta/asdf/mise when regenerating managed gateway services, so `gateway install --force` no longer recreates service definitions that doctor immediately flags as version-manager-backed. Fixes #76339. Thanks @brokemac79.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
056760c0a86627641d8e2993cc0cc987820dc4289c40c67dc8c2c1e8970c1849 config-baseline.json
|
||||
5603f93164f1bed3b39714b813c7597e188321fff07cfbb6980d7198a69da162 config-baseline.json
|
||||
5b5ebd95939d75496597d9858a375e27544812d0f79dc3b4bf87c794ada2ba08 config-baseline.core.json
|
||||
7b207901b595ad527026b1f357f63a5cd33123a72eeb66bdac24a8f2e8bb1ac8 config-baseline.channel.json
|
||||
c83a29196d34b4aff4849f63ae8850298441c367811d928e1ab2efe787eae520 config-baseline.channel.json
|
||||
055fae0d0067a751dc10125af7421da45633f73519c94c982d02b0c4eb2bdf67 config-baseline.plugin.json
|
||||
|
||||
@@ -78,6 +78,40 @@ const MattermostNetworkSchema = z
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
const MattermostStreamingModeSchema = z.enum(["off", "partial", "block", "progress"]);
|
||||
const MattermostStreamingProgressSchema = z
|
||||
.object({
|
||||
label: z.union([z.string(), z.literal(false)]).optional(),
|
||||
labels: z.array(z.string()).optional(),
|
||||
maxLines: z.number().int().positive().optional(),
|
||||
toolProgress: z.boolean().optional(),
|
||||
})
|
||||
.strict();
|
||||
const MattermostStreamingPreviewSchema = z
|
||||
.object({
|
||||
toolProgress: z.boolean().optional(),
|
||||
})
|
||||
.strict();
|
||||
const MattermostStreamingBlockSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
coalesce: BlockStreamingCoalesceSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
const MattermostStreamingSchema = z.union([
|
||||
MattermostStreamingModeSchema,
|
||||
z.boolean(),
|
||||
z
|
||||
.object({
|
||||
mode: MattermostStreamingModeSchema.optional(),
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
preview: MattermostStreamingPreviewSchema.optional(),
|
||||
progress: MattermostStreamingProgressSchema.optional(),
|
||||
block: MattermostStreamingBlockSchema.optional(),
|
||||
})
|
||||
.strict(),
|
||||
]);
|
||||
|
||||
const MattermostAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
@@ -97,6 +131,7 @@ const MattermostAccountSchemaBase = z
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
textChunkLimit: z.number().int().positive().optional(),
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
streaming: MattermostStreamingSchema.optional(),
|
||||
blockStreaming: z.boolean().optional(),
|
||||
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
||||
replyToMode: z.enum(["off", "first", "all", "batched"]).optional(),
|
||||
|
||||
@@ -29,6 +29,25 @@ describe("MattermostConfigSchema", () => {
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts documented streaming modes and progress config", () => {
|
||||
const result = MattermostConfigSchema.safeParse({
|
||||
streaming: {
|
||||
mode: "progress",
|
||||
progress: {
|
||||
label: "Shelling",
|
||||
maxLines: 4,
|
||||
toolProgress: false,
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
quiet: {
|
||||
streaming: "off",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts groups with requireMention", () => {
|
||||
const result = MattermostConfigSchema.safeParse({
|
||||
groups: {
|
||||
|
||||
@@ -135,4 +135,24 @@ describe("resolveMattermostReplyToMode", () => {
|
||||
callbackPath: "/hooks/work",
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves documented streaming mode from account config", () => {
|
||||
const account = resolveMattermostAccount({
|
||||
cfg: {
|
||||
channels: {
|
||||
mattermost: {
|
||||
streaming: "partial",
|
||||
accounts: {
|
||||
work: {
|
||||
streaming: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "work",
|
||||
});
|
||||
|
||||
expect(account.streamingMode).toBe("off");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
resolveChannelStreamingBlockCoalesce,
|
||||
resolveChannelStreamingBlockEnabled,
|
||||
resolveChannelStreamingChunkMode,
|
||||
resolveChannelPreviewStreamMode,
|
||||
type StreamingMode,
|
||||
} from "openclaw/plugin-sdk/channel-streaming";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "../secret-input.js";
|
||||
@@ -34,6 +36,7 @@ export type ResolvedMattermostAccount = {
|
||||
requireMention?: boolean;
|
||||
textChunkLimit?: number;
|
||||
chunkMode?: MattermostAccountConfig["chunkMode"];
|
||||
streamingMode: StreamingMode;
|
||||
blockStreaming?: boolean;
|
||||
blockStreamingCoalesce?: MattermostAccountConfig["blockStreamingCoalesce"];
|
||||
};
|
||||
@@ -120,6 +123,7 @@ export function resolveMattermostAccount(params: {
|
||||
requireMention,
|
||||
textChunkLimit: merged.textChunkLimit,
|
||||
chunkMode: resolveChannelStreamingChunkMode(merged) ?? merged.chunkMode,
|
||||
streamingMode: resolveChannelPreviewStreamMode(merged, "partial"),
|
||||
blockStreaming: resolveChannelStreamingBlockEnabled(merged) ?? merged.blockStreaming,
|
||||
blockStreamingCoalesce:
|
||||
resolveChannelStreamingBlockCoalesce(merged) ?? merged.blockStreamingCoalesce,
|
||||
|
||||
@@ -13,6 +13,7 @@ const accountFixture: ResolvedMattermostAccount = {
|
||||
baseUrl: "https://chat.example.com",
|
||||
botTokenSource: "config",
|
||||
baseUrlSource: "config",
|
||||
streamingMode: "partial",
|
||||
config: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -281,6 +281,20 @@ type MattermostDraftPreviewState = {
|
||||
finalizedViaPreviewPost: boolean;
|
||||
};
|
||||
|
||||
function createDisabledMattermostDraftStream(): ReturnType<typeof createMattermostDraftStream> {
|
||||
const noopAsync = async () => {};
|
||||
return {
|
||||
update: () => {},
|
||||
flush: noopAsync,
|
||||
postId: () => undefined,
|
||||
clear: noopAsync,
|
||||
discardPending: noopAsync,
|
||||
seal: noopAsync,
|
||||
stop: noopAsync,
|
||||
forceNewMessage: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
type MattermostDraftPreviewDeliverParams = {
|
||||
payload: ReplyPayload;
|
||||
info: { kind: "tool" | "block" | "final" };
|
||||
@@ -1619,14 +1633,17 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
},
|
||||
},
|
||||
});
|
||||
const draftStream = createMattermostDraftStream({
|
||||
client,
|
||||
channelId,
|
||||
rootId: effectiveReplyToId,
|
||||
throttleMs: 1200,
|
||||
log: logVerboseMessage,
|
||||
warn: logVerboseMessage,
|
||||
});
|
||||
const draftPreviewEnabled = account.streamingMode !== "off";
|
||||
const draftStream = draftPreviewEnabled
|
||||
? createMattermostDraftStream({
|
||||
client,
|
||||
channelId,
|
||||
rootId: effectiveReplyToId,
|
||||
throttleMs: 1200,
|
||||
log: logVerboseMessage,
|
||||
warn: logVerboseMessage,
|
||||
})
|
||||
: createDisabledMattermostDraftStream();
|
||||
let lastPartialText = "";
|
||||
const previewState: MattermostDraftPreviewState = {
|
||||
finalizedViaPreviewPost: false,
|
||||
@@ -1815,7 +1832,9 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
disableBlockStreaming: true,
|
||||
onModelSelected,
|
||||
onPartialReply: (payload) => {
|
||||
updateDraftFromPartial(payload.text);
|
||||
if (account.streamingMode !== "progress") {
|
||||
updateDraftFromPartial(payload.text);
|
||||
}
|
||||
},
|
||||
onAssistantMessageStart: () => {
|
||||
lastPartialText = "";
|
||||
|
||||
@@ -205,6 +205,7 @@ const accountFixture: ResolvedMattermostAccount = {
|
||||
baseUrl: "https://chat.example.com",
|
||||
botTokenSource: "config",
|
||||
baseUrlSource: "config",
|
||||
streamingMode: "partial",
|
||||
config: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ const accountFixture: ResolvedMattermostAccount = {
|
||||
baseUrl: "https://chat.example.com",
|
||||
botTokenSource: "config",
|
||||
baseUrlSource: "config",
|
||||
streamingMode: "partial",
|
||||
config: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ function createResolvedMattermostAccount(accountId: string): ResolvedMattermostA
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
baseUrlSource: "config",
|
||||
streamingMode: "partial",
|
||||
config: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type {
|
||||
ChannelPreviewStreamingConfig,
|
||||
StreamingMode,
|
||||
} from "openclaw/plugin-sdk/channel-streaming";
|
||||
import type { BlockStreamingCoalesceConfig, DmPolicy, GroupPolicy } from "./runtime-api.js";
|
||||
import type { SecretInput } from "./secret-input.js";
|
||||
|
||||
@@ -51,6 +55,8 @@ export type MattermostAccountConfig = {
|
||||
textChunkLimit?: number;
|
||||
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
|
||||
chunkMode?: "length" | "newline";
|
||||
/** Preview streaming mode/config. */
|
||||
streaming?: StreamingMode | boolean | ChannelPreviewStreamingConfig;
|
||||
/** Disable block streaming for this account. */
|
||||
blockStreaming?: boolean;
|
||||
/** Merge streamed block replies before sending. */
|
||||
|
||||
@@ -8198,6 +8198,101 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
type: "string",
|
||||
enum: ["length", "newline"],
|
||||
},
|
||||
streaming: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
enum: ["off", "partial", "block", "progress"],
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
mode: {
|
||||
type: "string",
|
||||
enum: ["off", "partial", "block", "progress"],
|
||||
},
|
||||
chunkMode: {
|
||||
type: "string",
|
||||
enum: ["length", "newline"],
|
||||
},
|
||||
preview: {
|
||||
type: "object",
|
||||
properties: {
|
||||
toolProgress: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
progress: {
|
||||
type: "object",
|
||||
properties: {
|
||||
label: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
const: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
labels: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
maxLines: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
toolProgress: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
block: {
|
||||
type: "object",
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
},
|
||||
coalesce: {
|
||||
type: "object",
|
||||
properties: {
|
||||
minChars: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
maxChars: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
idleMs: {
|
||||
type: "integer",
|
||||
minimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
blockStreaming: {
|
||||
type: "boolean",
|
||||
},
|
||||
@@ -8500,6 +8595,101 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
type: "string",
|
||||
enum: ["length", "newline"],
|
||||
},
|
||||
streaming: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
enum: ["off", "partial", "block", "progress"],
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
mode: {
|
||||
type: "string",
|
||||
enum: ["off", "partial", "block", "progress"],
|
||||
},
|
||||
chunkMode: {
|
||||
type: "string",
|
||||
enum: ["length", "newline"],
|
||||
},
|
||||
preview: {
|
||||
type: "object",
|
||||
properties: {
|
||||
toolProgress: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
progress: {
|
||||
type: "object",
|
||||
properties: {
|
||||
label: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
type: "boolean",
|
||||
const: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
labels: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
maxLines: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
toolProgress: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
block: {
|
||||
type: "object",
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
},
|
||||
coalesce: {
|
||||
type: "object",
|
||||
properties: {
|
||||
minChars: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
maxChars: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
idleMs: {
|
||||
type: "integer",
|
||||
minimum: 0,
|
||||
maximum: 9007199254740991,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
blockStreaming: {
|
||||
type: "boolean",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user