mom: add working indicator and improve stop command

Working Indicator:
- Add '...' to channel messages while mom is processing
- Automatically removed when work completes or stops
- Applies to working message, not status messages

Improved Stop Command:
- Posts separate 'Stopping...' message that updates to 'Stopped'
- Original working message continues updating with tool results
- Clean separation between status and work output
- Properly handles abort during multi-step operations

Clean Stop Reason Handling:
- Agent run() now returns { stopReason } instead of throwing
- Handle 'aborted', 'error', 'stop', 'length', 'toolUse' cases cleanly
- No more exception-based control flow
- Track stopReason from assistant message.stopReason field

New SlackContext Methods:
- replaceMessage() - replace message text instead of appending
- setWorking() - add/remove working indicator
- Improved context tracking for stop command updates
This commit is contained in:
Mario Zechner 2025-11-26 20:49:02 +01:00
parent bfe7df6a49
commit 0c6c0f34dd
4 changed files with 104 additions and 23 deletions

View file

@ -13,7 +13,7 @@ import { createMomTools, setUploadFunction } from "./tools/index.js";
const model = getModel("anthropic", "claude-sonnet-4-5");
export interface AgentRunner {
run(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<void>;
run(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<{ stopReason: string }>;
abort(): void;
}
@ -316,7 +316,7 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
const executor = createExecutor(sandboxConfig);
return {
async run(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<void> {
async run(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<{ stopReason: string }> {
// Ensure channel directory exists
await mkdir(channelDir, { recursive: true });
@ -374,6 +374,9 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
},
};
// Track stop reason
let stopReason = "stop";
// Subscribe to events
agent.subscribe(async (event: AgentEvent) => {
switch (event.type) {
@ -474,6 +477,11 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
if (event.message.role === "assistant") {
const assistantMsg = event.message as any; // AssistantMessage type
// Track stop reason
if (assistantMsg.stopReason) {
stopReason = assistantMsg.stopReason;
}
// Accumulate usage
if (assistantMsg.usage) {
totalUsage.input += assistantMsg.usage.input;
@ -520,6 +528,8 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
const summary = log.logUsageSummary(logCtx, totalUsage);
await ctx.respondInThread(summary);
}
return { stopReason };
},
abort(): void {