Skip to content

[codex] add durable message lifecycle delivery#77205

Merged
steipete merged 5 commits into
mainfrom
codex/durable-message-lifecycle
May 6, 2026
Merged

[codex] add durable message lifecycle delivery#77205
steipete merged 5 commits into
mainfrom
codex/durable-message-lifecycle

Conversation

@steipete

@steipete steipete commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR completes the message lifecycle refactor for durable final replies, message-tool sends, live-preview finalization, receipts, and receive acknowledgement policy. It moves bundled channel delivery paths onto openclaw/plugin-sdk/channel-message, so core owns the shared lifecycle and plugins own platform-native I/O.

Core now owns queue intent, retry/recovery state, rendered batch plans, message-sending hooks, receipts, commit/failure hooks, and SDK compatibility shims. Channel plugins own target normalization, native send/edit/delete calls, threading, quotes, silent delivery, pending IDs, account state, and platform-specific receipt construction.

Public API

The primary SDK surface is openclaw/plugin-sdk/channel-message.

Channel plugins express delivery through defineChannelMessageAdapter(...):

  • durableFinal.capabilities: declares the final-delivery features this adapter preserves.
  • send.text(...), send.media(...), and optional payload/batch senders: perform platform I/O and return normalized receipts.
  • send.lifecycle.beforeSendAttempt(...): runs before platform I/O and may return an opaque attempt token.
  • send.lifecycle.afterSendSuccess(...): runs after successful platform I/O with the result, receipt, and attempt token.
  • send.lifecycle.afterSendFailure(...): runs after failed/canceled sends without replacing the primary provider error.
  • send.lifecycle.afterCommit(...): runs after queued ack, recovered commit, or direct best-effort fallback success.
  • live.finalizer: owns live preview edit/delete/final-send transitions.
  • receive: exposes receive context and acknowledgement policy. Plugins that keep platform acknowledgement outside core declare manual policy.

Capability keys include text, media, replyTo, thread, silent, nativeQuote, messageSendingHooks, afterSendSuccess, afterCommit, payload, batch, and reconcileUnknownSend.

Default final-reply delivery and prepared message(action="send") use the shared lifecycle with best_effort queueing unless a caller explicitly requests required durability. Required durable delivery must preflight reconcileUnknownSend and the adapter must both declare the capability and implement durableFinal.reconcileUnknownSend(...). This keeps bundled adapters reachable through the new lifecycle without falsely promising no-duplicate crash recovery.

Compatibility

Existing SDK entry points remain importable for third-party plugins, but are deprecated compatibility wrappers:

  • openclaw/plugin-sdk/channel-reply-pipeline: use openclaw/plugin-sdk/channel-message.
  • createChannelTurnReplyPipeline(...): use createChannelMessageReplyPipeline(...) or a message adapter.
  • deliverDurableInboundReplyPayload(...): use deliverInboundReplyWithMessageSendContext(...).
  • dispatchInboundReplyWithBase(...): use dispatchChannelMessageReplyWithBase(...) for compatibility dispatchers.
  • recordInboundSessionAndDispatchReply(...): use recordChannelMessageReplyDispatch(...) where the old shape is still needed.
  • resolveChannelSourceReplyDeliveryMode(...): use resolveChannelMessageSourceReplyDeliveryMode(...).
  • deliverFinalizableDraftPreview(...): use defineFinalizableLivePreviewAdapter(...), deliverFinalizableLivePreview(...), or deliverWithFinalizableLivePreviewAdapter(...).
  • DraftPreviewFinalizerDraft / DraftPreviewFinalizerResult: use LivePreviewFinalizerDraft / LivePreviewFinalizerResult.

createReplyPrefixContext(...), createReplyPrefixOptions(...), and createTypingCallbacks(...) are available through the message facade for compatibility dispatchers.

