Skip to content

feat: make page/admin policy + owner changes take effect live (onConfigEvent + owner sync + reported-policy push) - #5

Merged
athanbase merged 2 commits into
mainfrom
feat/owner-sync-policy-config-port
Jul 22, 2026
Merged

feat: make page/admin policy + owner changes take effect live (onConfigEvent + owner sync + reported-policy push)#5
athanbase merged 2 commits into
mainfrom
feat/owner-sync-policy-config-port

Conversation

@athanbase

Copy link
Copy Markdown
Collaborator

Why

Page/admin-configured permissions were not taking effect: the plugin never registered the SDK's onConfigEvent callback, so all six agent.config.* access-change events and owner_changed were silently dropped (SDK classifies them at orchestrator.js:550, then hands them to callbacks.onConfigEvent — which was undefined here). Ported the equivalent logic from zylos-openmax (source of truth).

Changes

Logic 1 — change-message → live config sync

  • Register onConfigEventhandleConfigEvent.
  • Six access events (dm_policy / dm_allowlist / group_mode / group_allowfrom / group_scope / group_allowlist) mutate orgConfig.access in place — the same object the SDK's decideInbound reads per inbound message, so gates re-read immediately, no restart — then persist to config.json (persistAccess).
  • Pure event→access reducer extracted as applyConfigEvent(access, event, data) in helpers.ts (unit-tested).

Logic 2 — owner sync

  • owner_changedsyncOwnerFromCore: fetch /members/{self}, sync owner + display_name into live orgConfig + persistOwner. Never clears a local owner when core has none (preserves first-DM auto-bind).
  • 5-min ownerSyncTimer (unref'd, cleared on shutdown) heals missed events; event-driven path retained. Matches zylos-openmax's owner-config-sync timer cadence.

Reverse push — local policy → cws-comm

  • syncConfigToComm: PUT /agents/{memberId}/reported-policy via http.putForOrg, payload shape mirrors zylos byte-for-byte (dm_policy/dm_allowlist/group_scope/group_allowlist/groups[]). 404 → skip quietly (endpoint may land later). Called on every access change (config-event epilogue) and in the 5-min tick, mirroring zylos's two call sites.

Tests

npm test15 pass / 0 fail (+2 for buildReportedPolicy, existing +7 for applyConfigEvent). helpers.ts strict-typechecks clean.

Notes / open items

  • Event data field names follow the zylos source; if cws-comm's live payload keys differ, extraction needs adjusting (graceful no-op on mismatch).
  • reported-policy endpoint must accept the same field names on the openclaw deployment; degrades gracefully (404 → skip) if not yet deployed.

Commits: 8dc71f9 (logics 1+2), 8550879 (reverse push).

Zylos and others added 2 commits July 22, 2026 15:51
Port two logics from zylos-openmax (source of truth) so page/admin-configured
permissions actually take effect in the openclaw-openmax bridge.

The openclaw SDK (CwsAgentBridge) already classifies agent.config.* system
frames and applies the "not for us" target check, then hands them to
callbacks.onConfigEvent(orgConfig, {event,data,frame}). The plugin was not
supplying that callback, so all six policy events + owner_changed were silently
dropped. Wire it in.

Logic 1 — six agent.config.* access events → live config + persist:
  - applyConfigEvent() in helpers.ts (pure, unit-tested): dm_policy_changed,
    dm_allowlist_changed, group_mode_changed, group_allowfrom_changed,
    group_scope_changed, group_allowlist_changed → mutate OpenMaxAccessConfig.
  - handleConfigEvent() in index.ts mutates the SAME orgConfig.access object the
    SDK's decideInbound gate reads (live effect, no restart) then persistAccess()
    writes it to config.json (durability across restart), mirroring persistOwner.

Logic 2 — owner sync:
  - owner_changed event → syncOwnerFromCore(): fetch /members/{self.member_id}
    from cws-core, sync self.display_name + owner into the live orgConfig, persist
    via the existing persistOwner path. Never clears a local owner when core has
    none (preserves the SDK first-DM auto-bind fallback).
  - Periodic 5-min owner/display-name sync timer (OWNER_SYNC_INTERVAL_MS),
    unref'd, cleared in stopBridge — matches zylos-openmax's owner-config-sync
    task; the SDK arms no owner timer of its own. Event-driven path is kept.

Tests: 7 new applyConfigEvent cases (13 total, all pass under `npm test`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port zylos-openmax's syncConfigToComm (comm-bridge.js:1875-1912): the agent
PUTs its local DM/group access policy to cws-comm's
/agents/{memberId}/reported-policy so the server reflects offline config.json
edits or a fresh install's pre-populated policy.

- helpers.ts: buildReportedPolicy(access) — pure payload builder mirroring
  zylos's shape/defaults exactly (dm_policy/dm_allowlist/group_scope/
  group_allowlist/groups[]; dmPolicy→'owner', groupPolicy→'allowlist',
  per-group mode→'mention', allowFrom→['*']).
- index.ts: syncConfigToComm(orgConfig) uses http.putForOrg; 404 → skip quietly
  (endpoint not available), matching zylos. Wired into BOTH call sites zylos
  uses: the config-change epilogue in handleConfigEvent (immediate report,
  mirrors comm-bridge.js:1300) and the 5-min ownerSyncTimer tick after the
  owner pull (mirrors periodicSync).

Tests: 2 new buildReportedPolicy cases (15 total, all pass under `npm test`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@zylos-luna-coco zylos-luna-coco 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.

APPROVED.

Reviewed 428 lines across 3 files. Clean port of the zylos-openmax config-event + owner-sync + reported-policy-push pattern to OpenClaw.

Architecture is sound:

  • Pure helpers in helpers.ts (testable without host): applyConfigEvent handles all 6 access events with proper validation against VALID_* sets; buildReportedPolicy builds the reverse-push payload with correct zylos defaults.
  • Integration in index.ts: live mutation of the same orgConfig object the SDK gate reads (no restart needed), clone-before-write persist for durability, and the 3-layer owner convergence (event-driven + periodic 5-min timer + reconnect) consistent with hermes-openmax PR #10.

Key correctness points verified:

  1. owner_changed correctly routed to syncOwnerFromCore (not applyConfigEvent) — treated as refresh hint, Core is authoritative.
  2. Never clears local owner when Core reports none — preserves first-DM auto-bind fallback.
  3. persistAccess uses structuredClone before write — safe from mutation bugs.
  4. Timer .unref() — won't keep the process alive; properly cleared in stopBridge.
  5. syncConfigToComm 404 → skip quietly (endpoint may not exist on older cws-comm).

Test coverage (105 lines): all 6 access event types + edge cases (invalid policies, empty arrays, idempotent adds, silent-mode deletion, unknown events). owner_changed explicitly asserted as not an access mutation. buildReportedPolicy tested with both empty and populated access.

No issues found.

Co-Reviewed-By: luna.coco

@athanbase
athanbase merged commit 5e2b358 into main Jul 22, 2026
3 checks passed
@athanbase
athanbase deleted the feat/owner-sync-policy-config-port branch July 22, 2026 08:26
@athanbase athanbase mentioned this pull request Jul 22, 2026
athanbase added a commit that referenced this pull request Jul 22, 2026
Live policy/owner-change propagation (onConfigEvent) + owner sync timer +
reported-policy reverse push (PR #5). Bump plugin version 1.0.0 -> 1.1.0 and
update clone-tag references in README + onboarding.

Co-authored-by: Zylos <zylos@coco.xyz>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants