feat: integrate Mock Service Worker (MSW) for API mocking and testing
Currently just used for demo page
- Added MSW as a dependency in package.json.
- Configured MSW in the demo HTML to start the worker and handle API requests.
- Created mockServiceWorker.js to manage service worker lifecycle and request handling.
- Implemented browser.ts to set up the MSW worker with defined request handlers.
- Developed handlers.ts to simulate API responses and manage application state for testing.
diff --git a/webui/src/web-components/demo/mocks/browser.ts b/webui/src/web-components/demo/mocks/browser.ts
new file mode 100644
index 0000000..bcd82e4
--- /dev/null
+++ b/webui/src/web-components/demo/mocks/browser.ts
@@ -0,0 +1,4 @@
+import { setupWorker } from "msw/browser";
+import { handlers } from "./handlers";
+
+export const worker = setupWorker(...handlers);
diff --git a/webui/src/web-components/demo/mocks/fixtures/dummy.ts b/webui/src/web-components/demo/mocks/fixtures/dummy.ts
new file mode 100644
index 0000000..5dcf509
--- /dev/null
+++ b/webui/src/web-components/demo/mocks/fixtures/dummy.ts
@@ -0,0 +1,372 @@
+import { AgentMessage, State } from "../../../../types";
+
+export const initialMessages: AgentMessage[] = [
+ {
+ type: "user",
+ end_of_turn: false,
+ content:
+ "add a line to dummy.txt. Doesn't matter what it is and don't bother running tests it's just a dummy repo. Please commit afterward",
+ timestamp: "2025-04-24T10:32:00.679873+01:00",
+ conversation_id: "",
+ idx: 0,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content:
+ "I'll help you add a line to dummy.txt and commit the change. Let me first check if this file exists and create it if needed.",
+ tool_calls: [
+ {
+ name: "title",
+ input: '{"title":"Add a line to dummy.txt and commit the change"}',
+ tool_call_id: "toolu_01HSQ8xb9Ba8oP1gJPv1eD2s",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:03.988439+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 4,
+ cache_creation_input_tokens: 2228,
+ cache_read_input_tokens: 0,
+ output_tokens: 92,
+ cost_usd: 0.009747,
+ },
+ start_time: "2025-04-24T10:32:00.680125+01:00",
+ end_time: "2025-04-24T10:32:03.988434+01:00",
+ elapsed: 3308355125,
+ idx: 1,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "title",
+ input: '{"title":"Add a line to dummy.txt and commit the change"}',
+ tool_result: "Title set to: Add a line to dummy.txt and commit the change",
+ tool_call_id: "toolu_01HSQ8xb9Ba8oP1gJPv1eD2s",
+ timestamp: "2025-04-24T10:32:03.988567+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:03.988553+01:00",
+ end_time: "2025-04-24T10:32:03.988566+01:00",
+ elapsed: 13500,
+ idx: 2,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "",
+ tool_calls: [
+ {
+ name: "bash",
+ input: '{"command":"ls -la"}',
+ tool_call_id: "toolu_01RtkhetY8bqC6U1QBfVbukS",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:06.146332+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 116,
+ cache_read_input_tokens: 2228,
+ output_tokens: 54,
+ cost_usd: 0.0019314,
+ },
+ start_time: "2025-04-24T10:32:04.007226+01:00",
+ end_time: "2025-04-24T10:32:06.146325+01:00",
+ elapsed: 2139128375,
+ idx: 3,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "bash",
+ input: '{"command":"ls -la"}',
+ tool_result:
+ "total 1712\ndrwxr-xr-x 29 pokey staff 928 Apr 15 13:06 .\ndrwxr-xr-x 397 pokey staff 12704 Apr 23 09:53 ..\n-rw-r--r--@ 1 pokey staff 292 Jan 8 2024 .editorconfig\ndrwxr-xr-x 17 pokey staff 544 Apr 24 10:32 .git\ndrwxr-xr-x@ 3 pokey staff 96 Jan 8 2024 .github\n-rw-r--r--@ 1 pokey staff 777 Jan 8 2024 .gitignore\n-rw-r--r--@ 1 pokey staff 0 Apr 15 13:06 .PROMPT_DRAFT.md\n-rw------- 1 pokey staff 11811 Oct 4 2022 .tags\n-rw-r--r--@ 1 pokey staff 762 Jan 8 2024 .travis.yml\ndrwxr-xr-x 3 pokey staff 96 Apr 9 10:51 .vscode\n-rw-r--r--@ 1 pokey staff 156 Jan 8 2024 AUTHORS.rst\n-rw-r--r--@ 1 pokey staff 3249 Jan 8 2024 CONTRIBUTING.rst\ndrwxr-xr-x@ 12 pokey staff 384 Jan 8 2024 docs\n-rw-r--r-- 1 pokey staff 6644 Apr 23 15:31 dummy.txt\n-rw-r--r--@ 1 pokey staff 89 Jan 8 2024 HISTORY.rst\n-rw-r--r--@ 1 pokey staff 1070 Jan 8 2024 LICENSE\n-rw-r--r--@ 1 pokey staff 2303 Jan 8 2024 Makefile\n-rw-r--r--@ 1 pokey staff 264 Jan 8 2024 MANIFEST.in\n-rw-r--r-- 1 pokey staff 794413 Mar 7 2017 myVim.log\n-rw-r--r--@ 1 pokey staff 1043 Apr 8 12:21 README.rst\n-rw-r--r--@ 1 pokey staff 145 Jan 8 2024 requirements_dev.txt\n-rw-r--r--@ 1 pokey staff 342 Jan 8 2024 setup.cfg\n-rw-r--r--@ 1 pokey staff 1559 Jan 8 2024 setup.py\ndrwxr-xr-x 6 pokey staff 192 Jan 8 2024 spaghetti\ndrwxr-xr-x 9 pokey staff 288 Feb 26 2017 spaghetti.egg-info\ndrwxr-xr-x@ 5 pokey staff 160 Apr 4 19:07 tests\n-rw-r--r--@ 1 pokey staff 430 Jan 8 2024 tox.ini\n-rw-r--r--@ 1 pokey staff 3753 Jan 8 2024 travis_pypi_setup.py\ndrwxr-xr-x 2 pokey staff 64 Mar 11 2017 UltiSnips\n",
+ tool_call_id: "toolu_01RtkhetY8bqC6U1QBfVbukS",
+ timestamp: "2025-04-24T10:32:06.176176+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:06.146414+01:00",
+ end_time: "2025-04-24T10:32:06.176175+01:00",
+ elapsed: 29760750,
+ idx: 4,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content:
+ "I see that dummy.txt already exists. Let me add a new line to it.",
+ tool_calls: [
+ {
+ name: "patch",
+ input:
+ '{"path":"/Users/pokey/src/spaghetti/dummy.txt","patches":[{"operation":"append_eof","newText":"\\nAdding a new dummy line here - requested change"}]}',
+ tool_call_id: "toolu_01KjY2a2MWhfvwy6Pmci8SX2",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:09.724348+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 970,
+ cache_read_input_tokens: 2344,
+ output_tokens: 141,
+ cost_usd: 0.0064737,
+ },
+ start_time: "2025-04-24T10:32:06.195746+01:00",
+ end_time: "2025-04-24T10:32:09.724335+01:00",
+ elapsed: 3528638042,
+ idx: 5,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "patch",
+ input:
+ '{"path":"/Users/pokey/src/spaghetti/dummy.txt","patches":[{"operation":"append_eof","newText":"\\nAdding a new dummy line here - requested change"}]}',
+ tool_result: "- Applied all patches\n",
+ tool_call_id: "toolu_01KjY2a2MWhfvwy6Pmci8SX2",
+ timestamp: "2025-04-24T10:32:09.728267+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:09.725039+01:00",
+ end_time: "2025-04-24T10:32:09.728265+01:00",
+ elapsed: 3225875,
+ idx: 6,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "Now let me commit this change:",
+ tool_calls: [
+ {
+ name: "bash",
+ input:
+ '{"command":"GIT_AUTHOR_NAME=\\"Pokey Rule\\" GIT_AUTHOR_EMAIL=\\"755842+pokey@users.noreply.github.com\\" git add dummy.txt && git commit -m \\"Add a new line to dummy.txt\\" -m \\"Co-Authored-By: sketch\\" -m \\"Add a line to dummy.txt. Doesn\'t matter what it is and don\'t bother running tests it\'s just a dummy repo.\\""}',
+ tool_call_id: "toolu_01NQXuuU4JdUHn29S35oGLxF",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:13.306331+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 146,
+ cache_read_input_tokens: 3314,
+ output_tokens: 160,
+ cost_usd: 0.0039597,
+ },
+ start_time: "2025-04-24T10:32:09.76053+01:00",
+ end_time: "2025-04-24T10:32:13.306203+01:00",
+ elapsed: 3545723083,
+ idx: 7,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "bash",
+ input:
+ '{"command":"GIT_AUTHOR_NAME=\\"Pokey Rule\\" GIT_AUTHOR_EMAIL=\\"755842+pokey@users.noreply.github.com\\" git add dummy.txt && git commit -m \\"Add a new line to dummy.txt\\" -m \\"Co-Authored-By: sketch\\" -m \\"Add a line to dummy.txt. Doesn\'t matter what it is and don\'t bother running tests it\'s just a dummy repo.\\""}',
+ tool_result:
+ "branchless: processing 2 updates: branch main, ref HEAD\nbranchless: processed commit: 898178a Add a new line to dummy.txt\n[main 898178a] Add a new line to dummy.txt\n 1 file changed, 2 insertions(+), 1 deletion(-)\n",
+ tool_call_id: "toolu_01NQXuuU4JdUHn29S35oGLxF",
+ timestamp: "2025-04-24T10:32:13.652683+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:13.307345+01:00",
+ end_time: "2025-04-24T10:32:13.652678+01:00",
+ elapsed: 345337875,
+ idx: 8,
+ },
+ {
+ type: "commit",
+ end_of_turn: false,
+ content: "",
+ commits: [
+ {
+ hash: "898178a0c6681ce2b641cbe8994c22ed8bbb7dd7",
+ subject: "Add a new line to dummy.txt",
+ body: "Co-Authored-By: sketch\n\nAdd a line to dummy.txt. Doesn't matter what it is and don't bother running tests it's just a dummy repo.",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:13.690825+01:00",
+ conversation_id: "",
+ idx: 9,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "",
+ tool_calls: [
+ {
+ name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"not applicable","description":"The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_call_id: "toolu_01YMBDmBoCcE5tfZvbXAgWEa",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:19.07007+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 244,
+ cache_read_input_tokens: 3460,
+ output_tokens: 241,
+ cost_usd: 0.005586,
+ },
+ start_time: "2025-04-24T10:32:13.74753+01:00",
+ end_time: "2025-04-24T10:32:19.070058+01:00",
+ elapsed: 5322601792,
+ idx: 10,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"not applicable","description":"The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_result:
+ "codereview tool has not been run for commit 898178a0c6681ce2b641cbe8994c22ed8bbb7dd7",
+ tool_error: true,
+ tool_call_id: "toolu_01YMBDmBoCcE5tfZvbXAgWEa",
+ timestamp: "2025-04-24T10:32:19.121365+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:19.070249+01:00",
+ end_time: "2025-04-24T10:32:19.121358+01:00",
+ elapsed: 51109583,
+ idx: 11,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "Let me run a code review as required:",
+ tool_calls: [
+ {
+ name: "codereview",
+ input: "{}",
+ tool_call_id: "toolu_01Fgg5vaURskyxGcbwHoBu7z",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:21.918149+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 7,
+ cache_creation_input_tokens: 253,
+ cache_read_input_tokens: 3704,
+ output_tokens: 54,
+ cost_usd: 0.0028909499999999998,
+ },
+ start_time: "2025-04-24T10:32:19.14049+01:00",
+ end_time: "2025-04-24T10:32:21.918133+01:00",
+ elapsed: 2777681917,
+ idx: 12,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "codereview",
+ input: "{}",
+ tool_result: "OK",
+ tool_call_id: "toolu_01Fgg5vaURskyxGcbwHoBu7z",
+ timestamp: "2025-04-24T10:32:22.516658+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:21.918261+01:00",
+ end_time: "2025-04-24T10:32:22.516654+01:00",
+ elapsed: 598402125,
+ idx: 13,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content: "Now let me try the done call again:",
+ tool_calls: [
+ {
+ name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"yes","description":"Code review completed and no issues found. The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_call_id: "toolu_016TBxz5Ma8y3if3rY4DZBXE",
+ },
+ ],
+ timestamp: "2025-04-24T10:32:26.926407+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 6,
+ cache_creation_input_tokens: 60,
+ cache_read_input_tokens: 3957,
+ output_tokens: 217,
+ cost_usd: 0.0046851,
+ },
+ start_time: "2025-04-24T10:32:22.534359+01:00",
+ end_time: "2025-04-24T10:32:26.926391+01:00",
+ elapsed: 4392093250,
+ idx: 14,
+ },
+ {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "done",
+ input:
+ '{"checklist_items":{"git_commit":{"status":"yes","description":"Created a git commit for the code changes with the appropriate commit message that includes \'Co-Authored-By: sketch\' and the original user prompt."},"wrote_tests":{"status":"not applicable","description":"No functional code was changed, just added a line to a dummy file."},"passes_tests":{"status":"not applicable","description":"User explicitly mentioned not to run tests as this is just a dummy repo."},"code_reviewed":{"status":"yes","description":"Code review completed and no issues found. The change was trivial - just adding a line to a dummy.txt file."}}}',
+ tool_result:
+ "Please ask the user to review your work. Be concise - users are more likely to read shorter comments.",
+ tool_call_id: "toolu_016TBxz5Ma8y3if3rY4DZBXE",
+ timestamp: "2025-04-24T10:32:26.978771+01:00",
+ conversation_id: "37s-g6xg",
+ start_time: "2025-04-24T10:32:26.92661+01:00",
+ end_time: "2025-04-24T10:32:26.978758+01:00",
+ elapsed: 52149125,
+ idx: 15,
+ },
+ {
+ type: "agent",
+ end_of_turn: false,
+ content:
+ 'I\'ve completed your request:\n\n1. Added a new line to dummy.txt: "Adding a new dummy line here - requested change"\n2. Created a git commit with the change using the specified author information\n3. Included "Co-Authored-By: sketch" in the commit message as required\n\nThe change has been successfully committed to the repository. Is there anything else you\'d like me to help with?',
+ timestamp: "2025-04-24T10:32:29.072661+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 5,
+ cache_creation_input_tokens: 250,
+ cache_read_input_tokens: 4017,
+ output_tokens: 92,
+ cost_usd: 0.0035376,
+ },
+ start_time: "2025-04-24T10:32:26.99749+01:00",
+ end_time: "2025-04-24T10:32:29.072654+01:00",
+ elapsed: 2075193375,
+ turnDuration: 28393844125,
+ idx: 16,
+ },
+];
+
+export const initialState: State = {
+ message_count: initialMessages.length,
+ total_usage: {
+ start_time: "2025-04-24T10:32:00.679057+01:00",
+ messages: 8,
+ input_tokens: 46,
+ output_tokens: 1051,
+ cache_read_input_tokens: 23024,
+ cache_creation_input_tokens: 4267,
+ total_cost_usd: 0.038811450000000004,
+ tool_uses: {
+ bash: 2,
+ codereview: 1,
+ done: 2,
+ patch: 1,
+ title: 1,
+ },
+ },
+ initial_commit: "a6c5a08a451ef1082774a7affb6af58775e7bc16",
+ title: "Add a line to dummy.txt and commit the change",
+ hostname: "MacBook-Pro-9.local",
+ working_dir: "/Users/pokey/src/spaghetti",
+ os: "darwin",
+ git_origin: "git@github.com:pokey/spaghetti.git",
+ inside_hostname: "MacBook-Pro-9.local",
+ inside_os: "darwin",
+ inside_working_dir: "/Users/pokey/src/spaghetti",
+};
diff --git a/webui/src/web-components/demo/mocks/handlers.ts b/webui/src/web-components/demo/mocks/handlers.ts
new file mode 100644
index 0000000..c112efd
--- /dev/null
+++ b/webui/src/web-components/demo/mocks/handlers.ts
@@ -0,0 +1,63 @@
+import { http, HttpResponse, delay } from "msw";
+import { initialState, initialMessages } from "./fixtures/dummy";
+
+// Mock state updates for long-polling simulation
+let currentState = { ...initialState };
+const messages = [...initialMessages];
+const ADD_NEW_MESSAGES =
+ new URL(window.location.href).searchParams.get("addNewMessages") === "1";
+
+export const handlers = [
+ // Unified state endpoint that handles both regular and polling requests
+ http.get("*/state", async ({ request }) => {
+ const url = new URL(request.url);
+ const isPoll = url.searchParams.get("poll") === "true";
+
+ if (!isPoll) {
+ // Regular state request
+ return HttpResponse.json(currentState);
+ }
+
+ // This is a long-polling request
+ await delay(ADD_NEW_MESSAGES ? 2000 : 60000); // Simulate waiting for changes
+
+ if (ADD_NEW_MESSAGES) {
+ // Simulate adding new messages
+ messages.push({
+ type: "agent",
+ end_of_turn: false,
+ content: "Here's a message",
+ timestamp: "2025-04-24T10:32:29.072661+01:00",
+ conversation_id: "37s-g6xg",
+ usage: {
+ input_tokens: 5,
+ cache_creation_input_tokens: 250,
+ cache_read_input_tokens: 4017,
+ output_tokens: 92,
+ cost_usd: 0.0035376,
+ },
+ start_time: "2025-04-24T10:32:26.99749+01:00",
+ end_time: "2025-04-24T10:32:29.072654+01:00",
+ elapsed: 2075193375,
+ turnDuration: 28393844125,
+ idx: messages.length,
+ });
+
+ // Update the state with new messages
+ currentState = {
+ ...currentState,
+ message_count: messages.length,
+ };
+ }
+
+ return HttpResponse.json(currentState);
+ }),
+
+ // Messages endpoint
+ http.get("*/messages", ({ request }) => {
+ const url = new URL(request.url);
+ const startIndex = parseInt(url.searchParams.get("start") || "0");
+
+ return HttpResponse.json(messages.slice(startIndex));
+ }),
+];
diff --git a/webui/src/web-components/demo/sketch-app-shell.demo.html b/webui/src/web-components/demo/sketch-app-shell.demo.html
index 48fc100..8c4458b 100644
--- a/webui/src/web-components/demo/sketch-app-shell.demo.html
+++ b/webui/src/web-components/demo/sketch-app-shell.demo.html
@@ -5,7 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>sketch coding assistant</title>
<link rel="stylesheet" href="sketch-app-shell.css" />
- <script src="../sketch-app-shell.ts" type="module"></script>
+ <script type="module">
+ const { worker } = await import("./mocks/browser");
+ await worker.start();
+ await import("../sketch-app-shell.ts");
+ </script>
</head>
<body>
<sketch-app-shell></sketch-app-shell>