mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 16:41:37 +00:00
* feat(sdk): always persist media facts and ship facts-first replacements for legacy Media* surfaces
PR 1 of the media legacy retirement program (audit-frozen, 4 PRs).
- Every media-bearing user turn now persists normalized __openclaw.media
facts unconditionally while continuing to emit the legacy top-level
Media* projection byte-identically (dual-write bridge; the conditional
shouldPersistStructuredMediaEntries gate now always includes media).
- New replacement APIs, shipped before any removal: typed hook media
facts (media[], originalMedia[], mediaStagingPending) on message
events; {{AttachmentPath}}/{{AttachmentUrl}}/{{AttachmentContentType}}/
{{AttachmentDir}}/{{AttachmentIndex}} template variables; focused
openclaw/plugin-sdk/media-local-roots subpath split out of the
deprecated agent-media-payload facade.
- Every legacy surface carries @deprecated naming its replacement, under
one named compatibility record media-legacy-projection with the
operator-approved removeAfter 2026-10-01 (two release trains; deletion
additionally gates on a clean published-plugin artifact sweep).
- Generic transcript append invariant documented; SDK migration, hooks,
and configuration docs updated to the facts-first path.
Writer golden matrix proves legacy bytes and model prompt bytes are
unchanged while nested facts become unconditional. 2,189 broad media
tests green; SDK api-baseline regenerated on fresh-env Testbox.
* feat(sdk): register media-local-roots subpath exports and deprecation metadata
Completes PR 1: package export map for openclaw/plugin-sdk/media-local-roots
plus the deprecated-subpath inventory and doc metadata entries for the
media-legacy-projection record.
* chore(sdk): track media-local-roots entrypoint and deprecated-export budgets
* fix(sdk): keep deprecated MSTeams buildMediaPayload re-export through the compat window
Deleting shipped runtime-api re-exports belongs to retirement PR 4 after
the media-legacy-projection window; PR 1 only deprecates. Also formats
the migration-guide schedule table.
* docs: regenerate docs map for media migration additions
196 lines
7.1 KiB
TypeScript
196 lines
7.1 KiB
TypeScript
import type { MessageHookMediaFact } from "../hooks/message-hook-media.js";
|
|
import type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js";
|
|
import type { PluginConversationBinding } from "./conversation-binding.types.js";
|
|
|
|
/** Ordered media fact exposed by inbound message hooks. */
|
|
export type PluginHookMediaFact = MessageHookMediaFact;
|
|
|
|
/** Provider metadata plus deprecated media aliases retained during the SDK migration window. */
|
|
export type PluginHookInboundMessageMetadata = Record<string, unknown> & {
|
|
/** @deprecated Use the first `event.media` fact with a defined `path`. */
|
|
mediaPath?: string;
|
|
/** @deprecated Use the first `event.media` fact's `url ?? path`. */
|
|
mediaUrl?: string;
|
|
/** @deprecated Use the first `event.media` fact's `contentType ?? kind`. */
|
|
mediaType?: string;
|
|
/** @deprecated Collect defined `path` values from `event.media` in order. */
|
|
mediaPaths?: string[];
|
|
/** @deprecated Collect each defined `url ?? path` from `event.media` in order. */
|
|
mediaUrls?: string[];
|
|
/** @deprecated Collect each defined `contentType ?? kind` from `event.media` in order. */
|
|
mediaTypes?: string[];
|
|
/** @deprecated Use the first `event.originalMedia` fact with a defined `path`. */
|
|
originalMediaPath?: string;
|
|
/** @deprecated Use the first `event.originalMedia` fact's `url ?? path`. */
|
|
originalMediaUrl?: string;
|
|
/** @deprecated Use the first `event.originalMedia` fact's `contentType ?? kind`. */
|
|
originalMediaType?: string;
|
|
/** @deprecated Collect defined `path` values from `event.originalMedia` in order. */
|
|
originalMediaPaths?: string[];
|
|
/** @deprecated Collect each defined `url ?? path` from `event.originalMedia` in order. */
|
|
originalMediaUrls?: string[];
|
|
/** @deprecated Collect each defined `contentType ?? kind` from `event.originalMedia` in order. */
|
|
originalMediaTypes?: string[];
|
|
/** @deprecated Use `event.mediaStagingPending`. */
|
|
mediaStagingPending?: boolean;
|
|
};
|
|
|
|
export type PluginHookMessageContext = {
|
|
channelId: string;
|
|
accountId?: string;
|
|
conversationId?: string;
|
|
/**
|
|
* Canonical session key for this conversation — the same value the agent
|
|
* runtime sees as `params.sessionKey` for the run that produced the
|
|
* outbound payload, and the same value `agent_end`/`llm_input`/`llm_output`
|
|
* fire with. Plugins correlating per-turn state across `agent_end` and
|
|
* `message_sending` rely on this equality.
|
|
*
|
|
* For inbound message hooks (`inbound_claim` etc.), this is the canonical
|
|
* session for the inbound conversation as resolved by `resolveSessionKey`
|
|
* / `deriveInboundMessageHookContext`.
|
|
*
|
|
* For outbound delivery hooks (`message_sending` and `message_sent`),
|
|
* this mirrors `OutboundSessionContext.key` from the dispatch path when
|
|
* delivery has a session attached. When the outbound path has no
|
|
* resolvable session (e.g. internal smoke runs without
|
|
* `OutboundSessionContext`), this field is omitted; plugins must treat
|
|
* it as optional.
|
|
*/
|
|
sessionKey?: string;
|
|
/**
|
|
* Per-turn run identifier (UUID), unique to one end-to-end agent turn:
|
|
* stable across all LLM-call iterations, retry attempts (compaction,
|
|
* empty-response, planning-only, etc.), and multi-payload reply chunks
|
|
* within that turn; distinct for each new inbound user message and for
|
|
* each cron/heartbeat/followup-triggered run.
|
|
*
|
|
* Generated once in `agent-runner-execution.ts`/`followup-runner.ts` via
|
|
* `crypto.randomUUID()`. Currently populated for inbound message hooks
|
|
* (`inbound_claim`, `message_received`) and for agent-runtime hooks that
|
|
* already receive the run id (e.g. `agent_end`, `llm_input`, `llm_output`).
|
|
* It is **not yet** plumbed through the outbound delivery path, so
|
|
* plugins observing `message_sending` / `message_sent` should not rely
|
|
* on `runId` to correlate against `agent_end`; use `sessionKey` for
|
|
* outbound→inbound correlation today (with the caveat that it cannot
|
|
* disambiguate concurrent turns in the same session).
|
|
*/
|
|
runId?: string;
|
|
messageId?: string;
|
|
senderId?: string;
|
|
replyToId?: string;
|
|
replyToIdFull?: string;
|
|
replyToBody?: string;
|
|
replyToSender?: string;
|
|
replyToIsQuote?: boolean;
|
|
trace?: DiagnosticTraceContext;
|
|
traceId?: string;
|
|
spanId?: string;
|
|
parentSpanId?: string;
|
|
callDepth?: number;
|
|
};
|
|
|
|
export type PluginHookInboundClaimContext = PluginHookMessageContext & {
|
|
/** Resolved owner for session scopes whose canonical key does not encode an agent id. */
|
|
agentId?: string;
|
|
parentConversationId?: string;
|
|
senderId?: string;
|
|
messageId?: string;
|
|
pluginBinding?: PluginConversationBinding;
|
|
};
|
|
|
|
export type PluginHookInboundClaimEvent = {
|
|
content: string;
|
|
body?: string;
|
|
bodyForAgent?: string;
|
|
transcript?: string;
|
|
timestamp?: number;
|
|
channel: string;
|
|
accountId?: string;
|
|
conversationId?: string;
|
|
parentConversationId?: string;
|
|
senderId?: string;
|
|
senderName?: string;
|
|
senderUsername?: string;
|
|
replyToId?: string;
|
|
replyToIdFull?: string;
|
|
replyToBody?: string;
|
|
replyToSender?: string;
|
|
replyToIsQuote?: boolean;
|
|
threadId?: string | number;
|
|
messageId?: string;
|
|
sessionKey?: string;
|
|
runId?: string;
|
|
trace?: DiagnosticTraceContext;
|
|
traceId?: string;
|
|
spanId?: string;
|
|
parentSpanId?: string;
|
|
isGroup: boolean;
|
|
commandAuthorized?: boolean;
|
|
senderIsOwner?: boolean;
|
|
wasMentioned?: boolean;
|
|
/** Staged, locally usable attachments in stable source order. */
|
|
media?: PluginHookMediaFact[];
|
|
/** Original attachment facts when local staging has not completed yet. */
|
|
originalMedia?: PluginHookMediaFact[];
|
|
/** True when `originalMedia` is present but `media` is intentionally withheld pending staging. */
|
|
mediaStagingPending?: boolean;
|
|
metadata?: PluginHookInboundMessageMetadata;
|
|
};
|
|
|
|
export type PluginHookMessageReceivedEvent = {
|
|
from: string;
|
|
content: string;
|
|
timestamp?: number;
|
|
threadId?: string | number;
|
|
messageId?: string;
|
|
senderId?: string;
|
|
replyToId?: string;
|
|
replyToIdFull?: string;
|
|
replyToBody?: string;
|
|
replyToSender?: string;
|
|
replyToIsQuote?: boolean;
|
|
sessionKey?: string;
|
|
runId?: string;
|
|
trace?: DiagnosticTraceContext;
|
|
traceId?: string;
|
|
spanId?: string;
|
|
parentSpanId?: string;
|
|
/** Staged, locally usable attachments in stable source order. */
|
|
media?: PluginHookMediaFact[];
|
|
/** Original attachment facts when local staging has not completed yet. */
|
|
originalMedia?: PluginHookMediaFact[];
|
|
/** True when `originalMedia` is present but `media` is intentionally withheld pending staging. */
|
|
mediaStagingPending?: boolean;
|
|
metadata?: PluginHookInboundMessageMetadata;
|
|
};
|
|
|
|
export type PluginHookMessageSendingEvent = {
|
|
to: string;
|
|
content: string;
|
|
replyToId?: string | number;
|
|
threadId?: string | number;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type PluginHookMessageSendingResult = {
|
|
content?: string;
|
|
cancel?: boolean;
|
|
cancelReason?: string;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type PluginHookMessageSentEvent = {
|
|
to: string;
|
|
content: string;
|
|
success: boolean;
|
|
messageId?: string;
|
|
sessionKey?: string;
|
|
runId?: string;
|
|
trace?: DiagnosticTraceContext;
|
|
traceId?: string;
|
|
spanId?: string;
|
|
parentSpanId?: string;
|
|
error?: string;
|
|
};
|