[plugin sdk] Add host-mediated session attachments#75581
Conversation
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: not applicable. for a current-main bug because this is an additive Plugin SDK feature. The PR regression is source-reproducible by following Real behavior proof Next step before merge Security Review findings
Review detailsBest possible solution: Keep the bundled-only host-mediated seam, forward the selected live config through route lookup, validation, and outbound delivery, then add redacted public-SDK-path runtime proof before maintainer review. Do we have a high-confidence way to reproduce the issue? Not applicable for a current-main bug because this is an additive Plugin SDK feature. The PR regression is source-reproducible by following Is this the best way to solve the issue? No, not merge-ready as written. The seam shape is plausible, but the outbound handoff should use the same live config snapshot and the proof should exercise a bundled plugin calling the public SDK method after registration. Full review comments:
Overall correctness: patch is incorrect Security concerns:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 5534c2e480c1. |
321a8bd to
4a9bb5f
Compare
6c8b0c6 to
69ce0ad
Compare
|
Fixed in |
69ce0ad to
9ab5cc0
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69ce0ade32
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a new Plugin SDK seam for host-mediated session attachments, allowing bundled plugins to request host validation and delivery of local files through the active session route, plus related outbound formatting/threading plumbing (notably Telegram parse-mode wiring) and delivery-route lookup hardening.
Changes:
- Add
OpenClawPluginApi.sendSessionAttachmentwith SDK-exported types and host-side implementation/validation. - Harden delivery-route lookup (
extractDeliveryInfo) across per-agent session stores and freshest-entry selection. - Plumb
parseMode/formatting through outbound messaging and Telegram adapters; re-export Telegram formatting helpers for plugin authors.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/plugins/types.ts | Exposes new attachment params/result types and adds sendSessionAttachment to the plugin API type. |
| src/plugins/registry.ts | Wires sendSessionAttachment into the registry with side-effects + stale-registry/loaded checks. |
| src/plugins/host-hooks.ts | Defines the new attachment types (PluginSessionAttachmentParams, hints, result union). |
| src/plugins/host-hook-workflow.ts | Implements host-side attachment validation, delivery-route resolution, and sendMessage dispatch. |
| src/plugins/contracts/session-attachments.contract.test.ts | Adds contract tests for validation, hint precedence, Telegram escaping, and stale-registry protection. |
| src/plugins/captured-registration.ts | Adds a stub implementation for captured registrations. |
| src/plugins/api-builder.ts | Adds handler wiring + noop fallback for sendSessionAttachment. |
| src/plugin-sdk/plugin-test-api.ts | Adds sendSessionAttachment stub to the test plugin API. |
| src/plugin-sdk/plugin-entry.ts | Re-exports attachment types from the SDK entry surface. |
| src/plugin-sdk/core.ts | Re-exports attachment types from the SDK core surface. |
| src/infra/outbound/message.ts | Adds parseMode to sendMessage params and forwards it to outbound formatting (direct delivery). |
| src/infra/outbound/formatting.ts | Extends outbound formatting options with parseMode. |
| src/config/sessions/delivery-info.ts | Enhances delivery-context lookup across per-agent stores and prefers freshest entries; supports injected cfg. |
| src/config/sessions/delivery-info.test.ts | Updates/extends tests for per-agent store lookup and numeric threadId behavior. |
| extensions/telegram/src/outbound-adapter.ts | Threads formatting into direct Telegram outbound adapter and sets textMode based on parseMode. |
| extensions/telegram/src/format.ts | Exports escapeTelegramHtml for reuse by plugin authors. |
| extensions/telegram/src/channel.ts | Threads formatting into Telegram channel sends and maps parseMode to textMode. |
| extensions/telegram/api.ts | Re-exports Telegram formatting helpers/types. |
| extensions/telegram/api.test.ts | Adds coverage verifying Telegram helper re-exports behave as expected. |
| docs/.generated/plugin-sdk-api-baseline.sha256 | Updates SDK API baseline hashes. |
| CHANGELOG.md | Documents the new sendSessionAttachment feature. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2bdf9477a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06abbcd273
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea56729b49
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9871e48e1c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
a623e9a to
2620cee
Compare
|
Rebased onto upstream/main 69c1487 (2026-05-08) — squash-rebase Because this branch carried 19 incremental follow-up commits that each touched the same set of conflicting files ( Conflict resolutions:
Force-pushed to |
a25bad5 to
14a6c63
Compare
|
@clawsweeper re-review Rebased onto current upstream/main, resolved the live review findings, kept the session-attachment seam narrow, and added the real behavior proof block to the PR body on head |
|
@clawsweeper re-review Fresh head |
|
Fresh head Why I used a no-op commit instead of widening the diff:
|
|
Fresh head Reason for this second follow-up: the earlier empty retrigger commit only woke the PR-body/label workflows; it did not create a new |
Squashed rebase of PR #75581 onto upstream/main (rebase-2026-05-08). Drops PR's outbound-base.ts and macOS GatewayModels.swift since upstream already deleted those and integrated the attachment delivery surface into createTelegramOutboundAdapter. Original commits in PR include 19 follow-up fixes; squashed for clean rebase. Refresh plugin-sdk-api-baseline. Replaces part of #73384/#74483.
c7bb819 to
38cb8e5
Compare
|
Superseded by #80229, which now carries the combined proof and merge-ready consolidation branch. |
Squashed rebase of PR #75581 onto upstream/main (rebase-2026-05-08). Drops PR's outbound-base.ts and macOS GatewayModels.swift since upstream already deleted those and integrated the attachment delivery surface into createTelegramOutboundAdapter. Original commits in PR include 19 follow-up fixes; squashed for clean rebase. Refresh plugin-sdk-api-baseline. Replaces part of #73384/#74483.
Why this matters
OpenClaw plugins increasingly need to send real files back to the user in the same session lane: support artifacts, generated reports, approval docs, exports, screenshots, and workflow evidence.
Without a generic seam, plugin authors end up with two bad options:
This PR adds the missing middle layer: a bundled plugin can ask the host to validate local files and deliver them through the active direct-outbound session route, while OpenClaw keeps ownership of channel credentials, delivery routing, formatting quirks, and transport behavior.
What kinds of plugins this could support
This seam is intentionally generic. It can support many plugin families without adding plugin-specific delivery logic to core:
The important part: the plugin decides what artifact to send. OpenClaw decides whether the file is safe and where the active session route actually goes.
New SDK seam
This PR adds:
OpenClawPluginApi.sendSessionAttachment(...)Example plugin usage:
Runtime architecture
The plugin never talks directly to Telegram, Slack, or another channel transport. It asks the host to deliver a validated file to the already-active session route.
Safety and boundaries
This stays intentionally narrow:
This is a host-mediated attachment seam, not a generic channel SDK.
What changed
OpenClawPluginApi.sendSessionAttachmentplus SDK exports for attachment params and results.parseModethrough outbound message formatting and the Telegram direct/gateway adapters.sendSessionAttachmentcallable afterregister()so a real bundled plugin can capture it during registration and invoke it later from a live callback or handler.Relationship to adjacent seams
This does not try to solve scheduling, session actions, session projection, or run-context lifecycle. It is deliberately narrower: one host-mediated path for delivering plugin-owned files through the active session route.
Non-goals
Stack context
Real behavior proof
Behavior or issue addressed:
This fixes the narrow host-mediated attachment seam without broadening it into direct channel ownership. Before these follow-up fixes, a captured
sendSessionAttachmentclosure could be closed accidentally by the loader guard, relative attachment paths could resolve against the wrong workspace, and Telegram media captions could be forced through the HTML path even when HTML was not explicitly requested. After this patch, the loader keepssendSessionAttachmentcallable afterregister(), the host resolves relative paths against the session agent workspace, and the active direct-outbound route receives the validated file plus caption text.Real environment tested:
Standalone runtime proof from
/Users/lume/openclaw-review-worktrees/pr-75581-rebaseusing a live session store, an agent workspace rooted in a fresh temp state directory, and a direct outbound proof channel registered in the active plugin registry.Exact steps or command run after this patch:
Evidence after fix:
{ "result": { "ok": true, "channel": "proofchat", "deliveredTo": "12345", "count": 1 }, "deliveries": [ { "to": "12345", "text": "attachment ready", "mediaUrl": "/var/folders/l7/tyvn8qfs50v57w__75s3k0f00000gp/T/openclaw-pr75581-proof-pHeAVB/workspace/proof-report.txt", "accountId": "default" } ] }Observed result after fix:
The bundled-only attachment dispatch succeeds, the relative attachment path resolves against the session agent workspace instead of falling back to the default host workspace, and the active direct-outbound route receives the resolved file plus caption text. The loader regression is also covered on this head by
pnpm test src/plugins/loader.test.ts, which provessendSessionAttachmentstays callable afterregister()while registration-only APIs still close.What was not tested:
This proof did not exercise a full external Telegram/Slack/Discord live account or an end-to-end GUI flow. The runtime proof here is intentionally limited to real host validation, session-route lookup, workspace-relative path resolution, and direct outbound attachment dispatch inside a live OpenClaw runtime.
Validation
pnpm test src/plugins/loader.test.tspnpm test src/config/sessions/delivery-info.test.ts src/plugins/contracts/session-attachments.contract.test.ts extensions/telegram/api.test.ts extensions/telegram/src/outbound-adapter.test.ts extensions/telegram/src/channel.gateway.test.tspnpm plugin-sdk:api:checkpnpm lint:corepnpm lint:extensionsgit diff --check