Skip to content

Commit 57e833f

Browse files
committed
WIP
Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent ce726f3 commit 57e833f

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

src/browser/stores/WorkspaceStore.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,13 @@ export class WorkspaceStore {
341341
const messages = aggregator.getAllMessages();
342342
const metadata = this.workspaceMetadata.get(workspaceId);
343343

344+
const canInterrupt = activeStreams.length > 0 || aggregator.hasPendingScriptExecution();
345+
344346
return {
345347
name: metadata?.name ?? workspaceId, // Fall back to ID if metadata missing
346348
messages: aggregator.getDisplayedMessages(),
347349
queuedMessage: this.queuedMessages.get(workspaceId) ?? null,
348-
canInterrupt: activeStreams.length > 0,
350+
canInterrupt,
349351
isCompacting: aggregator.isCompacting(),
350352
loading: !hasMessages && !isCaughtUp,
351353
muxMessages: messages,

src/browser/utils/messages/StreamingMessageAggregator.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,72 @@ describe("StreamingMessageAggregator", () => {
422422
}
423423
});
424424

425+
test("tracks pending script executions until a result arrives", () => {
426+
const aggregator = new StreamingMessageAggregator(TEST_CREATED_AT);
427+
const timestamp = Date.now();
428+
429+
const pendingScript = createMuxMessage("script-2", "user", "Run script", {
430+
historySequence: 1,
431+
timestamp,
432+
muxMetadata: {
433+
type: "script-execution",
434+
id: "script-exec-2",
435+
historySequence: 1,
436+
timestamp,
437+
command: "/script wait",
438+
scriptName: "wait",
439+
args: [],
440+
},
441+
});
442+
443+
aggregator.addMessage(pendingScript);
444+
expect(aggregator.hasPendingScriptExecution()).toBe(true);
445+
446+
const completedScript = createMuxMessage("script-2", "user", "Run script", {
447+
historySequence: 1,
448+
timestamp,
449+
muxMetadata: {
450+
type: "script-execution",
451+
id: "script-exec-2",
452+
historySequence: 1,
453+
timestamp,
454+
command: "/script wait",
455+
scriptName: "wait",
456+
args: [],
457+
result: BASE_SCRIPT_RESULT,
458+
},
459+
});
460+
461+
aggregator.addMessage(completedScript);
462+
expect(aggregator.hasPendingScriptExecution()).toBe(false);
463+
});
464+
465+
test("clears pending script executions when messages are deleted", () => {
466+
const aggregator = new StreamingMessageAggregator(TEST_CREATED_AT);
467+
const timestamp = Date.now();
468+
469+
const pendingScript = createMuxMessage("script-3", "user", "Run script", {
470+
historySequence: 7,
471+
timestamp,
472+
muxMetadata: {
473+
type: "script-execution",
474+
id: "script-exec-3",
475+
historySequence: 7,
476+
timestamp,
477+
command: "/script cleanup",
478+
scriptName: "cleanup",
479+
args: [],
480+
},
481+
});
482+
483+
aggregator.addMessage(pendingScript);
484+
expect(aggregator.hasPendingScriptExecution()).toBe(true);
485+
486+
const deleteEvent: DeleteMessage = { type: "delete", historySequences: [7] };
487+
aggregator.handleDeleteMessage(deleteEvent);
488+
489+
expect(aggregator.hasPendingScriptExecution()).toBe(false);
490+
});
425491
test("removes script logs when history is truncated", () => {
426492
const aggregator = new StreamingMessageAggregator(TEST_CREATED_AT);
427493

src/browser/utils/messages/StreamingMessageAggregator.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export class StreamingMessageAggregator {
100100
// (or the user retries) so retry UI/backoff logic doesn't misfire on send failures.
101101

102102
private pendingStreamStartTime: number | null = null;
103+
private pendingScriptExecutions = new Set<string>();
103104

104105
// Workspace creation timestamp (used for recency calculation)
105106
// REQUIRED: Backend guarantees every workspace has createdAt via config.ts
@@ -214,6 +215,7 @@ export class StreamingMessageAggregator {
214215

215216
// Just store the message - backend assigns historySequence
216217
this.messages.set(message.id, message);
218+
this.syncScriptExecutionState(message);
217219
this.invalidateCache();
218220
}
219221

@@ -228,6 +230,7 @@ export class StreamingMessageAggregator {
228230
// First, add all messages to the map
229231
for (const message of messages) {
230232
this.messages.set(message.id, message);
233+
this.syncScriptExecutionState(message);
231234
}
232235

233236
// Then, reconstruct derived state from the most recent assistant message
@@ -277,6 +280,22 @@ export class StreamingMessageAggregator {
277280
private setPendingStreamStartTime(time: number | null): void {
278281
this.pendingStreamStartTime = time;
279282
}
283+
hasPendingScriptExecution(): boolean {
284+
return this.pendingScriptExecutions.size > 0;
285+
}
286+
287+
private syncScriptExecutionState(message: MuxMessage): void {
288+
const muxMetadata = message.metadata?.muxMetadata;
289+
if (muxMetadata?.type === "script-execution" && muxMetadata.result === undefined) {
290+
this.pendingScriptExecutions.add(message.id);
291+
} else {
292+
this.pendingScriptExecutions.delete(message.id);
293+
}
294+
}
295+
296+
private clearScriptExecutionState(messageId: string): void {
297+
this.pendingScriptExecutions.delete(messageId);
298+
}
280299

281300
getActiveStreams(): StreamingContext[] {
282301
return Array.from(this.activeStreams.values());
@@ -324,6 +343,7 @@ export class StreamingMessageAggregator {
324343
clear(): void {
325344
this.messages.clear();
326345
this.activeStreams.clear();
346+
this.pendingScriptExecutions.clear();
327347
this.streamSequenceCounter = 0;
328348
this.invalidateCache();
329349
}
@@ -339,6 +359,7 @@ export class StreamingMessageAggregator {
339359
const historySeq = message.metadata?.historySequence;
340360
if (historySeq !== undefined && sequencesToDelete.has(historySeq)) {
341361
this.messages.delete(messageId);
362+
this.clearScriptExecutionState(messageId);
342363
}
343364
}
344365

@@ -377,6 +398,7 @@ export class StreamingMessageAggregator {
377398
});
378399

379400
this.messages.set(data.messageId, streamingMessage);
401+
this.syncScriptExecutionState(streamingMessage);
380402
this.invalidateCache();
381403
}
382404

@@ -454,6 +476,7 @@ export class StreamingMessageAggregator {
454476
};
455477

456478
this.messages.set(data.messageId, message);
479+
this.syncScriptExecutionState(message);
457480

458481
// Clean up stream-scoped state (active stream tracking, TODOs)
459482
this.cleanupStreamState(data.messageId);
@@ -702,6 +725,7 @@ export class StreamingMessageAggregator {
702725
}
703726
for (const removeId of messagesToRemove) {
704727
this.messages.delete(removeId);
728+
this.clearScriptExecutionState(removeId);
705729
}
706730
break; // Found and handled the conflict
707731
}

0 commit comments

Comments
 (0)