feat(channel-discovery): add purpose column + PATCH /api/channels/:id
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.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Get, Param, Post, Query, Req, UnauthorizedException } from '@nestjs/common';
|
||||
import { BadRequestException, Body, Controller, Get, Param, Patch, Post, Query, Req, UnauthorizedException } from '@nestjs/common';
|
||||
import { ChannelsService } from './channels.service.js';
|
||||
|
||||
// ApiKeyGuard attaches the introspected Center user id onto the request.
|
||||
@@ -32,11 +32,33 @@ export class ChannelsController {
|
||||
bypassUserIds: Array.isArray(body.bypassUserIds)
|
||||
? (body.bypassUserIds as string[])
|
||||
: [],
|
||||
purpose: body.purpose as string | undefined,
|
||||
},
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
// Patch a channel's free-form purpose. Body: { purpose: string }. Pass
|
||||
// empty string to clear. Auth: channel member (or anyone for public
|
||||
// channels, mirroring close()). Frontend doesn't call this today —
|
||||
// intended for agent-side use (fabric-channel-set-purpose tool).
|
||||
@Patch(':id')
|
||||
patch(
|
||||
@Req() req: AuthedRequest,
|
||||
@Param('id') channelId: string,
|
||||
@Body() body: Record<string, unknown>,
|
||||
) {
|
||||
const userId = req.userId ?? '';
|
||||
if (!userId) throw new UnauthorizedException('missing user');
|
||||
// Only `purpose` is patchable today. Future patchable fields would
|
||||
// get their own typed branch; we explicitly NOT allow {} no-op patches
|
||||
// because that signals a caller bug.
|
||||
if (typeof body.purpose !== 'string') {
|
||||
throw new BadRequestException('purpose (string) is required');
|
||||
}
|
||||
return this.channelsService.updatePurpose(channelId, userId, body.purpose);
|
||||
}
|
||||
|
||||
// Move an order member into the bypass list (discuss/work only).
|
||||
@Post(':id/bypass')
|
||||
bypass(
|
||||
|
||||
Reference in New Issue
Block a user