feat: add source field to messages for system notifications
- Enhanced TeamDataService to include a 'source' field in message payloads, specifically for system notifications. - Updated InboxMessage type to accommodate the new 'system_notification' source. - Modified TeamInboxWriter to conditionally include the source field in the message payload. - Added tests to verify the inclusion and omission of the source field based on request parameters.
This commit is contained in:
parent
8da7e1f8e2
commit
17775274a0
6 changed files with 64 additions and 25 deletions
|
|
@ -862,6 +862,7 @@ export class TeamDataService {
|
|||
from: leadName,
|
||||
text: parts.join('\n'),
|
||||
summary: `New task #${task.id} assigned`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -906,6 +907,7 @@ export class TeamDataService {
|
|||
from: leadName,
|
||||
text: parts.join('\n'),
|
||||
summary: `Task #${task.id} started`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -961,6 +963,7 @@ export class TeamDataService {
|
|||
from: last.actor,
|
||||
text: `Task #${task.id} "${task.subject}" has been started by ${last.actor}.`,
|
||||
summary: `Task #${task.id} started`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn(`[TeamDataService] notifyLeadOnTeammateTaskStart failed: ${String(error)}`);
|
||||
|
|
@ -1072,6 +1075,7 @@ export class TeamDataService {
|
|||
from: leadName,
|
||||
text: parts.join('\n'),
|
||||
summary: `Comment on #${taskId}`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
} else if (task && owner && this.isLeadOwner(owner, leadName)) {
|
||||
// Notify lead about user's comment on their own task.
|
||||
|
|
@ -1088,6 +1092,7 @@ export class TeamDataService {
|
|||
from: 'user',
|
||||
text: parts.join('\n'),
|
||||
summary: `Comment on #${taskId}`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -1208,6 +1213,7 @@ export class TeamDataService {
|
|||
`node "${toolPath}" --team ${teamName} review request-changes ${taskId} --comment "..."\n` +
|
||||
AGENT_BLOCK_CLOSE,
|
||||
summary: `Review request for #${taskId}`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
} catch (error) {
|
||||
await this.kanbanManager
|
||||
|
|
@ -1307,6 +1313,7 @@ export class TeamDataService {
|
|||
for (const msg of messages) {
|
||||
if (!msg.messageId || !msg.summary || msg.from === 'user') continue;
|
||||
if (msg.source === 'lead_session' || msg.source === 'lead_process') continue;
|
||||
if (msg.source === 'system_notification') continue;
|
||||
if (isAutomatedCommentNotification(msg)) continue;
|
||||
|
||||
const textKey = `${msg.from}\0${msg.text}`;
|
||||
|
|
@ -1490,6 +1497,7 @@ export class TeamDataService {
|
|||
`${patch.comment?.trim() || 'Reviewer requested changes.'}\n\n` +
|
||||
`Please fix and mark it as completed when ready.`,
|
||||
summary: `Fix request for #${taskId}`,
|
||||
source: 'system_notification',
|
||||
});
|
||||
} catch (error) {
|
||||
await this.taskWriter
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export class TeamInboxWriter {
|
|||
summary: request.summary,
|
||||
messageId,
|
||||
attachments: attachmentMeta?.length ? attachmentMeta : undefined,
|
||||
...(request.source && { source: request.source }),
|
||||
};
|
||||
|
||||
await withInboxLock(inboxPath, async () => {
|
||||
|
|
|
|||
|
|
@ -1416,28 +1416,25 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
: undefined
|
||||
}
|
||||
headerExtra={
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="pointer-events-auto size-6 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void window.electronAPI.openExternal(
|
||||
'https://github.com/777genius/claude-notifications-go'
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Bell size={12} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Desktop notifications plugin</TooltipContent>
|
||||
</Tooltip>
|
||||
}
|
||||
defaultOpen
|
||||
action={
|
||||
<div className="flex items-center gap-2 pl-2">
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="pointer-events-auto size-6 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void window.electronAPI.openExternal(
|
||||
'https://github.com/777genius/claude-notifications-go'
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Bell size={12} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Desktop notifications plugin</TooltipContent>
|
||||
</Tooltip>
|
||||
{messagesUnreadCount > 0 && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
|
@ -1455,6 +1452,11 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
<TooltipContent side="bottom">Mark all as read</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
defaultOpen
|
||||
action={
|
||||
<div className="flex items-center gap-2 pl-2">
|
||||
<div className="flex w-36 items-center gap-1.5 rounded-md border border-[var(--color-border)] bg-transparent px-2 py-1">
|
||||
<Search size={12} className="shrink-0 text-[var(--color-text-muted)]" />
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -290,10 +290,13 @@ export const ActivityTimeline = ({
|
|||
const currSessionId = getItemSessionId(item);
|
||||
if (prevSessionId && currSessionId && prevSessionId !== currSessionId) {
|
||||
sessionSeparator = (
|
||||
<div className="flex items-center gap-3 py-4">
|
||||
<div
|
||||
className="flex items-center gap-3"
|
||||
style={{ paddingTop: 30, paddingBottom: 30 }}
|
||||
>
|
||||
<div className="h-px flex-1 bg-[var(--color-border-emphasis)]" />
|
||||
<span className="whitespace-nowrap text-[11px] text-[var(--color-text-muted)]">
|
||||
Новая сессия
|
||||
New session
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-[var(--color-border-emphasis)]" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ export interface InboxMessage {
|
|||
summary?: string;
|
||||
color?: string;
|
||||
messageId?: string;
|
||||
source?: 'inbox' | 'lead_session' | 'lead_process' | 'user_sent';
|
||||
source?: 'inbox' | 'lead_session' | 'lead_process' | 'user_sent' | 'system_notification';
|
||||
attachments?: AttachmentMeta[];
|
||||
/** Lead session ID that produced this message (for session boundary detection). */
|
||||
leadSessionId?: string;
|
||||
|
|
@ -207,6 +207,7 @@ export interface SendMessageRequest {
|
|||
summary?: string;
|
||||
from?: string;
|
||||
attachments?: AttachmentPayload[];
|
||||
source?: InboxMessage['source'];
|
||||
}
|
||||
|
||||
export interface SendMessageResult {
|
||||
|
|
|
|||
|
|
@ -130,4 +130,28 @@ describe('TeamInboxWriter', () => {
|
|||
expect(persisted).toHaveLength(2);
|
||||
expect(persisted.map((row) => row.text).sort()).toEqual(['first', 'second']);
|
||||
});
|
||||
|
||||
it('includes source field in payload when provided in request', async () => {
|
||||
await writer.sendMessage('my-team', {
|
||||
member: 'alice',
|
||||
text: 'task assigned',
|
||||
summary: 'New task #1 assigned',
|
||||
source: 'system_notification',
|
||||
});
|
||||
|
||||
const persisted = JSON.parse(hoisted.files.get(inboxPath) ?? '[]') as Record<string, unknown>[];
|
||||
expect(persisted).toHaveLength(1);
|
||||
expect(persisted[0].source).toBe('system_notification');
|
||||
});
|
||||
|
||||
it('omits source field from payload when not provided in request', async () => {
|
||||
await writer.sendMessage('my-team', {
|
||||
member: 'alice',
|
||||
text: 'hello',
|
||||
});
|
||||
|
||||
const persisted = JSON.parse(hoisted.files.get(inboxPath) ?? '[]') as Record<string, unknown>[];
|
||||
expect(persisted).toHaveLength(1);
|
||||
expect(persisted[0]).not.toHaveProperty('source');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue