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

推荐订阅源

P
Proofpoint News Feed
V
V2EX
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
The Cloudflare Blog
T
Tailwind CSS Blog
H
Help Net Security
腾讯CDC
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
Stack Overflow Blog
Stack Overflow Blog
D
DataBreaches.Net
C
Check Point Blog
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

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
Add opt-in reaction tool tracking · openclaw/openclaw@788...
2026-05-04 · via Recent Commits to openclaw:main

@@ -82,6 +82,21 @@ type DiscordMessageProcessObserver = {

8282

onReplyPlanResolved?: (params: { createdThreadId?: string; sessionKey?: string }) => void;

8383

};

848485+

type ToolStartPayload = {

86+

name?: string;

87+

phase?: string;

88+

args?: Record<string, unknown>;

89+

};

90+91+

function readToolStringArg(args: Record<string, unknown>, key: string): string | undefined {

92+

const value = args[key];

93+

return typeof value === "string" && value.trim() ? value.trim() : undefined;

94+

}

95+96+

function readToolBooleanArg(args: Record<string, unknown>, key: string): boolean {

97+

return args[key] === true;

98+

}

99+85100

export async function processDiscordMessage(

86101

ctx: DiscordMessagePreflightContext,

87102

observer?: DiscordMessageProcessObserver,

@@ -203,7 +218,9 @@ export async function processDiscordMessage(

203218

messageId: message.id,

204219

reactionContext: ackReactionContext,

205220

});

206-

const statusReactions = createStatusReactionController({

221+

let statusReactionTarget = `${messageChannelId}/${message.id}`;

222+

let statusReactionsActive = statusReactionsEnabled;

223+

let statusReactions = createStatusReactionController({

207224

enabled: statusReactionsEnabled,

208225

adapter: discordAdapter,

209226

initialEmoji: ackReaction,

@@ -213,11 +230,67 @@ export async function processDiscordMessage(

213230

logAckFailure({

214231

log: logVerbose,

215232

channel: "discord",

216-

target: `${messageChannelId}/${message.id}`,

233+

target: statusReactionTarget,

217234

error: err,

218235

});

219236

},

220237

});

238+

const maybeBindStatusReactionsToToolReaction = (payload: ToolStartPayload) => {

239+

if (

240+

sourceRepliesAreToolOnly ||

241+

cfg.messages?.statusReactions?.enabled === false ||

242+

payload.phase !== "start" ||

243+

payload.name !== "message" ||

244+

!payload.args

245+

) {

246+

return;

247+

}

248+

const args = payload.args;

249+

const action = readToolStringArg(args, "action")?.toLowerCase();

250+

if (action !== "react") {

251+

return;

252+

}

253+

const shouldTrack =

254+

readToolBooleanArg(args, "trackToolCalls") || readToolBooleanArg(args, "track_tool_calls");

255+

if (!shouldTrack) {

256+

return;

257+

}

258+

const emoji = readToolStringArg(args, "emoji");

259+

const remove = readToolBooleanArg(args, "remove");

260+

if (!emoji || remove) {

261+

return;

262+

}

263+

const trackedMessageId =

264+

readToolStringArg(args, "messageId") ?? readToolStringArg(args, "message_id") ?? message.id;

265+

const trackedChannelId =

266+

readToolStringArg(args, "channelId") ?? readToolStringArg(args, "to") ?? messageChannelId;

267+

statusReactionTarget = `${trackedChannelId}/${trackedMessageId}`;

268+

if (statusReactionsActive) {

269+

void statusReactions.clear();

270+

}

271+

const trackedAdapter = createDiscordAckReactionAdapter({

272+

channelId: trackedChannelId,

273+

messageId: trackedMessageId,

274+

reactionContext: ackReactionContext,

275+

});

276+

statusReactions = createStatusReactionController({

277+

enabled: true,

278+

adapter: trackedAdapter,

279+

initialEmoji: emoji,

280+

emojis: cfg.messages?.statusReactions?.emojis,

281+

timing: cfg.messages?.statusReactions?.timing,

282+

onError: (err) => {

283+

logAckFailure({

284+

log: logVerbose,

285+

channel: "discord",

286+

target: statusReactionTarget,

287+

error: err,

288+

});

289+

},

290+

});

291+

statusReactionsActive = true;

292+

void statusReactions.setQueued();

293+

};

221294

queueInitialDiscordAckReaction({

222295

enabled: statusReactionsEnabled,

223296

shouldSendAckReaction,

@@ -546,6 +619,7 @@ export async function processDiscordMessage(

546619

if (isProcessAborted(abortSignal)) {

547620

return;

548621

}

622+

maybeBindStatusReactionsToToolReaction(payload);

549623

await statusReactions.setTool(payload.name);

550624

draftPreview.pushToolProgress(

551625

payload.name ? `tool: ${payload.name}` : "tool running",

@@ -632,7 +706,7 @@ export async function processDiscordMessage(

632706

markDispatchIdle();

633707

}

634708

}

635-

if (statusReactionsEnabled) {

709+

if (statusReactionsActive) {

636710

if (dispatchAborted) {

637711

if (removeAckAfterReply) {

638712

void statusReactions.clear();