惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
博客园_首页
量子位
博客园 - 司徒正美
罗磊的独立博客
腾讯CDC
IT之家
IT之家
S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
L
Lohrmann on Cybersecurity
H
Hacker News: Front Page
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
Know Your Adversary
Know Your Adversary
人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
大猫的无限游戏
大猫的无限游戏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
I
InfoQ
T
Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
L
LINUX DO - 热门话题
A
About on SuperTechFans
Blog — PlanetScale
Blog — PlanetScale
Latest news
Latest news
Forbes - Security
Forbes - Security
Security Latest
Security Latest
NISL@THU
NISL@THU
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
The Hacker News
The Hacker News
M
MIT News - Artificial intelligence
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog
A
Arctic Wolf
C
Check Point Blog
G
Google Developers Blog
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
The Cloudflare Blog
L
LINUX DO - 最新话题
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Privacy International News Feed
小众软件
小众软件

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4 test: trim doctor command hotspots · openclaw/openclaw@c66f16a test: isolate agent auth and spawn hotspots · openclaw/openclaw@9285935 test: stabilize MCP startup disposal race · openclaw/openclaw@dd9d2eb test: merge browser contract server suites · openclaw/openclaw@5817a76 test: narrow ollama provider discovery setup · openclaw/openclaw@a0d9598 build: declare qa-lab aimock runtime dependency · openclaw/openclaw@24431e5 test: speed up safe-bins exec harness · openclaw/openclaw@ee856ab test: preserve tool helpers in embedded runner mocks · openclaw/openclaw@acd86a0 refactor: move memory embeddings into provider plugins · openclaw/openclaw@77e6e4c test: reuse system-run temp fixtures · openclaw/openclaw@7e9ff0f test: trim hotspot wait overhead · openclaw/openclaw@12a59b0 Check: avoid duplicate boundary prep · openclaw/openclaw@baf11b8 test: reduce hotspot fixture overhead · openclaw/openclaw@3a59edd feat(ui): overhaul settings and slash command UX (#67819) thanks @Bun… · openclaw/openclaw@2cfb660 QA Matrix: exit cleanly on failure · openclaw/openclaw@42805d2 QA Matrix: isolate scenario coverage · openclaw/openclaw@7e659e1 Matrix: refresh crypto bootstrap state · openclaw/openclaw@94081d8 QA Lab: add provider registry · openclaw/openclaw@bb7e982 Matrix: add plugin changelog · openclaw/openclaw@4acab55 test: trim more hotspot overhead · openclaw/openclaw@f485311 test: trim remaining hotspot tests · openclaw/openclaw@6ba8626 test: narrow hotspot mocks · openclaw/openclaw@dbc8179 test: isolate gemini embedding request helpers · openclaw/openclaw@cd330f5 test: trim memory and mcp hotspots · openclaw/openclaw@fd48dfa test: slim provider registry mocks · openclaw/openclaw@2e08c77 test: harden Parallels update smoke · openclaw/openclaw@1a98090 feat: default Anthropic to Opus 4.7 · openclaw/openclaw@628b454 fix: harden node-host shell payload mutability checks · openclaw/openclaw@75c551e fix: land node-host approval binding for native binaries (#66731) (th… · openclaw/openclaw@29919bb CI: add daily schedule to CodeQL workflow (#67645) · openclaw/openclaw@69d25f5 fix(gateway): capture config hash after plugin auto-enable to prevent… · openclaw/openclaw@8c11210 fix: repair sanitized replay tool results before send (#67620) (thank… · openclaw/openclaw@c3c7a99 fix: restrict HTML timeout short-circuit to transient statuses · openclaw/openclaw@de129a6 fix: keep TUI watchdog bound to active run (#67401) (thanks @xantorres) · openclaw/openclaw@3525273 Gateway/skills: dedupe skills prefix-match + drop dead fallback on log · openclaw/openclaw@d7f489f Extensions/lmstudio: back off inference preload after consecutive fai… · openclaw/openclaw@b555214 TUI/streaming: add watchdog that resets the activity indicator after … · openclaw/openclaw@f44ab20 Agents/tool-loop: enable unknown-tool stream guard by default · openclaw/openclaw@36ed367 Gateway/skills: invalidate session skills snapshot on config write · openclaw/openclaw@b23d59a fix: classify HTML provider error pages correctly (#67642) (thanks @s… · openclaw/openclaw@e588e90 fix(skills): remove unused model-usage import (#67641) · openclaw/openclaw@55f05df docs(changelog): credit codex fix superseded PRs · openclaw/openclaw@e485f24 fix(openai-codex): normalize stale transport metadata in resolution a… · openclaw/openclaw@90801ba CI: pin Docker-related GitHub Actions (#67632) · openclaw/openclaw@f697b01 Android: modernize WebView and discovery API usage (#67627) · openclaw/openclaw@44a6e50 fix(deps): bump hono to 4.12.14 and @hono/node-server to 1.19.14 (GHS… · openclaw/openclaw@fbccc18 fix(deps): bump dompurify to 3.4.0 (#67614) · openclaw/openclaw@2c2dc00 CI: add explicit permissions to all workflow jobs (fixes code-scannin… · openclaw/openclaw@01b7516 fix: register bundled TTS providers and route overrides correctly (#6… · openclaw/openclaw@6ea3cdd fix: align host tilde paths with OS home (#62804) (thanks @stainlu) · openclaw/openclaw@ecfaf64 fix: flush creds queue before reconnect socket open (#67464) (thanks … · openclaw/openclaw@405c63f fix: strip standalone <function> tool call tags from visible text (#6… · openclaw/openclaw@78df859 fix(agents): preserve cli session metadata before transcript persist … · openclaw/openclaw@898fd04 docs(changelog): move cli transcript entry · openclaw/openclaw@c1817c6 fix(agents): normalize cli transcript api field · openclaw/openclaw@3a3fae0 docs(changelog): note cli transcript persistence · openclaw/openclaw@6c343f1 fix(agents): persist cli transcript turns · openclaw/openclaw@b8ef507 fix(msteams): harden security-sensitive flows (#65841) · openclaw/openclaw@c56b56e [Dashboard] Fix exec approval modal overflow for long command content… · openclaw/openclaw@053c5b0 Docs: remove QA changelog entry · openclaw/openclaw@7fd5771 QA: fix private runtime source loading (#67428) · openclaw/openclaw@d5933af docs(gateway): correct protocol.md schema path, hello-ok example, aut… · openclaw/openclaw@489404d CI: pin Node 22 runners to 22.18.0 · openclaw/openclaw@4ffa621 models.authStatus: normalize provider ids + tighten env-backed escape… · openclaw/openclaw@f2fdb9d Update CHANGELOG.md · openclaw/openclaw@7694a92 test(parallels): clean up npm update guard jobs · openclaw/openclaw@045ea7b Plugins: prefer scanDir override paths · openclaw/openclaw@b2974da fix(dreaming): default storage.mode to "separate" so phase blocks sto… · openclaw/openclaw@8c392f0 fix(memory-core): skip dreaming transcript ingestion via session stor… · openclaw/openclaw@a1b01f0 fix: dedupe replayed exec.finished node events (#67281) · openclaw/openclaw@5dcf526
fix(signal): bind approval reactions from structured deliveries · openclaw/openclaw@6830aa3
joshavant · 2026-06-26 · via Recent Commits to openclaw:main
1+

import {

2+

buildExecApprovalPendingReplyPayload,

3+

buildPluginApprovalPendingReplyPayload,

4+

} from "openclaw/plugin-sdk/approval-reply-runtime";

15

// Signal tests cover approval reactions plugin behavior.

26

import { beforeEach, describe, expect, it, vi } from "vitest";

37

import {

48

addSignalApprovalReactionHintToText,

5-

appendSignalApprovalReactionHintForOutboundMessage,

9+

addSignalApprovalReactionHintToStructuredPayload,

610

buildSignalApprovalReactionHint,

711

clearSignalApprovalReactionTargetsForTest,

812

maybeResolveSignalApprovalReaction,

9-

registerSignalApprovalReactionTargetForOutboundMessage,

13+

registerSignalApprovalReactionTargetForDeliveredPayload,

1014

registerSignalApprovalReactionTarget,

1115

resolveSignalApprovalReactionTargetWithPersistence,

1216

} from "./approval-reactions.js";

@@ -78,85 +82,334 @@ describe("Signal approval reactions", () => {

7882

).toBe(prompt);

7983

});

