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

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
Y
Y Combinator Blog
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
S
SegmentFault 最新的问题
J
Java Code Geeks
V
Visual Studio Blog
H
Help Net Security
Stack Overflow Blog
Stack Overflow Blog
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
B
Blog RSS Feed
The Cloudflare Blog
MyScale Blog
MyScale Blog
月光博客
月光博客
Microsoft Security Blog
Microsoft Security Blog
美团技术团队

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
fix(outbound): reject reserved Telegram targets · opencla...
2026-06-25 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -737,6 +737,9 @@ outbound host generic and use the messaging adapter surface for provider rules:

737737

should be treated as `direct`, `group`, or `channel` before directory lookup.

738738

- `messaging.targetResolver.looksLikeId(raw, normalized)` tells core whether an

739739

input should skip straight to id-like resolution instead of directory search.

740+

- `messaging.targetResolver.reservedLiterals` lists bare words that are

741+

channel/session references for that provider and must fail closed before

742+

directory or default-target fallback.

740743

- `messaging.targetResolver.resolveTarget(...)` is the plugin fallback when

741744

core needs a final provider-owned resolution after normalization or after a

742745

directory miss.

Original file line numberDiff line numberDiff line change

@@ -739,7 +739,7 @@ Write colocated tests in `src/channel.test.ts`:

739739

describeMessageTool and action discovery

740740

</Card>

741741

<Card title="Target resolution" icon="crosshair" href="/plugins/architecture-internals#channel-target-resolution">

742-

inferTargetChatType, looksLikeId, resolveTarget

742+

inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget

743743

</Card>

744744

<Card title="Runtime helpers" icon="settings" href="/plugins/sdk-runtime">

745745

TTS, STT, media, subagent via api.runtime

Original file line numberDiff line numberDiff line change

@@ -833,6 +833,7 @@ export const telegramPlugin = createChatChannelPlugin({

833833

targetResolver: {

834834

looksLikeId: looksLikeTelegramTargetId,

835835

hint: "<chatId>",

836+

reservedLiterals: ["current", "self", "this", "me"],

836837

},

837838

},

838839

resolver: {

Original file line numberDiff line numberDiff line change

@@ -92,6 +92,14 @@ export function expectChannelSurfaceContract(params: {

9292

expect(typeof messaging.targetResolver.hint).toBe("string");

9393

expect(messaging.targetResolver.hint.trim()).not.toBe("");

9494

}

95+

if (messaging.targetResolver.reservedLiterals !== undefined) {

96+

expect(Array.isArray(messaging.targetResolver.reservedLiterals)).toBe(true);

97+

expect(

98+

messaging.targetResolver.reservedLiterals.every(

99+

(value) => typeof value === "string" && value.trim(),

100+

),

101+

).toBe(true);

102+

}

95103

if (messaging.targetResolver.resolveTarget) {

96104

expect(typeof messaging.targetResolver.resolveTarget).toBe("function");

97105

}

Original file line numberDiff line numberDiff line change

@@ -608,6 +608,8 @@ export type ChannelMessagingAdapter = {

608608

targetResolver?: {

609609

looksLikeId?: (raw: string, normalized?: string) => boolean;

610610

hint?: string;

611+

/** Bare words that are command/session references for this channel, not literal destinations. */

612+

reservedLiterals?: readonly string[];

611613

/**

612614

* Plugin-owned fallback for explicit/native targets or post-directory-miss

613615

* resolution. This should complement directory lookup, not duplicate it.

Original file line numberDiff line numberDiff line change

@@ -2300,7 +2300,7 @@ export const agentHandlers: GatewayRequestHandlers = {

23002300

let resolvedTo = deliveryPlan.resolvedTo;

23012301

let effectivePlan = deliveryPlan;

23022302

let deliveryDowngradeReason: string | null = null;

2303-

let deliveryTargetResolutionError: Error | undefined;

2303+

let deliveryTargetResolutionError: Error | undefined = deliveryPlan.targetResolutionError;

23042304
23052305

if (wantsDelivery && resolvedChannel === INTERNAL_MESSAGE_CHANNEL) {

23062306

const cfgResolved = cfgForAgent ?? cfg;

@@ -2328,6 +2328,27 @@ export const agentHandlers: GatewayRequestHandlers = {

23282328

}

23292329

}

23302330
2331+

if (wantsDelivery && deliveryTargetResolutionError) {

2332+

if (!bestEffortDeliver) {

2333+

respond(

2334+

false,

2335+

undefined,

2336+

errorShape(ErrorCodes.INVALID_REQUEST, String(deliveryTargetResolutionError)),

2337+

);

2338+

return;

2339+

}

2340+

deliveryDowngradeReason = String(deliveryTargetResolutionError);

2341+

resolvedChannel = INTERNAL_MESSAGE_CHANNEL;

2342+

deliveryTargetMode = undefined;

2343+

resolvedTo = undefined;

2344+

effectivePlan = {

2345+

...deliveryPlan,

2346+

resolvedChannel,

2347+

resolvedTo,

2348+

deliveryTargetMode,

2349+

};

2350+

}

2351+
23312352

if (!resolvedTo && isDeliverableMessageChannel(resolvedChannel)) {

23322353

const cfgResolved = cfgForAgent ?? cfg;

23332354

const fallback = resolveAgentOutboundTarget({

Original file line numberDiff line numberDiff line change

@@ -313,6 +313,52 @@ describe("agent delivery helpers", () => {

313313

expect(plan.resolvedTo).toBe("1470130713209602050");

314314

});

315315
316+

it("carries explicit target resolution errors from session-route preparation", async () => {

317+

const targetResolutionError = new Error('reserved target "current"');

318+

mocks.resolveOutboundChannelPlugin.mockReturnValue({

319+

messaging: { resolveOutboundSessionRoute: vi.fn() },

320+

});

321+

mocks.resolveOutboundTarget.mockReturnValueOnce({

322+

ok: false,

323+

error: targetResolutionError,

324+

});

325+
326+

const plan = await resolveAgentDeliveryPlanWithSessionRoute({

327+

cfg: {} as OpenClawConfig,

328+

agentId: "agent",

329+

sessionEntry: undefined,

330+

requestedChannel: "workspace",

331+

explicitTo: "current",

332+

accountId: undefined,

333+

wantsDelivery: true,

334+

});

335+
336+

expect(mocks.resolveOutboundSessionRoute).not.toHaveBeenCalled();

337+

expect(plan.resolvedTo).toBe("current");

338+

expect(plan.targetResolutionError).toBe(targetResolutionError);

339+

});

340+
341+

it("surfaces stored explicit target errors even when explicit validation is disabled", () => {

342+

const targetResolutionError = new Error('reserved target "current"');

343+
344+

const resolved = resolveAgentOutboundTarget({

345+

cfg: {} as OpenClawConfig,

346+

plan: {

347+

baseDelivery: { mode: "explicit" },

348+

resolvedChannel: "workspace",

349+

resolvedTo: "current",

350+

deliveryTargetMode: "explicit",

351+

targetResolutionError,

352+

},

353+

targetMode: "explicit",

354+

validateExplicitTarget: false,

355+

});

356+
357+

expect(mocks.resolveOutboundTarget).not.toHaveBeenCalled();

358+

expect(resolved.resolvedTarget).toEqual({ ok: false, error: targetResolutionError });

359+

expect(resolved.resolvedTo).toBeUndefined();

360+

});

361+
316362

it("falls back to the original plan when session-route canonicalization fails", async () => {

317363

mocks.resolveOutboundChannelPlugin.mockReturnValue({

318364

messaging: { resolveOutboundSessionRoute: vi.fn() },

Original file line numberDiff line numberDiff line change

@@ -29,6 +29,7 @@ export type AgentDeliveryPlan = {

2929

resolvedAccountId?: string;

3030

resolvedThreadId?: string | number;

3131

deliveryTargetMode?: ChannelOutboundTargetMode;

32+

targetResolutionError?: Error;

3233

};

3334
3435

export function resolveAgentDeliveryPlan(params: {

@@ -163,7 +164,7 @@ export async function resolveAgentDeliveryPlanWithSessionRoute(

163164

mode: plan.deliveryTargetMode ?? "explicit",

164165

});

165166

if (!normalizedTarget.ok) {

166-

return plan;

167+

return { ...plan, targetResolutionError: normalizedTarget.error };

167168

}

168169

const explicitThreadId =

169170

params.explicitThreadId != null && params.explicitThreadId !== ""

@@ -210,6 +211,13 @@ export function resolveAgentOutboundTarget(params: {

210211

params.targetMode ??

211212

params.plan.deliveryTargetMode ??

212213

(params.plan.resolvedTo ? "explicit" : "implicit");

214+

if (params.plan.targetResolutionError) {

215+

return {

216+

resolvedTarget: { ok: false, error: params.plan.targetResolutionError },

217+

resolvedTo: undefined,

218+

targetMode,

219+

};

220+

}

213221

if (!isDeliverableMessageChannel(params.plan.resolvedChannel)) {

214222

return {

215223

resolvedTarget: null,

Original file line numberDiff line numberDiff line change

@@ -40,6 +40,14 @@ export function unknownTargetError(provider: string, raw: string, hint?: string)

4040

return new Error(unknownTargetMessage(provider, raw, hint));

4141

}

4242
43+

export function reservedTargetLiteralMessage(provider: string, raw: string, hint?: string): string {

44+

return `Reserved target "${raw}" for ${provider} cannot be used as a literal destination. Provide an explicit id or handle.${formatTargetHint(hint, true)}`;

45+

}

46+
47+

export function reservedTargetLiteralError(provider: string, raw: string, hint?: string): Error {

48+

return new Error(reservedTargetLiteralMessage(provider, raw, hint));

49+

}

50+
4351

function formatTargetHint(hint?: string, withLabel = false): string {

4452

const normalized = hint?.trim();

4553

if (!normalized) {

Original file line numberDiff line numberDiff line change

@@ -32,6 +32,52 @@ function resolveChannelPluginForTargetRead(channelId: ChannelId): ChannelPlugin

3232

return getLoadedChannelPluginForRead(channelId) ?? getChannelPlugin(channelId);

3333

}

3434
35+

function normalizeTargetLiteral(value: string): string | undefined {

36+

return normalizeOptionalLowercaseString(value);

37+

}

38+
39+

function stripPluginTargetPrefix(raw: string, plugin: ChannelPlugin): string {

40+

let target = raw.trim();

41+

const prefixes = [plugin.id, ...(plugin.messaging?.targetPrefixes ?? [])]

42+

.map((prefix) => normalizeTargetLiteral(String(prefix)))

43+

.filter((prefix): prefix is string => Boolean(prefix));

44+

while (target) {

45+

const lowered = normalizeTargetLiteral(target) ?? "";

46+

const prefix = prefixes.find((candidate) => lowered.startsWith(`${candidate}:`));

47+

if (!prefix) {

48+

return target;

49+

}

50+

target = target.slice(prefix.length + 1).trim();

51+

}

52+

return target;

53+

}

54+
55+

export function resolveReservedTargetLiteral(params: {

56+

raw?: string;

57+

plugin?: ChannelPlugin;

58+

}): string | undefined {

59+

const raw = normalizeOptionalString(params.raw);

60+

const plugin = params.plugin;

61+

const reservedLiterals = plugin?.messaging?.targetResolver?.reservedLiterals;

62+

if (!raw || !plugin || !reservedLiterals?.length) {

63+

return undefined;

64+

}

65+

const stripped = stripPluginTargetPrefix(raw, plugin);

66+

if (!stripped || /^[@#]/.test(stripped) || /^(channel|group|user):/i.test(stripped)) {

67+

return undefined;

68+

}

69+

const normalized = normalizeTargetLiteral(stripped);

70+

if (!normalized) {

71+

return undefined;

72+

}

73+

const reserved = new Set(

74+

reservedLiterals

75+

.map(normalizeTargetLiteral)

76+

.filter((literal): literal is string => Boolean(literal)),

77+

);

78+

return reserved.has(normalized) ? normalized : undefined;

79+

}

80+
3581

function resetTargetNormalizerCacheForTests(): void {

3682

targetNormalizerCacheByChannelId.clear();

3783

}

@@ -224,10 +270,15 @@ export function buildTargetResolverSignature(

224270

: "pinned";

225271

const resolver = plugin?.messaging?.targetResolver;

226272

const hint = resolver?.hint ?? "";

273+

const reserved = (resolver?.reservedLiterals ?? [])

274+

.map(normalizeTargetLiteral)

275+

.filter((literal): literal is string => Boolean(literal))

276+

.toSorted()

277+

.join(",");

227278

const looksLike = resolver?.looksLikeId;

228279

// Function source is only a cheap invalidation hint; resolver behavior still belongs to the plugin.

229280

const source = looksLike ? looksLike.toString() : "";

230-

return hashSignature(`${registryScope}|${hint}|${source}`);

281+

return hashSignature(`${registryScope}|${hint}|${reserved}|${source}`);

231282

}

232283
233284

function hashSignature(value: string): string {