Skip to content

Commit ac60db7

Browse files
committed
Auto-merge upstream openclaw/openclaw
2 parents 6155bba + 9fd08f9 commit ac60db7

74 files changed

Lines changed: 2697 additions & 745 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Docs: https://docs.openclaw.ai
1414
- QA/testing: add a `--runner multipass` lane for `openclaw qa suite` so repo-backed QA scenarios can run inside a disposable Linux VM and write back the usual report, summary, and VM logs. (#63426) Thanks @shakkernerd.
1515
- Docs i18n: chunk raw doc translation, reject truncated tagged outputs, avoid ambiguous body-only wrapper unwrapping, and recover from terminated Pi translation sessions without changing the default `openai/gpt-5.4` path. (#62969, #63808) Thanks @hxy91819.
1616
- Control UI/dreaming: simplify the Scene and Diary surfaces, preserve unknown phase state for partial status payloads, and stabilize waiting-entry recency ordering so Dreaming status and review lists stay clear and deterministic. (#64035) Thanks @davemorin.
17+
- Gateway: split startup and runtime seams so gateway lifecycle sequencing, reload state, and shutdown behavior stay easier to maintain without changing observed behavior. (#63975) Thanks @gumadeiras.
18+
- Matrix/partial streaming: add MSC4357 live markers to draft preview sends and edits so supporting Matrix clients can render a live/typewriter animation and stop it when the final edit lands. (#63513) Thanks @TigerInYourDream.
1719

1820
### Fixes
1921

@@ -101,6 +103,9 @@ Docs: https://docs.openclaw.ai
101103
- Reply/skills: keep resolved skill and memory secret config stable through embedded reply runs so raw SecretRefs in secondary skill settings no longer crash replies when the gateway already has the live env. (#64249) Thanks @mbelinky.
102104
- Dreaming/startup: keep plugin-registered startup hooks alive across workspace hook reloads and include dreaming startup owners in the gateway startup plugin scope, so managed Dreaming cron registration comes back reliably after gateway boot. (#62327) Thanks @mbelinky.
103105
- Plugins: treat duplicate `registerService` calls from the same plugin id as idempotent so snapshot and activation loads no longer emit spurious `service already registered` diagnostics. (#62033, #64128) Thanks @ly85206559.
106+
- Discord/TTS: route auto voice replies through the native voice-note path so Discord receives Opus voice messages instead of regular audio attachments. (#64096) Thanks @LiuHuaize.
107+
- Config/plugins: use plugin-owned command alias metadata when `plugins.allow` contains runtime command names like `dreaming`, and point users at the owning plugin instead of stale plugin-not-found guidance. (#64242) Thanks @feiskyer.
108+
- Agents/Gemini: strip orphaned `required` entries from Gemini tool schemas so provider validation no longer rejects tools after schema cleanup or union flattening. (#64284) Thanks @xxxxxmax.
104109

105110
## 2026.4.9
106111

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
268aca42eaae8b4dd37d7eddb7202d002db16a4a27830cd90d98b5c4413cbbe7 plugin-sdk-api-baseline.json
2-
4fe4fc194bec72a58bdd5566c4b31c00b2c0a520941fdcdd0f42bdf02b683ea5 plugin-sdk-api-baseline.jsonl
1+
70ecd040dd5815b237eb749db2de98e300fd53630e5b418deea0865b781adf4a plugin-sdk-api-baseline.json
2+
7a7b08495662e48d466a1c7cb20d6e81557df05d361de0bebc54369e34dffa35 plugin-sdk-api-baseline.jsonl

docs/plugins/manifest.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ Those belong in your plugin code and `package.json`.
147147
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
148148
| `modelSupport` | No | `object` | Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime. |
149149
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
150+
| `commandAliases` | No | `object[]` | Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads. |
150151
| `providerAuthEnvVars` | No | `Record<string, string[]>` | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code. |
151152
| `providerAuthAliases` | No | `Record<string, string>` | Provider ids that should reuse another provider id for auth lookup, for example a coding provider that shares the base provider API key and auth profiles. |
152153
| `channelEnvVars` | No | `Record<string, string[]>` | Cheap channel env metadata that OpenClaw can inspect without loading plugin code. Use this for env-driven channel setup or auth surfaces that generic startup/config helpers should see. |
@@ -183,6 +184,30 @@ OpenClaw reads this before provider runtime loads.
183184
| `cliDescription` | No | `string` | Description used in CLI help. |
184185
| `onboardingScopes` | No | `Array<"text-inference" \| "image-generation">` | Which onboarding surfaces this choice should appear in. If omitted, it defaults to `["text-inference"]`. |
185186

187+
## commandAliases reference
188+
189+
Use `commandAliases` when a plugin owns a runtime command name that users may
190+
mistakenly put in `plugins.allow` or try to run as a root CLI command. OpenClaw
191+
uses this metadata for diagnostics without importing plugin runtime code.
192+
193+
```json
194+
{
195+
"commandAliases": [
196+
{
197+
"name": "dreaming",
198+
"kind": "runtime-slash",
199+
"cliCommand": "memory"
200+
}
201+
]
202+
}
203+
```
204+
205+
| Field | Required | Type | What it means |
206+
| ------------ | -------- | ----------------- | ----------------------------------------------------------------------- |
207+
| `name` | Yes | `string` | Command name that belongs to this plugin. |
208+
| `kind` | No | `"runtime-slash"` | Marks the alias as a chat slash command rather than a root CLI command. |
209+
| `cliCommand` | No | `string` | Related root CLI command to suggest for CLI operations, if one exists. |
210+
186211
## uiHints reference
187212

188213
`uiHints` is a map from config field names to small rendering hints.

extensions/device-pair/openclaw.plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
"enabledByDefault": true,
44
"name": "Device Pairing",
55
"description": "Generate setup codes and approve device pairing requests.",
6+
"commandAliases": [
7+
{
8+
"name": "pair",
9+
"kind": "runtime-slash"
10+
}
11+
],
612
"configSchema": {
713
"type": "object",
814
"additionalProperties": false,

extensions/matrix/src/matrix/draft-stream.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,56 @@ describe("createMatrixDraftStream", () => {
203203
expect(eventId).toBe("$evt1");
204204
});
205205

206+
it("stop does not finalize live drafts on its own", async () => {
207+
const stream = createMatrixDraftStream({
208+
roomId: "!room:test",
209+
client,
210+
cfg: {} as import("../types.js").CoreConfig,
211+
mode: "partial",
212+
});
213+
214+
stream.update("Hello");
215+
await stream.stop();
216+
217+
expect(sendMessageMock).toHaveBeenCalledTimes(1);
218+
expect(sendMessageMock.mock.calls[0]?.[1]).toHaveProperty("org.matrix.msc4357.live");
219+
});
220+
221+
it("finalizeLive clears the live marker at most once", async () => {
222+
const stream = createMatrixDraftStream({
223+
roomId: "!room:test",
224+
client,
225+
cfg: {} as import("../types.js").CoreConfig,
226+
mode: "partial",
227+
});
228+
229+
stream.update("Hello");
230+
await stream.stop();
231+
232+
await stream.finalizeLive();
233+
await stream.finalizeLive();
234+
235+
expect(sendMessageMock).toHaveBeenCalledTimes(2);
236+
expect(sendMessageMock.mock.calls[1]?.[1]).not.toHaveProperty("org.matrix.msc4357.live");
237+
});
238+
239+
it("marks live finalize failures for normal final delivery fallback", async () => {
240+
sendMessageMock.mockResolvedValueOnce("$evt1").mockRejectedValueOnce(new Error("rate limited"));
241+
242+
const stream = createMatrixDraftStream({
243+
roomId: "!room:test",
244+
client,
245+
cfg: {} as import("../types.js").CoreConfig,
246+
mode: "partial",
247+
});
248+
249+
stream.update("Hello");
250+
await stream.stop();
251+
252+
await expect(stream.finalizeLive()).resolves.toBe(false);
253+
expect(stream.mustDeliverFinalNormally()).toBe(true);
254+
});
255+
206256
it("reset allows reuse for next block", async () => {
207257
sendMessageMock.mockResolvedValueOnce("$first").mockResolvedValueOnce("$second");
208258

extensions/matrix/src/matrix/draft-stream.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export type MatrixDraftStream = {
2929
flush: () => Promise<void>;
3030
/** Flush and mark this block as done. Returns the event ID if a message was sent. */
3131
stop: () => Promise<string | undefined>;
32+
/** Clear the MSC4357 live marker in place when the draft is kept as final text. */
33+
finalizeLive: () => Promise<boolean>;
3234
/** Reset state for the next text block (after tool calls). */
3335
reset: () => void;
3436
/** The event ID of the current draft message, if any. */
@@ -53,12 +55,17 @@ export function createMatrixDraftStream(params: {
5355
}): MatrixDraftStream {
5456
const { roomId, client, cfg, threadId, accountId, log } = params;
5557
const preview = resolveDraftPreviewOptions(params.mode ?? "partial");
58+
// MSC4357 live markers are only useful for "partial" mode where users see
59+
// the draft evolve. "quiet" mode uses m.notice for background previews
60+
// where a streaming animation would be unexpected.
61+
const useLive = params.mode !== "quiet";
5662

5763
let currentEventId: string | undefined;
5864
let lastSentText = "";
5965
let stopped = false;
6066
let sendFailed = false;
6167
let finalizeInPlaceBlocked = false;
68+
let liveFinalized = false;
6269
let replyToId = params.replyToId;
6370

6471
const sendOrEdit = async (text: string): Promise<boolean> => {
@@ -94,10 +101,11 @@ export function createMatrixDraftStream(params: {
94101
accountId,
95102
msgtype: preview.msgtype,
96103
includeMentions: preview.includeMentions,
104+
live: useLive,
97105
});
98106
currentEventId = result.messageId;
99107
lastSentText = preparedText.trimmedText;
100-
log?.(`draft-stream: created message ${currentEventId}`);
108+
log?.(`draft-stream: created message ${currentEventId}${useLive ? " (MSC4357 live)" : ""}`);
101109
} else {
102110
await editMessageMatrix(roomId, currentEventId, preparedText.trimmedText, {
103111
client,
@@ -106,6 +114,7 @@ export function createMatrixDraftStream(params: {
106114
accountId,
107115
msgtype: preview.msgtype,
108116
includeMentions: preview.includeMentions,
117+
live: useLive,
109118
});
110119
lastSentText = preparedText.trimmedText;
111120
}
@@ -133,6 +142,37 @@ export function createMatrixDraftStream(params: {
133142

134143
log?.(`draft-stream: ready (throttleMs=${DEFAULT_THROTTLE_MS})`);
135144

145+
const finalizeLive = async (): Promise<boolean> => {
146+
// Send a final edit without the MSC4357 live marker to signal that
147+
// the stream is complete. Supporting clients will stop the streaming
148+
// animation and display the final content.
149+
if (useLive && !liveFinalized && currentEventId && lastSentText) {
150+
liveFinalized = true;
151+
try {
152+
await editMessageMatrix(roomId, currentEventId, lastSentText, {
153+
client,
154+
cfg,
155+
threadId,
156+
accountId,
157+
msgtype: preview.msgtype,
158+
includeMentions: preview.includeMentions,
159+
live: false,
160+
});
161+
log?.(`draft-stream: finalized ${currentEventId} (MSC4357 stream ended)`);
162+
return true;
163+
} catch (err) {
164+
log?.(`draft-stream: finalize edit failed: ${String(err)}`);
165+
// If the finalize edit fails, the live marker remains on the last
166+
// successful edit. Flag the stream so callers can fall back to
167+
// normal final delivery or redaction instead of leaving the message
168+
// stuck in a "still streaming" state for MSC4357 clients.
169+
finalizeInPlaceBlocked = true;
170+
return false;
171+
}
172+
}
173+
return true;
174+
};
175+
136176
const stop = async (): Promise<string | undefined> => {
137177
// Flush before marking stopped so the loop can drain pending text.
138178
await loop.flush();
@@ -149,6 +189,7 @@ export function createMatrixDraftStream(params: {
149189
stopped = false;
150190
sendFailed = false;
151191
finalizeInPlaceBlocked = false;
192+
liveFinalized = false;
152193
loop.resetPending();
153194
loop.resetThrottleWindow();
154195
};
@@ -162,6 +203,7 @@ export function createMatrixDraftStream(params: {
162203
},
163204
flush: loop.flush,
164205
stop,
206+
finalizeLive,
165207
reset,
166208
eventId: () => currentEventId,
167209
matchesPreparedText: (text: string) =>

0 commit comments

Comments
 (0)