Archived
fix(sessions): stop duplicating user message for slash commands
A slash command sent as the first (or any idle) message showed the raw command text twice until reload: once from the client's optimistic insert and once from the server's message.append echo, neither of which converges with the agent's canonical expanded message (e.g. a /skill:* block). Make commands obey the same source-of-truth contract as prompts: - client no longer inserts the raw command text optimistically; it shows the existing per-session sending indicator instead - forwarded runtime/skill commands return a bare done result rather than a synthetic "Accepted ..." line - server suppresses the raw message.append echo for command-forwarded prompts (threaded through the compaction queue too) Result: pre-reload state matches reload, with no transient duplicate.
This commit is contained in:
@@ -76,6 +76,7 @@ interface QueuedPrompt {
|
||||
kind: QueuedPromptKind;
|
||||
text: string;
|
||||
images?: ImageContent[];
|
||||
echoUserMessage?: boolean;
|
||||
}
|
||||
|
||||
function requirePromptText(value: unknown): string {
|
||||
@@ -300,7 +301,7 @@ export class PiSessionService {
|
||||
this.heartbeat = setInterval(() => { this.publishHeartbeats(); }, deps.heartbeatIntervalMs ?? 2000);
|
||||
this.commandService = new SessionCommandService(
|
||||
(sessionId) => this.getActive(sessionId),
|
||||
(sessionId, text) => this.prompt(sessionId, text),
|
||||
(sessionId, text) => this.prompt(sessionId, text, undefined, undefined, { echoUserMessage: false }),
|
||||
events,
|
||||
{
|
||||
onCompactionStart: (session) => {
|
||||
@@ -478,8 +479,13 @@ export class PiSessionService {
|
||||
return commands.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
async prompt(ref: PiSessionLookup, text: unknown, streamingBehavior?: unknown, attachments?: unknown): Promise<void> {
|
||||
async prompt(ref: PiSessionLookup, text: unknown, streamingBehavior?: unknown, attachments?: unknown, options?: { echoUserMessage?: boolean }): Promise<void> {
|
||||
const promptText = requirePromptText(text);
|
||||
// Command-forwarded prompts (e.g. /skill:*) are expanded by the agent, which
|
||||
// streams the canonical message back. The client doesn't render the raw
|
||||
// command text, so the server must not echo it either, or it would show up
|
||||
// as a transient line that vanishes on reload.
|
||||
const echoUserMessage = options?.echoUserMessage !== false;
|
||||
const requestedBehavior = parsePromptStreamingBehavior(streamingBehavior);
|
||||
const parsedAttachments = parsePromptAttachments(attachments, { enforceInlineSizeLimit: false });
|
||||
const images = (await attachmentsToInlineImages(parsedAttachments)).map((entry) => entry.image);
|
||||
@@ -494,15 +500,15 @@ export class PiSessionService {
|
||||
return;
|
||||
}
|
||||
if (session.isCompacting) {
|
||||
this.enqueuePromptDuringCompaction(session, promptText, behavior ?? "followUp", images);
|
||||
this.enqueuePromptDuringCompaction(session, promptText, behavior ?? "followUp", images, echoUserMessage);
|
||||
return;
|
||||
}
|
||||
void this.submitPrompt(session, promptText, behavior, images);
|
||||
void this.submitPrompt(session, promptText, behavior, images, echoUserMessage);
|
||||
}
|
||||
|
||||
private submitPrompt(session: PiAgentSession, text: string, behavior: QueuedPromptKind | undefined, images: ImageContent[] = []): Promise<void> {
|
||||
private submitPrompt(session: PiAgentSession, text: string, behavior: QueuedPromptKind | undefined, images: ImageContent[] = [], echoUserMessage = true): Promise<void> {
|
||||
this.publishActivity(session, behavior === "steer" ? "steering queued" : behavior === "followUp" ? "message queued" : "prompt accepted", "active");
|
||||
if (behavior === undefined) this.events.publish(session.sessionId, { type: "message.append", message: userMessage(text, images) });
|
||||
if (behavior === undefined && echoUserMessage) this.events.publish(session.sessionId, { type: "message.append", message: userMessage(text, images) });
|
||||
const promptOptions = buildPromptOptions(behavior, images);
|
||||
const promptPromise = session.prompt(text, promptOptions).catch((error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
@@ -513,9 +519,9 @@ export class PiSessionService {
|
||||
return promptPromise;
|
||||
}
|
||||
|
||||
private enqueuePromptDuringCompaction(session: PiAgentSession, text: string, kind: QueuedPromptKind, images: ImageContent[] = []): void {
|
||||
private enqueuePromptDuringCompaction(session: PiAgentSession, text: string, kind: QueuedPromptKind, images: ImageContent[] = [], echoUserMessage = true): void {
|
||||
const queue = this.compactionPromptQueues.get(session.sessionId) ?? [];
|
||||
queue.push({ kind, text, ...(images.length > 0 ? { images } : {}) });
|
||||
queue.push({ kind, text, ...(images.length > 0 ? { images } : {}), ...(echoUserMessage ? {} : { echoUserMessage: false }) });
|
||||
this.compactionPromptQueues.set(session.sessionId, queue);
|
||||
this.publishActivity(session, "message queued during compaction", "active");
|
||||
this.publishStatus(session);
|
||||
@@ -859,14 +865,14 @@ export class PiSessionService {
|
||||
const queued = this.takeCompactionPromptQueue(sessionId);
|
||||
if (queued.length === 0) return;
|
||||
this.publishStatus(session);
|
||||
for (const prompt of queued) void this.submitPrompt(session, prompt.text, prompt.kind, prompt.images);
|
||||
for (const prompt of queued) void this.submitPrompt(session, prompt.text, prompt.kind, prompt.images, prompt.echoUserMessage ?? true);
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = this.shiftCompactionPrompt(sessionId);
|
||||
if (prompt === undefined) return;
|
||||
this.publishStatus(session);
|
||||
const submitted = this.submitPrompt(session, prompt.text, undefined, prompt.images);
|
||||
const submitted = this.submitPrompt(session, prompt.text, undefined, prompt.images, prompt.echoUserMessage ?? true);
|
||||
void submitted.finally(() => { this.scheduleCompactionQueueDrain(sessionId); });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user