Skip to content

Plugins: add binding resolution callbacks#48678

Merged
huntharo merged 6 commits intoopenclaw:mainfrom
huntharo:codex/plugin-bind-resolution-hook
Mar 17, 2026
Merged

Plugins: add binding resolution callbacks#48678
huntharo merged 6 commits intoopenclaw:mainfrom
huntharo:codex/plugin-bind-resolution-hook

Conversation

@huntharo
Copy link
Copy Markdown
Member

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: plugin bind requests only returned a pending reply, so plugins had to infer approval later by polling or lazily hydrating local state.
  • Why it matters: that delayed cleanup/finalization made bind UX brittle, especially for immediate post-approval actions like topic rename, starter messages, and clearing stale pending state after deny.
  • What changed: added a core plugin callback onConversationBindingResolved(...) that fires on both approval and denial, with the resolved decision and request conversation metadata.
  • What did NOT change (scope boundary): this PR does not change who is allowed to bind a conversation or weaken core-owned binding checks.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

User-visible / Behavior Changes

Bound plugins can now react immediately when a bind approval is resolved:

  • approved binds can finalize local state and send post-bind follow-ups immediately
  • denied binds can clear stale local pending intent immediately

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22 / pnpm
  • Model/provider: n/a
  • Integration/channel (if any): plugin binding approvals
  • Relevant config (redacted): local gateway + plugin test harnesses

Steps

  1. Create a plugin bind request and leave it pending.
  2. Resolve the approval through the core binding resolver.
  3. Verify the owning plugin receives the resolution callback.

Expected

  • Approved resolutions deliver the bound conversation to the owning plugin immediately.
  • Denied resolutions deliver the denial decision and let the plugin clear stale local pending state immediately.

Actual

  • Core now emits onConversationBindingResolved(...) for both approve and deny, with regression coverage.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • pnpm test -- src/plugins/conversation-binding.test.ts
    • pnpm build
  • Edge cases checked:
    • approve callback
    • deny callback
    • request conversation metadata is included in the callback payload
  • What you did not verify:
    • no manual multi-surface runtime test in this repo; plugin-side usage was verified separately in the plugin repo

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert commit d6a1ee89bb
  • Files/config to restore: src/plugins/types.ts, src/plugins/registry.ts, src/plugins/conversation-binding.ts
  • Known bad symptoms reviewers should watch for: plugins not receiving bind resolution callbacks, or duplicate callback delivery

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: a plugin could throw inside the new resolution callback and mask approval cleanup.
    • Mitigation: core catches callback failures, logs them, and does not block the underlying binding resolution.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 17, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Potential event-loop starvation / DoS via queueMicrotask dispatch of plugin conversation-binding resolved callbacks

1. 🟡 Potential event-loop starvation / DoS via queueMicrotask dispatch of plugin conversation-binding resolved callbacks

Property Value
Severity Medium
CWE CWE-400
Location src/plugins/conversation-binding.ts:772-784

Description

The new dispatchPluginConversationBindingResolved schedules plugin callbacks via queueMicrotask(). In Node.js, microtasks run before the event loop proceeds to I/O phases, and they also run before subsequently scheduled macrotasks.

This introduces a denial-of-service risk and may undermine the stated goal of keeping platform interaction acknowledgements fast:

  • Starvation risk: a plugin’s conversationBindingResolved handler can execute CPU-bound synchronous work (or long microtask chains) when invoked from the microtask queue, blocking the event loop before it can process pending I/O.
  • Ack delay still possible: because the microtask is queued during resolvePluginConversationBindingApproval(), it can run before the caller’s continuation (promise reaction) that would send the platform acknowledgement, meaning a slow synchronous plugin handler can still delay the ack.
  • No isolation/limits: notifyPluginConversationBindingResolved runs handlers in-process and sequentially with await registration.handler(event) and has no timeout, concurrency limit, or backpressure. A malicious or buggy plugin can therefore degrade overall service.

Vulnerable code:

queueMicrotask(() => {
  void notifyPluginConversationBindingResolved(params).catch((error) => {
    log.warn(`plugin binding resolved dispatch failed: ${String(error)}`);
  });
});

Recommendation

Use a macrotask-based deferral (or a bounded background queue) so the platform ack can be flushed before executing plugin work, and add isolation controls.

Safer scheduling option (yields to I/O):