Runtime Behavior

  • Durable final replies carry a rendered batch plan with text, media, voice, presentation, interactive payloads, and channel data.
  • Recovery uses explicit state: started/unknown sends require reconciliation before replay; required ack failures are reported instead of silently leaving stale queue entries.
  • Crash recovery for started/unknown sends resolves message adapters through the same channel activation path as outbound delivery before deciding reconciliation is unavailable.
  • Best-effort queue failures can fall back to direct platform I/O while still running lifecycle hooks consistently.
  • Telegram durable sends preserve quotes, silent error replies, media/presentation payloads, and finalized preview receipts.
  • WhatsApp canceled/no-send durable results are terminal and no longer fall through to legacy sends.
  • BlueBubbles pending outbound tracking is reachable through the message adapter lifecycle.
  • Discord/Slack/message-tool sends can build channel-specific payload data while core owns persist/retry/recover/ack.
  • Generic reply dedupe no longer lazily loads bundled channel implementations just to normalize same-target message-tool routes; Telegram reply-suppression fallback remains intact.
  • Native plugin loading still falls back to source transform for source/workspace layouts when native dependency resolution misses.
  • Self-review follow-up: source-checkout plugin loading now forces the source runtime path with a lightweight bundled plugin fixture, and ordinary source-transform fallback preserves the caller native-loading policy.

Real behavior proof

  • Behavior addressed: visible final replies and prepared message(action="send") payloads should use the shared channel-message lifecycle without requiring every bundled adapter to implement unknown-send reconciliation; generic same-target reply dedupe should not hang the auto-reply shard by loading Feishu just to compare message placeholder routes.
  • Real environment tested: local OpenClaw checkout on macOS, branch codex/durable-message-lifecycle-design, PR head 3c92b086b13a90cbcad0bd66455f252207e656f5.
  • Exact steps or command run after this patch:
node --import tsx --input-type=module <<'EOF'
import { shouldDedupeMessagingToolRepliesForRoute } from './src/auto-reply/reply/reply-payloads.js';
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from './src/plugins/runtime.js';
import { createTestRegistry } from './src/test-utils/channel-plugins.js';
resetPluginRuntimeStateForTest();
setActivePluginRegistry(createTestRegistry([]));
console.log(JSON.stringify({
  genericMessageRoute: shouldDedupeMessagingToolRepliesForRoute({ messageProvider: 'feishu', originatingTo: 'ou_abc123', messagingToolSentTargets: [{ tool: 'message', provider: 'message', to: 'ou_abc123' }] }),
  telegramFallback: shouldDedupeMessagingToolRepliesForRoute({ messageProvider: 'telegram', originatingTo: 'telegram:group:-100123:topic:77', messagingToolSentTargets: [{ tool: 'message', provider: 'telegram', to: '-100123', threadId: '77' }] }),
}, null, 2));
EOF
  • Evidence after fix: terminal output from the command above:
{
  "genericMessageRoute": true,
  "telegramFallback": true
}
  • Observed result after fix: the real dedupe code path matches the generic Feishu message placeholder route without loading a channel plugin for normalization, and Telegram's bundled reply-suppression fallback still matches topic replies when the active plugin registry omits Telegram.
  • What was not tested: live Telegram, WhatsApp, iMessage/BlueBubbles, Slack, Discord, Matrix, Mattermost, MSTEams, LINE, and Zalo credentials were not exercised manually in this pass.

Validation

Additional proof on the current branch:

pnpm test src/infra/outbound/delivery-queue.recovery.test.ts src/infra/outbound/channel-resolution.test.ts src/infra/outbound/deliver.test.ts -- --reporter=verbose
# passed: 3 files, 107 tests

pnpm test src/plugins/plugin-module-loader-cache.test.ts src/plugins/native-module-require.test.ts src/plugins/source-checkout-runtime.test.ts -- --reporter=verbose
# passed: source-checkout runtime plus plugin-module-loader/native-require regression set

pnpm test src/auto-reply/reply/reply-payloads.test.ts src/auto-reply/reply/agent-runner-payloads.test.ts -- --reporter=verbose
# passed: 2 files, 59 tests