808481-

it("registers target-mode outbound approval prompts for reactions", async () => {

85+

it("registers delivered structured approval payloads for reactions", async () => {

8286

const cfg = {

8387

channels: {

8488

signal: {

8589

allowFrom: ["+15551230000"],

8690

},

8791

},

8892

approvals: {

89-

plugin: {

93+

exec: {

9094

enabled: true,

9195

mode: "targets" as const,

9296

targets: [{ channel: "signal", to: "+15551230000" }],

9397

},

9498

},

9599

};

96-

const text =

97-

"Plugin approval required\nID: plugin:abc\n\nReply with: /approve plugin:abc allow-once|deny";

98-

const textWithHint = appendSignalApprovalReactionHintForOutboundMessage({

100+

const payload = buildExecApprovalPendingReplyPayload({

101+

approvalId: "exec-structured-approval",

102+

approvalSlug: "exec-str",

103+

allowedDecisions: ["allow-once", "deny"],

104+

command: "printf test",

105+

host: "gateway",

106+

agentId: "main",

107+

sessionKey: "agent:main:signal:direct:+15551230000",

108+

});

109+

const deliveredPayload = addSignalApprovalReactionHintToStructuredPayload({

99110

cfg,

100111

accountId: "default",

101112

to: "+15551230000",

102-

text,

113+

payload,

103114

targetAuthor: "+15550009999",

104115

});

105116106-

expect(textWithHint).toContain("React with:\n\n👍 Allow Once\n👎 Deny");

107117

expect(

108-

registerSignalApprovalReactionTargetForOutboundMessage({

118+

registerSignalApprovalReactionTargetForDeliveredPayload({

109119

cfg,

120+

target: {

121+

channel: "signal",

122+

to: "+15551230000",

123+

accountId: "default",

124+

},

125+

payload: deliveredPayload!,

126+

results: [

127+

{

128+

channel: "signal",

129+

messageId: "1700000000012",

130+

toJid: "+15551230000",

131+

},

132+

],

133+

targetAuthor: "+15550009999",

134+

}),

135+

).toBe(true);

136+137+

await expect(

138+

resolveSignalApprovalReactionTargetWithPersistence({

110139

accountId: "default",

140+

conversationKey: "+15551230000",

141+

messageId: "1700000000012",

142+

reactionKey: "👍",

143+

targetAuthor: "+15550009999",

144+

}),

145+

).resolves.toEqual({

146+

approvalId: "exec-structured-approval",

147+

approvalKind: "exec",

148+

decision: "allow-once",

149+

route: {

150+

deliveryMode: "target",

111151

to: "+15551230000",

112-

messageId: "1700000000009",

113-

text: textWithHint,

152+

accountId: "default",

153+

agentId: "main",

154+

sessionKey: "agent:main:signal:direct:+15551230000",

155+

},

156+

});

157+

});

158+159+

it("does not register metadata-only approval payloads without visible reaction hints", async () => {

160+

const cfg = {

161+

channels: {

162+

signal: {

163+

allowFrom: ["+15551230000"],

164+

},

165+

},

166+

approvals: {

167+

exec: {

168+

enabled: true,

169+

mode: "targets" as const,

170+

targets: [{ channel: "signal", to: "+15551230000" }],

171+

},

172+

},

173+

};

174+

const payload = buildExecApprovalPendingReplyPayload({

175+

approvalId: "exec-hidden-reaction",

176+

approvalSlug: "exec-hid",

177+

allowedDecisions: ["allow-once", "deny"],

178+

command: "printf hidden",

179+

host: "gateway",

180+

agentId: "main",

181+

sessionKey: "agent:main:signal:direct:+15551230000",

182+

});

183+184+

expect(

185+

registerSignalApprovalReactionTargetForDeliveredPayload({

186+

cfg,

187+

target: {

188+

channel: "signal",

189+

to: "+15551230000",

190+

accountId: "default",

191+

},

192+

payload,

193+

results: [

194+

{

195+

channel: "signal",

196+

messageId: "1700000000015",

197+

},

198+

],

114199

targetAuthor: "+15550009999",

115200

}),

116-

).toBe(true);

201+

).toBe(false);

117202118-

const handled = await maybeResolveSignalApprovalReaction({

203+

await expect(

204+

resolveSignalApprovalReactionTargetWithPersistence({

205+

accountId: "default",

206+

conversationKey: "+15551230000",

207+

messageId: "1700000000015",

208+

reactionKey: "👍",

209+

targetAuthor: "+15550009999",

210+

}),

211+

).resolves.toBeNull();

212+

});

213+214+

it("registers only delivered chunks that contain visible reaction hints", async () => {

215+

const cfg = {

216+

channels: {

217+

signal: {

218+

allowFrom: ["+15551230000"],

219+

},

220+

},

221+

approvals: {

222+

exec: {

223+

enabled: true,

224+

mode: "targets" as const,

225+

targets: [{ channel: "signal", to: "+15551230000" }],

226+

},

227+

},

228+

};

229+

const payload = buildExecApprovalPendingReplyPayload({

230+

approvalId: "exec-chunked-reaction",

231+

approvalSlug: "exec-ch",

232+

allowedDecisions: ["allow-once", "deny"],

233+

command: "printf chunked",

234+

host: "gateway",

235+

agentId: "main",

236+

sessionKey: "agent:main:signal:direct:+15551230000",

237+

});

238+

const deliveredPayload = addSignalApprovalReactionHintToStructuredPayload({

119239

cfg,

120240

accountId: "default",

121-

conversationKey: "+15551230000",

122-

messageId: "1700000000009",

123-

reactionKey: "👍",

124-

actorId: "+15551230000",

241+

to: "+15551230000",

242+

payload,

125243

targetAuthor: "+15550009999",

126244

});

127245128-

expect(handled).toBe(true);

129-

expect(resolverMocks.resolveSignalApproval).toHaveBeenCalledWith({

246+

expect(

247+

registerSignalApprovalReactionTargetForDeliveredPayload({

248+

cfg,

249+

target: {

250+

channel: "signal",

251+

to: "+15551230000",

252+

accountId: "default",

253+

},

254+

payload: deliveredPayload!,

255+

results: [

256+

{

257+

channel: "signal",

258+

messageId: "1700000000016",

259+

meta: {

260+

signalVisibleText: "Exec approval required\n\nReact with:\n\n👍 Allow Once\n👎 Deny",

261+

},

262+

},

263+

{

264+

channel: "signal",

265+

messageId: "1700000000017",

266+

meta: {

267+

signalVisibleText: "Continuation chunk without controls",

268+

},

269+

},

270+

],

271+

targetAuthor: "+15550009999",

272+

}),

273+

).toBe(true);

274+275+

await expect(

276+

resolveSignalApprovalReactionTargetWithPersistence({

277+

accountId: "default",

278+

conversationKey: "+15551230000",

279+

messageId: "1700000000016",

280+

reactionKey: "👍",

281+

targetAuthor: "+15550009999",

282+

}),

283+

).resolves.toMatchObject({

284+

approvalId: "exec-chunked-reaction",

285+

decision: "allow-once",

286+

});

287+

await expect(

288+

resolveSignalApprovalReactionTargetWithPersistence({

289+

accountId: "default",

290+

conversationKey: "+15551230000",

291+

messageId: "1700000000017",

292+

reactionKey: "👍",

293+

targetAuthor: "+15550009999",

294+

}),

295+

).resolves.toBeNull();

296+

});

297+298+

it("registers delivered structured plugin approval payloads using metadata kind", async () => {

299+

const cfg = {

300+

channels: {

301+

signal: {

302+

allowFrom: ["+15551230000"],

303+

},

304+

},

305+

approvals: {

306+

plugin: {

307+

enabled: true,

308+

mode: "targets" as const,

309+

targets: [{ channel: "signal", to: "+15551230000" }],

310+

},

311+

},

312+

};

313+

const payload = buildPluginApprovalPendingReplyPayload({

314+

request: {

315+

id: "plugin-structured-approval",

316+

request: {

317+

title: "Sensitive plugin action",

318+

description: "Needs approval",

319+

allowedDecisions: ["allow-once", "deny"],

320+

},

321+

createdAtMs: 1_000,

322+

expiresAtMs: 61_000,

323+

},

324+

nowMs: 1_000,

325+

});

326+

const deliveredPayload = addSignalApprovalReactionHintToStructuredPayload({

130327

cfg,

131-

approvalId: "plugin:abc",

328+

accountId: "default",

329+

to: "+15551230000",

330+

payload,

331+

targetAuthor: "+15550009999",

332+

});

333+334+

expect(

335+

registerSignalApprovalReactionTargetForDeliveredPayload({

336+

cfg,

337+

target: {

338+

channel: "signal",

339+

to: "+15551230000",

340+

accountId: "default",

341+

},

342+

payload: deliveredPayload!,

343+

results: [

344+

{

345+

channel: "signal",

346+

messageId: "1700000000013",

347+

},

348+

],

349+

targetAuthor: "+15550009999",

350+

}),

351+

).toBe(true);

352+353+

await expect(

354+

resolveSignalApprovalReactionTargetWithPersistence({

355+

accountId: "default",

356+

conversationKey: "+15551230000",

357+

messageId: "1700000000013",

358+

reactionKey: "👍",

359+

targetAuthor: "+15550009999",

360+

}),

361+

).resolves.toMatchObject({

362+

approvalId: "plugin-structured-approval",

363+

approvalKind: "plugin",

132364

decision: "allow-once",

133-

senderId: "+15551230000",

134-

gatewayUrl: undefined,

135365

});

136366

});

137367138-

it("keeps target-mode outbound prompts manual when the target route is disabled", () => {

139-

const text =

140-

"Plugin approval required\nID: plugin:abc\n\nReply with: /approve plugin:abc allow-once|deny";

368+

it("does not register delivered structured approval payloads without explicit approvers", () => {

369+

const payload = buildExecApprovalPendingReplyPayload({

370+

approvalId: "exec-no-approvers",

371+

approvalSlug: "exec-no",

372+

allowedDecisions: ["allow-once", "deny"],

373+

command: "printf test",

374+

host: "gateway",

375+

});

376+

const deliveredPayload = {

377+

...payload,

378+

text: addSignalApprovalReactionHintToText({

379+

text: payload.text ?? "",

380+

allowedDecisions: ["allow-once", "deny"],

381+

}),

382+

};

141383142384

expect(

143-

appendSignalApprovalReactionHintForOutboundMessage({

385+

registerSignalApprovalReactionTargetForDeliveredPayload({

144386

cfg: {

145-

channels: { signal: { allowFrom: ["+15551230000"] } },

387+

channels: {

388+

signal: {},

389+

},

146390

approvals: {

147-

plugin: {

148-

enabled: false,

391+

exec: {

392+

enabled: true,

149393

mode: "targets",

150394

targets: [{ channel: "signal", to: "+15551230000" }],

151395

},

152396

},

153397

},

154-

accountId: "default",

155-

to: "+15551230000",

156-

text,

398+

target: {

399+

channel: "signal",

400+

to: "+15551230000",

401+

accountId: "default",

402+

},

403+

payload: deliveredPayload,

404+

results: [

405+

{

406+

channel: "signal",

407+

messageId: "1700000000014",

408+

},

409+

],

157410

targetAuthor: "+15550009999",

158411

}),

159-

).toBe(text);

412+

).toBe(false);

160413

});

161414162415

it("registers reaction state when only allow-always is available", async () => {