fix(queue): deliver a queued slash command with context as a prompt
OpenCode's command route accepts file parts only. The server queue was attaching captured context as text parts to POST /session/:id/command, so a slash command queued with a comment, quote, or PR diff was rejected with 400 and retried forever. A command queued without context still takes the command route with its files. One queued with context now takes the prompt route the way the composer does: the command's template is expanded with its arguments, a skill keeps its text and gets the explicit skill-invocation instruction, and the context rides along. Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
This commit is contained in:
@@ -392,19 +392,60 @@ describe('message queue runtime', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('sends captured context with a slash command too', async () => {
|
||||
it('keeps files on the command route, which is all that route accepts', async () => {
|
||||
const { runtime, openCode, emit } = createRuntime();
|
||||
runtime.start();
|
||||
openCode.state.commands = [{ name: 'review' }];
|
||||
await runtime.enqueue(SESSION, DIRECTORY, item({
|
||||
content: '/review',
|
||||
text: '/review',
|
||||
context: [{ kind: 'synthetic', text: 'focus on tests' }],
|
||||
attachments: [{ id: 'a', filename: 'f.txt', mimeType: 'text/plain', size: 1, source: 'local', dataUrl: 'data:text/plain,hi' }],
|
||||
}));
|
||||
emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
|
||||
await settle();
|
||||
expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/command`);
|
||||
expect(openCode.state.sent[0].body.parts).toEqual([{ type: 'text', text: 'focus on tests', synthetic: true }]);
|
||||
expect(openCode.state.sent[0].body.parts).toEqual([{ type: 'file', mime: 'text/plain', filename: 'f.txt', url: 'data:text/plain,hi' }]);
|
||||
});
|
||||
|
||||
it('sends a command queued with context as its expanded prompt, context included', async () => {
|
||||
// The command route rejects text parts, so a command with captured
|
||||
// context takes the prompt route with the template expanded, exactly as
|
||||
// the composer does.
|
||||
const { runtime, openCode, emit } = createRuntime();
|
||||
runtime.start();
|
||||
openCode.state.commands = [{ name: 'review', source: 'command', template: 'Review $1 with focus on $2' }];
|
||||
const metadata = { openchamberContext: { kind: 'chat-quote', quote: 'q', text: 'why?' } };
|
||||
await runtime.enqueue(SESSION, DIRECTORY, item({
|
||||
content: '/review src "error handling"',
|
||||
text: '/review src "error handling"',
|
||||
context: [{ kind: 'context', text: 'quoted', metadata }],
|
||||
}));
|
||||
emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
|
||||
await settle();
|
||||
expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/prompt_async`);
|
||||
expect(openCode.state.sent[0].body.parts).toEqual([
|
||||
{ type: 'text', text: 'Review src with focus on error handling' },
|
||||
{ type: 'text', text: 'quoted', synthetic: true, metadata },
|
||||
]);
|
||||
});
|
||||
|
||||
it('sends a skill queued with context as an explicit invocation, context included', async () => {
|
||||
const { runtime, openCode, emit } = createRuntime();
|
||||
runtime.start();
|
||||
openCode.state.commands = [{ name: 'grill', source: 'skill', template: 'skill body' }];
|
||||
await runtime.enqueue(SESSION, DIRECTORY, item({
|
||||
content: '/grill auth',
|
||||
text: '/grill auth',
|
||||
context: [{ kind: 'synthetic', text: 'focus on tests' }],
|
||||
}));
|
||||
emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
|
||||
await settle();
|
||||
expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/prompt_async`);
|
||||
expect(openCode.state.sent[0].body.parts).toEqual([
|
||||
{ type: 'text', text: '/grill auth' },
|
||||
{ type: 'text', text: 'focus on tests', synthetic: true },
|
||||
{ type: 'text', text: 'The user explicitly invoked the grill skill. Use the corresponding skill tool to handle this request.', synthetic: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps captured context out of snapshots and broadcasts, and hands it back on take', async () => {
|
||||
|
||||
Reference in New Issue
Block a user