webui: Improve dx

For local development, switch to Vite and update web components for improved demo experience. Note that we haven't changed how we bundle when we're actually running in sketch; that's still the go/esbuild in-memory setup. This just changes demo dev setup to get breakpoints working and a functioning full sketch-app-shell.

We still need to add some mock data, but this is a start

- Introduced `vite.config.mts` for Vite setup with hot module reloading.
- Updated `package.json` and `package-lock.json` to include Vite and related plugins.
- Refactored demo scripts to utilize Vite for local development.
- Created `launch.json` for VSCode debugging configuration.
- Enhanced `Makefile` with a new demo task.
- Improved styling and structure in demo HTML and CSS files.
- Implemented `aggregateAgentMessages` function for message handling in web components.
diff --git a/loop/webui/src/web-components/aggregateAgentMessages.ts b/loop/webui/src/web-components/aggregateAgentMessages.ts
new file mode 100644
index 0000000..2fbd435
--- /dev/null
+++ b/loop/webui/src/web-components/aggregateAgentMessages.ts
@@ -0,0 +1,34 @@
+import { AgentMessage } from "../types";
+
+export function aggregateAgentMessages(
+  arr1: AgentMessage[],
+  arr2: AgentMessage[]): AgentMessage[] {
+  const mergedArray = [...arr1, ...arr2];
+  const seenIds = new Set<number>();
+  const toolCallResults = new Map<string, AgentMessage>();
+
+  let ret: AgentMessage[] = mergedArray
+    .filter((msg) => {
+      if (msg.type == "tool") {
+        toolCallResults.set(msg.tool_call_id, msg);
+        return false;
+      }
+      if (seenIds.has(msg.idx)) {
+        return false; // Skip if idx is already seen
+      }
+
+      seenIds.add(msg.idx);
+      return true;
+    })
+    .sort((a: AgentMessage, b: AgentMessage) => a.idx - b.idx);
+
+  // Attach any tool_call result messages to the original message's tool_call object.
+  ret.forEach((msg) => {
+    msg.tool_calls?.forEach((toolCall) => {
+      if (toolCallResults.has(toolCall.tool_call_id)) {
+        toolCall.result_message = toolCallResults.get(toolCall.tool_call_id);
+      }
+    });
+  });
+  return ret;
+}