sketch: add support for 'external' message types

- adds a new CodingAgentMessageType for loop.AgentMessage
- adds an new /external handler to loophttp.go
- modifies Agent to pass the .TextContent of ExternalMessage into the convo
  as though it came from the user.
- adds sketch-external-message web component, with a template for
  github workflow run events specifically.
- adds demos for sketch-external-message
diff --git a/webui/src/web-components/demo/demo-framework/demo-runner.ts b/webui/src/web-components/demo/demo-framework/demo-runner.ts
index 680651b..2871929 100644
--- a/webui/src/web-components/demo/demo-framework/demo-runner.ts
+++ b/webui/src/web-components/demo/demo-framework/demo-runner.ts
@@ -96,6 +96,7 @@
       "sketch-diff-range-picker",
       "sketch-timeline",
       "sketch-timeline-message",
+      "sketch-external-message",
       "sketch-todo-panel",
       "sketch-tool-calls",
       "sketch-view-mode-select",
diff --git a/webui/src/web-components/demo/sketch-external-message.demo.ts b/webui/src/web-components/demo/sketch-external-message.demo.ts
new file mode 100644
index 0000000..cdaa86d
--- /dev/null
+++ b/webui/src/web-components/demo/sketch-external-message.demo.ts
@@ -0,0 +1,120 @@
+import { DemoModule } from "./demo-framework/types";
+import { demoUtils } from "./demo-fixtures/index";
+import { SketchExternalMessage } from "../sketch-external-message";
+
+const demo: DemoModule = {
+  title: "Sketch External Message Demo",
+  description:
+    "Demonstration of external message components for various external message types",
+  imports: ["../sketch-external-message.ts"],
+
+  setup: async (container: HTMLElement) => {
+    const section = demoUtils.createDemoSection(
+      "External Message Components",
+      "Shows different external message components for various message types",
+    );
+
+    // external messages
+    const externalMessages = [
+      {
+        name: "github_workflow_run: completed, success",
+        message_type: "github_workflow_run",
+        body: {
+          workflow: {
+            name: "go_tests",
+          },
+          workflow_run: {
+            id: 123456789,
+            status: "completed",
+            conclusion: "success",
+            head_branch: "user/sketch/slug-name",
+            head_commit: "abc123",
+            html_url: "https://github.com/orgs/your-org/actions/runs/123456789",
+          },
+        },
+      },
+      {
+        name: "github_workflow_run: completed, failed",
+        message_type: "github_workflow_run",
+        body: {
+          workflow: {
+            name: "go_tests",
+          },
+          workflow_run: {
+            id: 123456789,
+            status: "completed",
+            conclusion: "failure",
+            head_branch: "user/sketch/slug-name",
+            head_commit: "abc123",
+            html_url: "https://github.com/orgs/your-org/actions/runs/123456789",
+          },
+        },
+      },
+      {
+        name: "github_workflow_run: queued",
+        message_type: "github_workflow_run",
+        body: {
+          workflow: {
+            name: "go_tests",
+          },
+          workflow_run: {
+            id: 123456789,
+            status: "queued",
+            head_branch: "user/sketch/slug-name",
+            head_commit: "abc123",
+            html_url: "https://github.com/orgs/your-org/actions/runs/123456789",
+          },
+        },
+      },
+      {
+        name: "github_workflow_run: in_progress",
+        message_type: "github_workflow_run",
+        body: {
+          workflow: {
+            name: "go_tests",
+          },
+          workflow_run: {
+            id: 123456789,
+            status: "in_progress",
+            head_branch: "user/sketch/slug-name",
+            head_commit: "abc123",
+            html_url: "https://github.com/orgs/your-org/actions/runs/123456789",
+          },
+        },
+      },
+    ];
+
+    // Create tool cards for each tool call
+    externalMessages.forEach((msg) => {
+      const msgSection = document.createElement("div");
+      msgSection.style.marginBottom = "2rem";
+      msgSection.style.border = "1px solid #eee";
+      msgSection.style.borderRadius = "8px";
+      msgSection.style.padding = "1rem";
+
+      const header = document.createElement("h3");
+      header.textContent = `Message: ${msg.name}`;
+      header.style.marginTop = "0";
+      header.style.marginBottom = "1rem";
+      header.style.color = "var(--demo-fixture-text-color)";
+      msgSection.appendChild(header);
+      const msgEl = document.createElement(
+        "sketch-external-message",
+      ) as SketchExternalMessage;
+
+      msgEl.message = {
+        message_type: msg.message_type,
+        body: msg.body,
+        text_content: `Workflow run ${msg.body.workflow_run.id} completed with status ${msg.body.workflow_run.status} and conclusion ${msg.body.workflow_run.conclusion}.`,
+      };
+      msgEl.open = true;
+
+      msgSection.appendChild(msgEl);
+      section.appendChild(msgSection);
+    });
+
+    container.appendChild(section);
+  },
+};
+
+export default demo;