function dispatchPluginConversationBindingResolved(params: {...}): void {
  setImmediate(() => {
    void notifyPluginConversationBindingResolved(params).catch((error) => {
      log.warn(`plugin binding resolved dispatch failed: ${String(error)}`);
    });
  });
}

Additionally consider:

  • Enforcing a timeout around each plugin handler (e.g., Promise.race with a timer / AbortSignal.timeout).
  • Running handlers concurrently with a small cap (e.g., p-limit) or queueing/coalescing resolved events to avoid unbounded buildup.
  • Tracking and dropping/deferring callbacks when the system is under load.

Analyzed PR: #48678 at commit 6d7b32b

Last updated on: 2026-03-17T17:49:35Z

@openclaw-barnacle openclaw-barnacle bot added size: M maintainer Maintainer-authored PR labels Mar 17, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 17, 2026

Greptile Summary

This PR adds an onConversationBindingResolved callback to the plugin API so that plugins can react immediately when a bind approval is approved or denied, rather than having to poll or lazily hydrate local state. The implementation is clean and well-contained: a new notifyPluginConversationBindingResolved helper is inserted into both the deny and approve branches of resolvePluginConversationBindingApproval, callback failures are caught and logged without blocking the underlying resolution, and the registry wiring correctly follows the existing "full" vs. no-op registration mode pattern.

  • The public PluginConversationBindingResolvedEvent type uses a flat shape with binding?: PluginConversationBinding instead of a discriminated union, which loses the type-safety already present in the internal PluginBindingResolveResult — plugin authors narrowing on status === "approved" won't get a compile-time guarantee that binding is defined or that decision !== "deny".
  • The new callback tests cover allow-once (approve) and deny, but the allow-always decision path (which takes a slightly different code path via addPersistentApproval) is not exercised by the new callback tests.
  • The beforeEach reset correctly installs a fresh empty registry, preventing cross-test handler leakage.

Confidence Score: 4/5

  • Safe to merge with minor type-safety improvement recommended for the public event type.
  • The core logic is correct, the callback is properly guarded against handler exceptions, and the resolution flow is not altered. The main concern is a type-safety gap in the public PluginConversationBindingResolvedEvent API (flat type vs. discriminated union) and a missing test for the allow-always callback path — neither is a runtime bug, but the type issue is a public API concern that is easiest to fix before this ships to plugin authors.
  • src/plugins/types.ts — the PluginConversationBindingResolvedEvent type should use a discriminated union to match the internal PluginBindingResolveResult pattern and give plugin consumers proper TypeScript narrowing.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugins/types.ts
Line: 960-976

Comment:
**Use a discriminated union for type-safe narrowing**

`PluginConversationBindingResolvedEvent` is a flat type with `binding?: PluginConversationBinding` and `decision: PluginConversationBindingResolutionDecision`. This means TypeScript won't prevent plugin authors from accessing `event.binding` as possibly `undefined` when `event.status === "approved"`, and won't narrow out `"deny"` from `decision` when the status is `"approved"`. The internal `PluginBindingResolveResult` already models this correctly with a discriminated union, and the public contract should follow the same pattern.

```suggestion
export type PluginConversationBindingResolvedEvent =
  | {
      status: "approved";
      binding: PluginConversationBinding;
      decision: Exclude<PluginConversationBindingResolutionDecision, "deny">;
      request: {
        summary?: string;
        detachHint?: string;
        requestedBySenderId?: string;
        conversation: {
          channel: string;
          accountId: string;
          conversationId: string;
          parentConversationId?: string;
          threadId?: string | number;
        };
      };
    }
  | {
      status: "denied";
      binding?: never;
      decision: "deny";
      request: {
        summary?: string;
        detachHint?: string;
        requestedBySenderId?: string;
        conversation: {
          channel: string;
          accountId: string;
          conversationId: string;
          parentConversationId?: string;
          threadId?: string | number;
        };
      };
    };
```

With this shape, narrowing on `event.status === "approved"` will guarantee `event.binding` is defined and exclude `"deny"` from `event.decision`, and narrowing on `"denied"` will guarantee `event.decision === "deny"` — consistent with how `PluginBindingResolveResult` is already typed internally.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/plugins/conversation-binding.test.ts
Line: 350-406

Comment:
**Missing `allow-always` callback coverage**