OPENCLAW_VITEST_MAX_WORKERS=2 OPENCLAW_TEST_PROJECTS_PARALLEL=2 OPENCLAW_VITEST_SHARD_NAME=auto-reply-reply-agent-runner OPENCLAW_VITEST_INCLUDE_FILE=/tmp/openclaw-auto-reply-agent-runner-include.json pnpm exec node scripts/test-projects.mjs test/vitest/vitest.auto-reply-reply.config.ts
# passed: 13 files, 226 tests

OPENCLAW_VITEST_MAX_WORKERS=2 OPENCLAW_TEST_PROJECTS_PARALLEL=2 OPENCLAW_VITEST_SHARD_NAME=auto-reply-reply-state-routing OPENCLAW_VITEST_INCLUDE_FILE=/tmp/openclaw-auto-reply-state-routing-include.json pnpm exec node scripts/test-projects.mjs test/vitest/vitest.auto-reply-reply.config.ts
# passed: 25 files, 399 tests

pnpm test src/auto-reply/reply/agent-runner-payloads.test.ts src/channels/turn/durable-delivery.test.ts src/channels/turn/kernel.test.ts -- --reporter=verbose
# passed: 35 auto-reply payload tests, 27 channel turn tests

pnpm test src/infra/outbound/message.test.ts src/infra/outbound/deliver.test.ts src/channels/message/send.test.ts src/channels/message/capabilities.test.ts src/channels/message/contracts.test.ts -- --reporter=verbose
# passed: 111 tests across infra/channel-message shards

pnpm exec oxfmt --check --threads=1 src/auto-reply/reply/agent-runner-payloads.test.ts src/auto-reply/reply/reply-payloads-dedupe.ts src/channels/turn/durable-delivery.ts src/channels/turn/durable-delivery.test.ts src/channels/turn/kernel.test.ts
# passed

pnpm docs:check-links:anchors docs/plugins/sdk-channel-message.md
# passed

git diff --check
# passed

OPENCLAW_TESTBOX=0 OPENCLAW_LOCAL_CHECK=1 pnpm check:changed
# passed through conflict-marker, changelog, extension export guards, duplicate coverage, runtime sidecar loader, tsgo:all, lint, and import-cycle checks

Testbox was warmed, but the run stayed queued and was stopped cleanly; the broad changed proof above was run locally with Testbox delegation disabled.

Not Tested

Live credentials for bundled messaging platforms were not exercised manually. Proof is from a real local code-path command, SDK checks, channel contract tests, owner-unit tests, durable queue tests, docs checks, and the changed gate.

@steipete

steipete commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper autofix

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: imessage Channel integration: imessage channel: signal Channel integration: signal channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: whatsapp-web Channel integration: whatsapp-web agents Agent runtime and tooling size: XL maintainer Maintainer-authored PR labels May 4, 2026
@steipete steipete added the clawsweeper:autofix Maintainer opted this PR into bounded ClawSweeper-reviewed autofix without merge label May 4, 2026
@steipete steipete force-pushed the codex/durable-message-lifecycle branch 2 times, most recently from 4fd6f75 to af88d5e Compare May 4, 2026 08:46
@clawsweeper

clawsweeper Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR adds openclaw/plugin-sdk/channel-message, durable message lifecycle delivery/recovery/receipt contracts, bundled channel migrations, docs, tests, and package exports.

Reproducibility: not applicable. as a current-main bug reproduction: this PR is a broad feature/refactor. The PR body proves one dedupe code path, but not a current-head live durable delivery or recovery scenario.

Real behavior proof
Needs stronger real behavior proof before merge: The PR body includes terminal output for a dedupe path on an older head, but not current-head real behavior proof for durable final delivery, recovery, or live-preview lifecycle behavior; terminal output, logs, screenshots, recordings, or linked artifacts from the current head would satisfy this gate and should trigger a fresh review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Human follow-up is required because protected maintainer/human-review handling and the real-behavior-proof gap remain; I did not find a narrow repairable code defect for an automated worker.

Security
Cleared: No concrete security or supply-chain regression was found in the inspected diff surfaces.

Review details

Best possible solution:

Land this after a maintainer SDK/channel review and current-head runtime proof for at least one representative durable final-send or recovery/live-preview path.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a current-main bug reproduction: this PR is a broad feature/refactor. The PR body proves one dedupe code path, but not a current-head live durable delivery or recovery scenario.

Is this the best way to solve the issue?

Unclear for merge readiness: the source and tests now cover the prior lifecycle correctness concerns, but this broad public SDK/channel change still needs maintainer review and current-head real behavior proof before it is the best path to merge.

What I checked:

  • PR state and protection: GitHub API reports the PR open, not draft, mergeable, at head 2dbc6958ebaa5d5ff5e2de6dd6164d89e1f98f14 with 322 changed files and protected maintainer plus clawsweeper:human-review labels. (2dbc6958ebaa)
  • Current main lacks requested SDK subpath: Current main's package exports jump from channel-lifecycle to channel-pairing; rg found no defineChannelMessageAdapter/channel-message implementation under the current checkout. (package.json:797, fcb188a41a48)
  • PR adds public channel-message exports: The PR head adds ./plugin-sdk/channel-message and ./plugin-sdk/channel-message-runtime package exports and a facade that re-exports the reply-prefix helpers and message lifecycle contracts. (src/plugin-sdk/channel-message.ts:32, 2dbc6958ebaa)
  • Lifecycle hook isolation checked: Latest head isolates afterSendSuccess and afterSendFailure hook failures so cleanup hooks do not replace the platform send result or original send error. (src/infra/outbound/deliver.ts:254, 2dbc6958ebaa)
  • Recovered-send commit semantics checked: Latest head acks reconciled sent entries before running commit hooks, logs hook failures, and records retry state when acking a reconciled sent entry fails. (src/infra/outbound/delivery-queue-recovery.ts:264, 2dbc6958ebaa)
  • Regression coverage present: PR-head tests cover success-hook isolation, best-effort queue fallback commit hooks, reconciled-send afterCommit, failed ack retry metadata, send-context failure cleanup, required durability, and explicit null thread handling. (src/infra/outbound/deliver.test.ts:819, 2dbc6958ebaa)

Likely related people:

  • steipete: Local blame/log for the central current-main outbound, reply pipeline, and plugin-loader files routes the relevant legacy behavior through Peter Steinberger's recent merged work; the PR also changes that same ownership area. (role: recent maintainer and likely follow-up owner; confidence: medium; commits: e28ad6a8697b; files: src/infra/outbound/deliver.ts, src/infra/outbound/delivery-queue-recovery.ts, src/plugin-sdk/channel-reply-pipeline.ts)

Remaining risk / open question:

  • The PR body's real behavior proof is dedupe-only and references an older head, so durable final delivery, recovery, and live-preview lifecycle behavior still need current-head runtime proof.
  • This is a 322-file public SDK/channel migration across many bundled plugins, so maintainer/channel-owner review remains important even with green CI.

Codex review notes: model gpt-5.5, reasoning high; reviewed against fcb188a41a48.

@clawsweeper

clawsweeper Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper saw the passing review, but the PR needs another repair pass before merge.

Source: clawsweeper[bot]
Feedback: structured ClawSweeper verdict: pass (sha=b20dc91b4fb613e18b177e88a0b597083661658c); PR is behind or has merge conflicts and needs a cloud rebase repair before automerge
Action: repair worker queued. Run: https://api.github.com/repos/openclaw/clawsweeper/actions/runs/25357934238
Model: gpt-5.5

I will update this PR branch, or open a safe credited replacement, if the repair worker finds a narrow CI fix.

