feat(queue): queue a message with everything the composer had attached
Queueing captured only the text and files. Context chips (inline comments, terminal selections, browser annotations, PR comments and checks, quotes, linked issue/PR/Linear references, pending synthetic parts) stayed in the composer and only left with the next manual send, so a queued message the server delivered went out without them and the chips rode an unrelated message later. A queued message now carries what the composer would have sent: the text with its agent mention stripped and file mentions resolved into attachments, the attached context as structured parts, and the skill instruction derived from the text. The server delivers those parts in the composer's order, the VS Code auto-send does the same, and editing a queued message puts the chips and linked references back. A failed queue restores the composer completely. Snapshots and broadcasts omit the captured context like attachment payloads; a take returns it. Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
This commit is contained in:
@@ -186,11 +186,13 @@ describe('buildQueuedAutoSendPayload', () => {
|
||||
{
|
||||
id: 'queued-1',
|
||||
content: 'first queued message',
|
||||
text: 'first queued message',
|
||||
createdAt: 1,
|
||||
},
|
||||
{
|
||||
id: 'queued-2',
|
||||
content: 'second queued message',
|
||||
text: 'second queued message',
|
||||
createdAt: 2,
|
||||
},
|
||||
];
|
||||
@@ -203,20 +205,18 @@ describe('buildQueuedAutoSendPayload', () => {
|
||||
expect(payload?.primaryAttachments).toEqual([]);
|
||||
});
|
||||
|
||||
test('uses the configured visible agents when parsing queued mentions', () => {
|
||||
visibleAgents = [
|
||||
{
|
||||
name: 'Builder',
|
||||
mode: 'subagent',
|
||||
permission: [],
|
||||
options: {},
|
||||
} as Agent,
|
||||
];
|
||||
|
||||
test('delivers the captured mention and context instead of re-parsing the content', () => {
|
||||
const metadata = { openchamberContext: { kind: 'github-issue' as const, number: 3, title: 'Bug', url: 'https://x/issues/3' } };
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: 'queued-mention',
|
||||
content: '@Builder please take this',
|
||||
text: 'please take this',
|
||||
agentMention: 'Builder',
|
||||
context: [
|
||||
{ kind: 'context', text: 'issue body', metadata },
|
||||
{ kind: 'instruction', text: 'use the skill' },
|
||||
],
|
||||
createdAt: 1,
|
||||
},
|
||||
];
|
||||
@@ -225,7 +225,11 @@ describe('buildQueuedAutoSendPayload', () => {
|
||||
|
||||
expect(payload).not.toBeNull();
|
||||
expect(payload?.agentMentionName).toBe('Builder');
|
||||
expect(payload?.primaryText).toBe('@Builder please take this');
|
||||
expect(payload?.primaryText).toBe('please take this');
|
||||
expect(payload?.additionalParts).toEqual([
|
||||
{ text: 'issue body', synthetic: true, metadata },
|
||||
{ text: 'use the skill', synthetic: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('preserves attachment-only queued messages as sendable payloads', () => {
|
||||
@@ -233,6 +237,7 @@ describe('buildQueuedAutoSendPayload', () => {
|
||||
{
|
||||
id: 'queued-attachments',
|
||||
content: '',
|
||||
text: '',
|
||||
createdAt: 1,
|
||||
attachments: [
|
||||
{
|
||||
@@ -249,6 +254,7 @@ describe('buildQueuedAutoSendPayload', () => {
|
||||
{
|
||||
id: 'queued-2',
|
||||
content: 'later queued message',
|
||||
text: 'later queued message',
|
||||
createdAt: 2,
|
||||
},
|
||||
];
|
||||
@@ -267,6 +273,7 @@ describe('buildQueuedAutoSendPayload', () => {
|
||||
{
|
||||
id: 'queued-1',
|
||||
content: 'queued message',
|
||||
text: 'queued message',
|
||||
createdAt: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useSelectionStore } from '@/sync/selection-store';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useContextStore } from '@/stores/contextStore';
|
||||
import { useAutoReviewStore } from '@/stores/useAutoReviewStore';
|
||||
import { parseAgentMentions } from '@/lib/messages/agentMentions';
|
||||
import { queuedContextToParts } from '@/components/chat/composer/submit/buildOutgoingMessage';
|
||||
import { getDirectoryState } from '@/sync/sync-refs';
|
||||
import { useDirectorySync } from '@/sync/sync-context';
|
||||
import { getRuntimeKey } from '@/lib/runtime-switch';
|
||||
@@ -82,14 +82,14 @@ export const buildQueuedAutoSendPayload = (queue: QueuedMessage[]) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const agents = useConfigStore.getState().getVisibleAgents();
|
||||
const { sanitizedText, mention } = parseAgentMentions(queued.content, agents);
|
||||
|
||||
// A queued message is delivered as captured: mention already stripped,
|
||||
// file mentions resolved, and the context it was queued with following it.
|
||||
return {
|
||||
queuedMessageId: queued.id,
|
||||
primaryText: sanitizedText,
|
||||
primaryText: queued.text,
|
||||
primaryAttachments: queued.attachments ?? [],
|
||||
agentMentionName: mention?.name,
|
||||
agentMentionName: queued.agentMention,
|
||||
additionalParts: queuedContextToParts(queued.context ?? []),
|
||||
sendConfig: queued.sendConfig,
|
||||
};
|
||||
};
|
||||
@@ -114,7 +114,7 @@ export const sendQueuedAutoSendPayload = (
|
||||
resolved.agent,
|
||||
payload.primaryAttachments,
|
||||
payload.agentMentionName,
|
||||
undefined,
|
||||
payload.additionalParts.length > 0 ? payload.additionalParts : undefined,
|
||||
resolved.variant,
|
||||
'normal',
|
||||
{ target },
|
||||
|
||||
Reference in New Issue
Block a user