




























@@ -315,6 +315,148 @@ Supplemental context covers quote, forwarded, and thread-bootstrap context. The
315315316316Media is fact-shaped. Platform download, auth, SSRF policy, CDN rules, and decryption stay channel-local. The kernel maps facts into `MediaPath`, `MediaUrl`, `MediaType`, `MediaPaths`, `MediaUrls`, `MediaTypes`, and `MediaTranscribedIndexes`.
317317318+Use `toInboundMediaFacts(...)` from `openclaw/plugin-sdk/channel-inbound` when
319+your channel has a resolved media list and only needs to attach generic facts:
320+321+```typescript
322+media: toInboundMediaFacts(resolvedMedia, {
323+ kind: "image",
324+ messageId: input.id,
325+});
326+```
327+328+If media mixes local files and URL-only entries, keep the list as media facts.
329+Core preserves array indexes when it writes legacy context fields so downstream
330+media understanding, transcription markers, and prompt notes continue to refer
331+to the same attachment.
332+333+For skipped group messages that should be available to a later mention, pass
334+media facts through the turn `preflight.media` field. The kernel converts those
335+facts into bounded history media entries before recording:
336+337+```typescript
338+preflight(input) {
339+return {
340+ admission: { kind: "drop", reason: "missing_mention", recordHistory: true },
341+media: () => toInboundMediaFacts(resolveLocalImages(input), {
342+ kind: "image",
343+ messageId: input.id,
344+ }),
345+ history: {
346+ key: historyKey,
347+ limit: historyLimit,
348+ mediaLimit: 4,
349+shouldRecord: () => stillCurrent(input),
350+ },
351+ };
352+}
353+```
354+355+History media is intentionally conservative: image-only today, local readable
356+paths only, bounded by the configured media limit, and still tied to the
357+channel history key. Authenticated provider URLs should be downloaded by the
358+plugin before they become model-visible media.
359+360+## History windows
361+362+Message-turn code should use `createChannelHistoryWindow(...)` instead of
363+calling low-level `reply-history` map helpers directly. The window facade keeps
364+text context, structured `InboundHistory`, history-media normalization, and
365+clearing behind one core-owned API while still letting the channel choose how a
366+history line is rendered.
367+368+```typescript
369+const history = createChannelHistoryWindow({ historyMap: groupHistories });
370+371+await history.recordWithMedia({
372+historyKey,
373+ limit: historyLimit,
374+entry,
375+media: () =>
376+toInboundMediaFacts(resolvedImages, {
377+ kind: "image",
378+ messageId: entry.messageId,
379+ }),
380+});
381+382+const combinedBody = history.buildPendingContext({
383+historyKey,
384+ limit: historyLimit,
385+currentMessage,
386+formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
387+});
388+```
389+390+The older `buildPendingHistoryContextFromMap`,
391+`buildInboundHistoryFromMap`, `recordPendingHistoryEntry*`, and
392+`clearHistoryEntriesIfEnabled` exports remain for compatibility with plugins
393+that have not migrated yet. New channel work should use the window or the turn
394+kernel record/finalize options.
395+396+## Common message patterns
397+398+Text-only group with mention required:
399+400+```typescript
401+preflight(input) {
402+const decision = resolveInboundMentionDecision({ facts, policy });
403+if (decision.shouldSkip) {
404+return {
405+ admission: { kind: "drop", reason: "missing_mention", recordHistory: true },
406+ history: { key: historyKey, limit: historyLimit },
407+ };
408+ }
409+return { access: { mentions: decision } };
410+}
411+```
412+413+Image-only message followed by a later mention:
414+415+```typescript
416+preflight(input) {
417+if (!wasMentioned && resolvedImages.length > 0) {
418+return {
419+ admission: { kind: "drop", reason: "missing_mention", recordHistory: true },
420+media: () => toInboundMediaFacts(resolvedImages, {
421+ kind: "image",
422+ messageId: input.id,
423+ }),
424+ history: { key: historyKey, limit: historyLimit, mediaLimit: 4 },
425+ };
426+ }
427+return {};
428+}
429+```
430+431+Explicit reply-to-image:
432+433+```typescript
434+resolveTurn(input, _eventClass, preflight) {
435+return {
436+...assembled,
437+ media: toInboundMediaFacts([...currentMedia, ...referencedReplyMedia]),
438+ supplemental: {
439+ quote: preflight.supplemental?.quote,
440+ },
441+ };
442+}
443+```
444+445+Direct message with history:
446+447+```typescript
448+resolveTurn(input) {
449+return {
450+...assembled,
451+ history: undefined,
452+ message: {
453+ rawBody: input.rawText,
454+ bodyForAgent: input.textForAgent,
455+ },
456+ };
457+}
458+```
459+318460## Adapter contract
319461320462For full `run`, the adapter shape is:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。