The two new tests cover `allow-once` approval and `deny`, but the `allow-always` decision path through `resolvePluginConversationBindingApproval` is not exercised by the new callback tests. `allow-always` takes a slightly different code path — it first calls `addPersistentApproval` before `bindConversationNow` — so a dedicated test confirming the callback fires (and carries `decision: "allow-always"`) after the persistent-approval write would complete the regression suite and guard against a future regression where, for example, an early return or exception inside `addPersistentApproval` silently skips the notification.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: d6a1ee8

Comment on lines +960 to +976
export type PluginConversationBindingResolvedEvent = {
status: "approved" | "denied";
binding?: PluginConversationBinding;
decision: PluginConversationBindingResolutionDecision;
request: {
summary?: string;
detachHint?: string;
requestedBySenderId?: string;
conversation: {
channel: string;
accountId: string;
conversationId: string;
parentConversationId?: string;
threadId?: string | number;
};
};
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Use a discriminated union for type-safe narrowing

PluginConversationBindingResolvedEvent is a flat type with binding?: PluginConversationBinding and decision: PluginConversationBindingResolutionDecision. This means TypeScript won't prevent plugin authors from accessing event.binding as possibly undefined when event.status === "approved", and won't narrow out "deny" from decision when the status is "approved". The internal PluginBindingResolveResult already models this correctly with a discriminated union, and the public contract should follow the same pattern.

Suggested change
export type PluginConversationBindingResolvedEvent = {
status: "approved" | "denied";
binding?: PluginConversationBinding;
decision: PluginConversationBindingResolutionDecision;
request: {
summary?: string;
detachHint?: string;
requestedBySenderId?: string;
conversation: {
channel: string;
accountId: string;
conversationId: string;
parentConversationId?: string;
threadId?: string | number;
};
};
};
export type PluginConversationBindingResolvedEvent =
| {
status: "approved";
binding: PluginConversationBinding;
decision: Exclude<PluginConversationBindingResolutionDecision, "deny">;
request: {
summary?: string;
detachHint?: string;
requestedBySenderId?: string;
conversation: {
channel: string;
accountId: string;
conversationId: string;
parentConversationId?: string;
threadId?: string | number;
};
};
}
| {
status: "denied";
binding?: never;
decision: "deny";
request: {
summary?: string;
detachHint?: string;
requestedBySenderId?: string;
conversation: {
channel: string;
accountId: string;
conversationId: string;
parentConversationId?: string;
threadId?: string | number;
};
};
};

With this shape, narrowing on event.status === "approved" will guarantee event.binding is defined and exclude "deny" from event.decision, and narrowing on "denied" will guarantee event.decision === "deny" — consistent with how PluginBindingResolveResult is already typed internally.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/types.ts
Line: 960-976

Comment:
**Use a discriminated union for type-safe narrowing**

`PluginConversationBindingResolvedEvent` is a flat type with `binding?: PluginConversationBinding` and `decision: PluginConversationBindingResolutionDecision`. This means TypeScript won't prevent plugin authors from accessing `event.binding` as possibly `undefined` when `event.status === "approved"`, and won't narrow out `"deny"` from `decision` when the status is `"approved"`. The internal `PluginBindingResolveResult` already models this correctly with a discriminated union, and the public contract should follow the same pattern.

```suggestion
export type PluginConversationBindingResolvedEvent =
  | {
      status: "approved";
      binding: PluginConversationBinding;
      decision: Exclude<PluginConversationBindingResolutionDecision, "deny">;
      request: {
        summary?: string;
        detachHint?: string;
        requestedBySenderId?: string;
        conversation: {
          channel: string;
          accountId: string;
          conversationId: string;
          parentConversationId?: string;
          threadId?: string | number;
        };
      };
    }
  | {
      status: "denied";
      binding?: never;
      decision: "deny";
      request: {
        summary?: string;
        detachHint?: string;
        requestedBySenderId?: string;
        conversation: {
          channel: string;
          accountId: string;
          conversationId: string;
          parentConversationId?: string;
          threadId?: string | number;
        };
      };
    };
```

With this shape, narrowing on `event.status === "approved"` will guarantee `event.binding` is defined and exclude `"deny"` from `event.decision`, and narrowing on `"denied"` will guarantee `event.decision === "deny"` — consistent with how `PluginBindingResolveResult` is already typed internally.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +350 to +406
it("notifies the owning plugin when a bind approval is approved", async () => {
const registry = createEmptyPluginRegistry();
const onResolved = vi.fn(async () => undefined);
registry.conversationBindingResolvedHandlers.push({
pluginId: "codex",
pluginRoot: "/plugins/callback-test",
handler: onResolved,
source: "/plugins/callback-test/index.ts",
rootDir: "/plugins/callback-test",
});
setActivePluginRegistry(registry);

const request = await requestPluginConversationBinding({
pluginId: "codex",
pluginName: "Codex App Server",
pluginRoot: "/plugins/callback-test",
requestedBySenderId: "user-1",
conversation: {
channel: "discord",
accountId: "isolated",
conversationId: "channel:callback-test",
},
binding: { summary: "Bind this conversation to Codex thread abc." },
});

expect(request.status).toBe("pending");
if (request.status !== "pending") {
throw new Error("expected pending bind request");
}

const approved = await resolvePluginConversationBindingApproval({
approvalId: request.approvalId,
decision: "allow-once",
senderId: "user-1",
});

expect(approved.status).toBe("approved");
expect(onResolved).toHaveBeenCalledWith({
status: "approved",
binding: expect.objectContaining({
pluginId: "codex",
pluginRoot: "/plugins/callback-test",
conversationId: "channel:callback-test",
}),
decision: "allow-once",
request: {
summary: "Bind this conversation to Codex thread abc.",
detachHint: undefined,
requestedBySenderId: "user-1",
conversation: {
channel: "discord",
accountId: "isolated",
conversationId: "channel:callback-test",
},
},
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing allow-always callback coverage

The two new tests cover allow-once approval and deny, but the allow-always decision path through resolvePluginConversationBindingApproval is not exercised by the new callback tests. allow-always takes a slightly different code path — it first calls addPersistentApproval before bindConversationNow — so a dedicated test confirming the callback fires (and carries decision: "allow-always") after the persistent-approval write would complete the regression suite and guard against a future regression where, for example, an early return or exception inside addPersistentApproval silently skips the notification.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/conversation-binding.test.ts
Line: 350-406

Comment:
**Missing `allow-always` callback coverage**

The two new tests cover `allow-once` approval and `deny`, but the `allow-always` decision path through `resolvePluginConversationBindingApproval` is not exercised by the new callback tests. `allow-always` takes a slightly different code path — it first calls `addPersistentApproval` before `bindConversationNow` — so a dedicated test confirming the callback fires (and carries `decision: "allow-always"`) after the persistent-approval write would complete the regression suite and guard against a future regression where, for example, an early return or exception inside `addPersistentApproval` silently skips the notification.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

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

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: d6a1ee89bb

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

conversation: params.request.conversation,
},
};
await registration.handler(event);
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 Run resolution callbacks off the approval hot path

resolvePluginConversationBindingApproval now waits for each registered onConversationBindingResolved handler, so a slow plugin callback (for example, network I/O) can stall approval handling long enough to miss interaction deadlines. This is user-visible because channel handlers await resolvePluginConversationBindingApproval before completing their response flow (e.g., Discord/Telegram button handling), so approvals can succeed internally while the user sees timeout/failed-interaction errors; callback execution should be detached or time-bounded so it cannot block the approval response path.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle bot added gateway Gateway runtime extensions: lobster Extension: lobster labels Mar 17, 2026
Copy link
Copy Markdown

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

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: 18e35a720c

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

conversation: params.request.conversation,
},
};
await registration.handler(event);
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 Move resolution callbacks off the approval hot path

Awaiting registration.handler(event) here puts arbitrary plugin work (including network I/O) directly on the approval response path. Fresh evidence: Discord, Telegram, and Slack each await resolvePluginConversationBindingApproval(...) before sending their interaction acknowledgements (extensions/discord/src/monitor/agent-components.ts:860, extensions/telegram/src/bot-handlers.ts:1232, extensions/slack/src/monitor/events/interactions.block-actions.ts:480), so one slow callback can push the response past platform deadlines and produce timeout/failed-interaction UX even when the approval itself succeeded.

Useful? React with 👍 / 👎.

@huntharo huntharo force-pushed the codex/plugin-bind-resolution-hook branch from 18e35a7 to 0d2d7e7 Compare March 17, 2026 12:24
@huntharo huntharo self-assigned this Mar 17, 2026
Copy link
Copy Markdown

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

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: 2abaef2efa

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

request: PendingPluginBindingRequest;
}): void {
// Keep platform interaction acks fast even if the plugin does slow post-bind work.
queueMicrotask(() => {
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 Defer callback dispatch past interaction-ack continuation

dispatchPluginConversationBindingResolved now uses queueMicrotask, but that still runs before the caller resumes after await resolvePluginConversationBindingApproval(...); in the Discord/Telegram/Slack approval handlers that means plugin callback code executes before the interaction acknowledgement path. If a plugin callback does synchronous work (or chains sync-heavy handlers before its first await), approvals can still hit timeout UX even though binding succeeded, so this should be moved to a macrotask-style detachment (setImmediate/setTimeout(0)) or another fully off-path dispatcher. Fresh evidence is the new queueMicrotask(...) scheduling added here, which preserves pre-ack execution order.

Useful? React with 👍 / 👎.

@huntharo huntharo force-pushed the codex/plugin-bind-resolution-hook branch from 22e1708 to dd31601 Compare March 17, 2026 16:05
@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Mar 17, 2026
@huntharo huntharo force-pushed the codex/plugin-bind-resolution-hook branch from dd31601 to 6d7b32b Compare March 17, 2026 17:10
@openclaw-barnacle openclaw-barnacle bot added size: S and removed gateway Gateway runtime extensions: lobster Extension: lobster size: M labels Mar 17, 2026
@huntharo huntharo merged commit 272d6ed into openclaw:main Mar 17, 2026
24 of 36 checks passed
@huntharo
Copy link
Copy Markdown
Member Author

Merged via squash.

Thanks @huntharo!

Copy link
Copy Markdown

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

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: 6d7b32b184

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

request: PendingPluginBindingRequest;
}): void {
// Keep platform interaction acks fast even if the plugin does slow post-bind work.
queueMicrotask(() => {
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 Schedule resolution callbacks after caller continuation

Using queueMicrotask here still keeps plugin callback execution on the interaction-ack critical path: Discord/Telegram/Slack all await resolvePluginConversationBindingApproval(...) before sending their acknowledgement/update, and microtasks queued inside this function can run before those callers resume. In environments where a plugin handler does synchronous work (or substantial pre-await CPU work), approvals can still miss platform response deadlines even though the bind decision succeeded, so this needs a true off-path dispatch (e.g., macrotask/background queue) rather than a microtask.

Useful? React with 👍 / 👎.

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 17, 2026
* main: (142 commits)
  fix(zalouser): fix setup-only onboarding flow (openclaw#49219)
  CI: add built plugin singleton smoke (openclaw#48710)
  update contributing focus areas
  docs(providers): clarify provider capabilities vs public capability model
  docs(refactor): align plugin SDK plan with public capability model
  docs(cli): add plugins inspect command reference
  docs(plugins): document public capability model, plugin shapes, and inspection
  Plugins: internalize diagnostics OTel imports
  Plugins: internalize diffs SDK imports
  Plugins: internalize more extension SDK imports
  Plugins: add local extension API barrels
  Plugins: add inspect matrix and trim export
  Plugins: add inspect command and capability report
  fix(telegram): unify transport fallback chain (openclaw#49148)
  Plugins: add binding resolution callbacks (openclaw#48678)
  fix(gateway): clear trusted-proxy control ui scopes
  refactor: narrow extension public seams
  test: stabilize memory async search close
  docs(hooks): clarify trust model and audit guidance
  feat(mattermost): add retry logic and timeout handling for DM channel creation (openclaw#42398)
  ...
nikolaisid pushed a commit to nikolaisid/openclaw that referenced this pull request Mar 18, 2026
Merged via squash.

Prepared head SHA: 6d7b32b
Co-authored-by: huntharo <[email protected]>
Co-authored-by: huntharo <[email protected]>
Reviewed-by: @huntharo
brandontyler pushed a commit to brandontyler/clawdbot that referenced this pull request Mar 19, 2026
Merged via squash.

Prepared head SHA: 6d7b32b
Co-authored-by: huntharo <[email protected]>
Co-authored-by: huntharo <[email protected]>
Reviewed-by: @huntharo
pholpaphankorn pushed a commit to pholpaphankorn/openclaw that referenced this pull request Mar 22, 2026
Merged via squash.

Prepared head SHA: 6d7b32b
Co-authored-by: huntharo <[email protected]>
Co-authored-by: huntharo <[email protected]>
Reviewed-by: @huntharo
ralyodio pushed a commit to ralyodio/openclaw that referenced this pull request Apr 3, 2026
Merged via squash.

Prepared head SHA: 6d7b32b
Co-authored-by: huntharo <[email protected]>
Co-authored-by: huntharo <[email protected]>
Reviewed-by: @huntharo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant