Adds a free-form 'purpose' text field on Channel so agents (or anyone
creating a channel via API) can describe what the channel is for —
'debate broadcasts', 'security alerts', etc. — and other agents can
later find the right channel by intent rather than channel id.
Wire:
- Channel.purpose (text, nullable; TypeORM synchronize auto-adds)
- POST /api/channels accepts optional 'purpose' in body
- GET /api/channels returns purpose on every row (already returns the
full entity via {...c})
- PATCH /api/channels/:id { purpose } — member-or-public auth (mirrors
the close() rule). Today only 'purpose' is patchable; other fields
would get their own typed branch.
Frontend create form continues to omit the field — purpose stays optional.
This pairs with Fabric.OpenclawPlugin's fabric-channel-set-purpose tool +
fabric-channel-list returning purpose, so agent workflows can say 'find
an announce channel about X' instead of pinning a UUID.
- channel_turn_state.bypass_user_ids: order and bypass form a disjoint
partition of the channel members; bypass excluded from rotation.
- initForChannel(channelId, members, bypass=[]) computes order = members
− bypass; create() passes bypassUserIds (∩ members) for discuss/work.
- pushFrame() enforces mention nesting cap: max 4 sub-frames (5 levels
incl. root); overflow evicts the bottom-most (root->A..D + E => root->B..E).
- mention sites use pushFrame so bypass members are only transiently
pulled in via @-mention, then return to bypass on pop.
- moveToBypass(): move an order member to bypass mid-rotation; if current
speaker, successor takes over. onMemberRemoved also strips bypass.
- POST /channels/:id/bypass; GET :id/members now returns {userId,bypass}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
package.json type=module, tsconfig module/moduleResolution=NodeNext,
target es2022, explicit .js on all relative imports. Center: jsonwebtoken
& bcryptjs switched to default imports (ESM/CJS interop). Verified:
builds, boots, full auth + plugin round-trip work under ESM.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Channel.closed; POST /channels/:id/close (member-only); message/command
posts on closed channel -> 409 {error:channel_closed}; GET history still
allowed; listForUser carries closed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Channel.x_type enum(general|work|report|discuss|triage|custom); required
and validated on channel creation (400 if missing/invalid).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- new channel_members table; creator always added, plus selected members
- Channel.isPublic (default false): public channels visible to all guild
members; non-public only to explicit members
- GET /channels filters to channels visible to the requesting user
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>