Automerge progress:

  • 2026-05-04 23:12:50 UTC review passed dc20eb386439 (structured ClawSweeper verdict: pass (sha=dc20eb386439fb2aa639baf236f79542a8e59...)
  • 2026-05-05 00:51:41 UTC review passed b31d3fe89125 (structured ClawSweeper verdict: pass (sha=b31d3fe8912537926525b0c650c6874631902...)
  • 2026-05-05 01:15:23 UTC review passed 06c2bf99a57c (structured ClawSweeper verdict: pass (sha=06c2bf99a57ce41a4da09d1cd69b28ee0af57...)
  • 2026-05-05 02:33:48 UTC review passed efb228b0988c (structured ClawSweeper verdict: pass (sha=efb228b0988c8f376ac31d8359dfab778ef7c...)
  • 2026-05-05 03:39:28 UTC review passed b174549be1e3 (structured ClawSweeper verdict: pass (sha=b174549be1e31f9e46ee9fb84dd929d0082d5...)
  • 2026-05-05 04:04:20 UTC review passed 4c852bc382f8 (structured ClawSweeper verdict: pass (sha=4c852bc382f89750dc1a1d10d5782e82d9788...)
  • 2026-05-05 04:30:39 UTC review passed 143701952490 (structured ClawSweeper verdict: pass (sha=1437019524906d3fb1e08e8ea1670a14ffd97...)
  • 2026-05-05 04:44:09 UTC review passed b20dc91b4fb6 (structured ClawSweeper verdict: pass (sha=b20dc91b4fb613e18b177e88a0b5970836616...)

@clawsweeper

clawsweeper Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper autofix is enabled.

  • Head: af88d5eef10c
  • Label: clawsweeper:autofix
  • Action: exact-head review queued (workflow sweep.yml, event repository_dispatch).
  • Flow: review this head, repair/rebase only if needed, then re-review the exact repaired head before merge.

This is fix-only; I will not merge this PR.

@steipete steipete force-pushed the codex/durable-message-lifecycle branch 13 times, most recently from ed178d3 to 50de0e6 Compare May 4, 2026 22:40
@clawsweeper clawsweeper Bot force-pushed the codex/durable-message-lifecycle branch from 50de0e6 to dc20eb3 Compare May 4, 2026 23:03
@openclaw-barnacle openclaw-barnacle Bot added channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser labels May 5, 2026
@steipete steipete force-pushed the codex/durable-message-lifecycle branch from a68c719 to 40092a1 Compare May 5, 2026 07:26
@openclaw-barnacle openclaw-barnacle Bot added channel: bluebubbles Channel integration: bluebubbles app: web-ui App: web-ui gateway Gateway runtime channel: nostr Channel integration: nostr channel: qqbot labels May 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6bd79a61b8

ℹ️ 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".

Comment thread src/channels/message/send.ts Outdated
...deliveryParams,
payloads: rendered.payloads,
renderedBatchPlan: rendered.plan,
queuePolicy: "required",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect requested best-effort durability in send context

When withDurableMessageSendContext is created with durability: "best_effort", the actual send still hard-codes queuePolicy: "required", so queue-write failures throw instead of falling back to direct send. This makes the runtime behavior contradict the context’s declared durability and can unexpectedly fail deliveries in environments where the durable queue is temporarily unavailable.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc4c9488f6

ℹ️ 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".

Comment on lines 1174 to +1178
if (queueId) {
if (hadPartialFailure) {
await failDelivery(queueId, "partial delivery failure (bestEffort)").catch(() => {});
} else {
await ackDelivery(queueId).catch(() => {}); // Best-effort cleanup.
const acked = await ackDelivery(queueId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run afterCommit hooks for non-queued sends

When delivery falls back to direct send (queueId is null, e.g. queuePolicy: "best_effort" with queue write failure or explicit skipQueue), this branch skips runOutboundDeliveryCommitHooks(...) entirely, so channel send.lifecycle.afterCommit never runs even though the platform send succeeded. Plugins that use afterCommit to clear pending state can be left inconsistent specifically in fallback/recovery paths.

Useful? React with 👍 / 👎.

Comment thread src/infra/outbound/deliver.ts Outdated
Comment on lines +265 to +266
await params.lifecycle.afterSendFailure?.({
...params.ctx,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve original send error when failure cleanup fails

In the send lifecycle catch path, afterSendFailure is awaited without isolation, so if cleanup itself throws, that new error replaces the original send failure. This only appears when a send fails and failure cleanup also errors, but it obscures root-cause diagnostics and can misclassify queue failure reasons for retries/recovery.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3978dbbbc3

ℹ️ 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".

if (result.status !== "failed") {
await ctx.commit(result.receipt);
} else {
await ctx.fail(result.error);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve original send failure when failure hook throws

When outbound delivery fails, this path awaits ctx.fail(result.error) and will throw if onSendFailure itself errors, which replaces the original delivery failure and changes sendDurableMessageBatch from returning { status: "failed", error } to rejecting with cleanup-hook errors. This only appears when both send and failure cleanup fail, but it obscures root cause and breaks callers that rely on the structured failed result for retry/recovery decisions.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54ac28e396

ℹ️ 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".

Comment on lines +143 to +145
const adapter = await loadChannelMessageAdapter(opts.entry.channel);
if (adapter?.durableFinal?.capabilities?.reconcileUnknownSend !== true) {
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bootstrap message adapter before unknown-send reconciliation

Recovery now calls loadChannelMessageAdapter directly for unknown_after_send entries, but this loader only reads the active registry and does not bootstrap bundled channel plugins. On startup, if the channel plugin has not been hydrated yet, this returns undefined, reconciliation is skipped, and the entry is moved to failed/ without replay, which can permanently drop deliveries after a crash. This path should bootstrap or otherwise ensure the channel message adapter is loaded before deciding reconciliation is unavailable.

Useful? React with 👍 / 👎.

Comment on lines +21 to +25
export {
createChannelReplyPipeline as createChannelMessageReplyPipeline,
createReplyPrefixContext,
resolveChannelSourceReplyDeliveryMode as resolveChannelMessageSourceReplyDeliveryMode,
} from "./channel-reply-core.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-export reply-prefix helpers from channel-message facade

The new openclaw/plugin-sdk/channel-message facade re-exports createReplyPrefixContext but omits createReplyPrefixOptions and createTypingCallbacks, even though those helpers still exist in the message pipeline surface. Plugin code migrating off the deprecated channel-reply-pipeline subpath will hit missing-export compile/runtime errors for these helpers, creating an avoidable SDK compatibility regression.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cee371bf8a

ℹ️ 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".

Comment on lines +123 to +129
const requiredCapabilities =
params.requiredCapabilities ??
deriveDurableFinalDeliveryRequirements({
payload: params.payload,
replyToId,
threadId,
silent: params.silent,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require unknown-send reconciliation for required durability

deliverInboundReplyWithMessageSendContext derives durable requirements without requesting reconcileUnknownSend, so channels can pass preflight and enqueue queuePolicy: "required" sends even when they cannot reconcile unknown_after_send. In this commit, queue entries are marked unknown_after_send once platform send starts and recovery refuses blind replay without reconciliation (moving the entry to failed/), so a crash between send-start and ack can permanently drop a required delivery. This is currently reachable because no bundled production adapter declares reconcileUnknownSend capability.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c29246647

ℹ️ 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".

Comment on lines +264 to +269
} catch (ackErr) {
if (getErrnoCode(ackErr) === "ENOENT") {
return "already-gone";
}
return "failed";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record retry metadata when ack after reconciliation fails

When reconcileUnknownSend returns sent but ackDelivery(...) throws a non-ENOENT error, this branch returns "failed" immediately without calling failDelivery(...). That leaves retryCount/lastAttemptAt unchanged, so the same entry is treated as first-attempt eligible on every recovery run and can loop indefinitely without backoff or max-retry cutoff if ack keeps failing (for example, persistent FS permission issues).

Useful? React with 👍 / 👎.

Comment thread src/channels/turn/durable-delivery.ts Outdated
function resolveThreadId(
params: DurableInboundReplyDeliveryParams,
): string | number | null | undefined {
return params.threadId ?? params.ctxPayload.MessageThreadId;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve explicit null thread override in durable delivery

This uses ?? to resolve the thread id, so an explicit threadId: null is discarded and replaced with ctxPayload.MessageThreadId. That prevents callers from intentionally clearing thread targeting in durable mode, and replies can be forced back into the inbound thread even when the delivery adapter requested no thread.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper is pausing this repair loop for human review.

Source: clawsweeper[bot]
Reason: Human follow-up is required because the PR has protected maintainer/human-review handling and still needs runtime proof for a broad public SDK/channel lifecycle change.; Cleared: No concrete security or supply-chain regression was found in the inspected PR surfaces. (sha=5b66274700ecec7b73af84d069e4128b81802a0e)

I added clawsweeper:human-review and left the final call with a maintainer.

@clawsweeper

clawsweeper Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper is pausing this repair loop for human review.

Source: clawsweeper[bot]
Reason: Human follow-up is required because the PR has protected maintainer/human-review handling and the remaining blocker is runtime proof plus SDK/channel ownership review, not a narrow repairable code defect.; Cleared: No concrete security or supply-chain regression was found in the inspected PR surfaces. (sha=aa7545e803e6698779cc69ff2f3381ff535eddac)

I added clawsweeper:human-review and left the final call with a maintainer.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa7545e803

ℹ️ 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".

Comment thread src/infra/outbound/deliver.ts Outdated
result,
...(attemptToken !== undefined ? { attemptToken } : {}),
};
await params.lifecycle.afterSendSuccess?.(successCtx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not fail delivery when afterSendSuccess hook throws

If params.send() succeeds but afterSendSuccess throws, this path rethrows as if the platform send failed, so callers receive a failed send even though the message may already be delivered. In queued flows this can trigger unnecessary retries/recovery state changes, and in direct flows callers may retry manually, producing duplicate outbound messages; the hook failure should be isolated from the actual send outcome.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c33613d03d

ℹ️ 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".

Comment on lines +266 to +269
await ackDelivery(entry.id, opts.stateDir);
opts.onRecovered?.(entry);
opts.log.info(`Delivery entry ${entry.id} reconciled unknown_after_send as already sent`);
return "recovered";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run afterCommit lifecycle on reconciled-sent recovery

When reconcileUnknownSend returns status: "sent", this branch acks the queue entry and returns immediately, but it never executes any commit lifecycle (afterCommit) for that delivery. That means adapters that rely on commit-time cleanup (for example, clearing pending outbound state after a crash) are skipped specifically in the send_attempt_started / unknown_after_send recovery path, leaving plugin state inconsistent even though recovery marks the delivery as recovered.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2dbc6958eb

ℹ️ 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".

Comment on lines +300 to 302
fallbackOnMissingDependency: true,
fallbackOnNativeError: true,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve native loader errors instead of falling back unconditionally

Passing fallbackOnNativeError: true here causes every native require failure (including plugin runtime exceptions) to be treated as a cache miss and retried through the source-transform loader. When a plugin module executes side effects before throwing at top level, this path can execute those side effects twice and leave duplicate registrations or partially-mutated process state before ultimately failing. Restricting fallback to resolution/format errors avoids re-running modules that already failed during execution.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper is pausing this repair loop for human review.

Source: clawsweeper[bot]
Reason: Human follow-up is required because protected maintainer/human-review handling and the real-behavior-proof gap remain; I did not find a narrow repairable code defect for an automated worker.; Cleared: No concrete security or supply-chain regression was found in the inspected diff surfaces. (sha=2dbc6958ebaa5d5ff5e2de6dd6164d89e1f98f14)

I added clawsweeper:human-review and left the final call with a maintainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: qa-channel Channel integration: qa-channel channel: qqbot channel: signal Channel integration: signal channel: slack Channel integration: slack channel: synology-chat channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser clawsweeper:autofix Maintainer opted this PR into bounded ClawSweeper-reviewed autofix without merge clawsweeper:human-review Needs maintainer review before ClawSweeper can continue docs Improvements or additions to documentation extensions: diagnostics-otel Extension: diagnostics-otel gateway Gateway runtime maintainer Maintainer-authored PR scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant