loop: do slug generation outside the agent loop

[This commit message written entirely by a human; it is all useful.]

We can make a slug based on the first message.
It's good enough.
That keeps it--and the slug tool--out of the agent's context.
It's also one fewer step for extremely short Sketch runs,
which is the straw that broke this particular camel's back.

This is a mild UI regression, in that there's a slight stall
after the user types their first message, during which
the slug is being generated. See (2) below.

While we're here, add handling of compaction agent messages.

This leaves two big TODOs outstanding:

1.

Untangle the awful rats nest that is slug and branch management;
we have callbacks back and forth and layers and locking and it's all confusing.
One visible for that this ugliness takes is that every time the agent tries out a slug,
the top bar in the webui updates, even if we then reject that slug as a duplicate.
there are other forms of ugliness, just less visible.

2.

Make slug generation concurrent with the rest of the agent,
to avoid a short stall right after the user's first request (ick).

When we make slug setting concurrent, we'll likely need to resuscitate
the bashPermissionCheck, except it'll be "silently block and wait for
background slug generation to complete", rather than "reject the tool call".
Ditto for about_sketch, and any other tool call that expects
the slug or branch name to be set.

Generally, before undertaking this effort, we should fix (1) above,
make convos generally concurrency safe (maybe COW?), and
figure out to get race-enabled innie builds.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s8ac5f6a9faa611ebk
diff --git a/webui/src/types.ts b/webui/src/types.ts
index 6cc4150..d4dd475 100644
--- a/webui/src/types.ts
+++ b/webui/src/types.ts
@@ -171,6 +171,6 @@
 	subject: string;
 }
 
-export type CodingAgentMessageType = 'user' | 'agent' | 'error' | 'budget' | 'tool' | 'commit' | 'auto' | 'port';
+export type CodingAgentMessageType = 'user' | 'agent' | 'error' | 'budget' | 'tool' | 'commit' | 'auto' | 'port' | 'compact' | 'slug';
 
 export type Duration = number;
diff --git a/webui/src/web-components/aggregateAgentMessages.ts b/webui/src/web-components/aggregateAgentMessages.ts
index 366b690..5347540 100644
--- a/webui/src/web-components/aggregateAgentMessages.ts
+++ b/webui/src/web-components/aggregateAgentMessages.ts
@@ -14,6 +14,10 @@
         toolCallResults.set(msg.tool_call_id, msg);
         return false;
       }
+      // Suppress internal message types that shouldn't be displayed
+      if (msg.type == "slug" || msg.type == "compact") {
+        return false;
+      }
       if (seenIds.has(msg.idx)) {
         return false; // Skip if idx is already seen
       }
diff --git a/webui/src/web-components/mobile-chat.ts b/webui/src/web-components/mobile-chat.ts
index 9ce01fe..0c3bc48 100644
--- a/webui/src/web-components/mobile-chat.ts
+++ b/webui/src/web-components/mobile-chat.ts
@@ -272,9 +272,6 @@
         case "todo_read":
           return "Read todo list";
 
-        case "set-slug":
-          return `Slug: "${input.slug || ""}"`;
-
         case "multiplechoice":
           const question = input.question || "Multiple choice question";
           const options = input.responseOptions || [];
diff --git a/webui/src/web-components/sketch-tool-calls.ts b/webui/src/web-components/sketch-tool-calls.ts
index d1b2018..826052d 100644
--- a/webui/src/web-components/sketch-tool-calls.ts
+++ b/webui/src/web-components/sketch-tool-calls.ts
@@ -68,11 +68,6 @@
           .open=${open}
           .toolCall=${toolCall}
         ></sketch-tool-card-think>`;
-      case "set-slug":
-        return html`<sketch-tool-card-set-slug
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-set-slug>`;
       case "commit-message-style":
         return html`<sketch-tool-card-commit-message-style
           .open=${open}
diff --git a/webui/src/web-components/sketch-tool-card.ts b/webui/src/web-components/sketch-tool-card.ts
index 5954e24..5c5e4c2 100644
--- a/webui/src/web-components/sketch-tool-card.ts
+++ b/webui/src/web-components/sketch-tool-card.ts
@@ -268,29 +268,6 @@
   }
 }
 
-@customElement("sketch-tool-card-set-slug")
-export class SketchToolCardSetSlug extends SketchTailwindElement {
-  @property() toolCall: ToolCall;
-  @property() open: boolean;
-
-  render() {
-    const inputData = JSON.parse(this.toolCall?.input || "{}");
-
-    const summaryContent = html`<span class="italic">
-      Slug: "${inputData.slug}"
-    </span>`;
-
-    const inputContent = html`<div>Set slug to: <b>${inputData.slug}</b></div>`;
-
-    return html`<sketch-tool-card-base
-      .open=${this.open}
-      .toolCall=${this.toolCall}
-      .summaryContent=${summaryContent}
-      .inputContent=${inputContent}
-    ></sketch-tool-card-base>`;
-  }
-}
-
 @customElement("sketch-tool-card-commit-message-style")
 export class SketchToolCardCommitMessageStyle extends SketchTailwindElement {
   @property()
@@ -567,7 +544,6 @@
     "sketch-tool-card-done": SketchToolCardDone;
     "sketch-tool-card-patch": SketchToolCardPatch;
     "sketch-tool-card-think": SketchToolCardThink;
-    "sketch-tool-card-set-slug": SketchToolCardSetSlug;
     "sketch-tool-card-commit-message-style": SketchToolCardCommitMessageStyle;
     "sketch-tool-card-multiple-choice": SketchToolCardMultipleChoice;
     "sketch-tool-card-todo-write": SketchToolCardTodoWrite;