Added LM Studio as a provider - #4
Merged
Merged
Conversation
rschardosin
added a commit
that referenced
this pull request
Sep 10, 2026
…ory calls Critical #1+#2 (backend): - newChatRunner now accepts an appName parameter and injects it into cr.ctx via context.WithValue(ctx, appNameContextKey, appName). This makes the context fallback in persistRunError/persistSessionMessage live (previously appNameContextKey was defined and read but never set). - runner.New() now uses getAppNameFromContext(cr.ctx) instead of hardcoded studioChatAppName, so ADK transcript events are persisted under the same app namespace as the session record. - StudioChatHandler passes effectiveApp to newChatRunner. - All test call sites updated to pass studioChatAppName as the appName argument. Medium #3 (extension delete): - Added deleteSession() to astonish-client.ts — sends DELETE /api/studio/sessions/{id}?app=astonish-extension. - main.ts delete button handler now calls deleteSession() before deleteExtensionSession() (best-effort: local cleanup proceeds even on server error so the UI stays consistent). Medium #4 (extension history): - fetchSessionHistory() now appends ?app=astonish-extension to the GET URL so the backend reads from the correct session namespace. Tests: - fetchSessionHistory test updated to assert ?app= is present in the URL. - Two new tests for deleteSession: success (204) and error (404 → throws).
rschardosin
added a commit
that referenced
this pull request
Sep 10, 2026
- Add DOM.Iterable to tsconfig.json lib array — fixes all 10 tsc errors for NodeListOf/HTMLCollection iteration (Critical #1) - Add 'typecheck' npm script so local devs can run 'npm run typecheck' matching the CI gate; update build.yml to use it - Fix skipPageTools let-before-declaration in main.ts — move the declaration above its first use (found by tsc after DOM.Iterable fix) - Add validAppName regex (^[a-z][a-z0-9_-]{0,63}$) and enforce it in StudioSessionsHandler, StudioSessionHandler, StudioDeleteSessionHandler, and StudioChatHandler — blocks path traversal, namespace squatting, and unbounded key lengths (Medium #2) - Replace tautological session handler tests with tests that exercise the real validAppName regex, validate the resolution+validation code path end-to-end, and assert rejection of traversal/injection attempts (Medium #4)
rschardosin
added a commit
that referenced
this pull request
Sep 10, 2026
#510) * extension: remove duplicate New button, add delete session feature - Remove redundant 'New' button from session bar (duplicate of 'New chat' option in dropdown) - Clean up dead JavaScript references (newSessionButton variable, event listeners, disabled toggles) - Remove dead CSS rule for #new-session - Add deleteExtensionSession() function to extension-sessions.ts for per-session deletion - Add trash button (🗑️) to session bar that: - Prevents deletion of 'New chat' (empty session) - Shows confirmation dialog - Deletes session and switches to next available or 'New chat' - Properly disables during streaming - All 84 tests pass, build clean * feat(extension): cross-origin iframe piercing via allFrames + frame coordinator - Add chrome.webNavigation permission to enumerate frames - Add allFrames: true to content script injection in service worker - Add MSG_FRAME_TOOL message type for frame relay - Add getAccessibleDocuments() in dom-tools to traverse same-origin iframes - Update collectCandidates, snapshot to query all accessible documents - Add frame detection and iframe source tagging in snapshot output - Add frame fan-out in service worker: getAllFrameIds, sendToFrame (with retry), mergeFrameResults - Add per-frame DOM tool execution: runPageToolInFrame, collectCandidatesFrameLocal, snapshotFrameInteractive, queryFrame - Add iframeSource() helper to tag refs from iframe elements - Add INTERACTIVE selector support for [tabindex="0"] (calendar day cells, SAPUI5 components) - Update architecture docs to document iframe support (same-origin traversal, cross-origin skip) - All 90 tests pass; both Vite bundles build successfully The extension now discovers and interacts with elements inside same-origin iframes. For SAP SAPUI5 calendars with day cells in cross-origin iframe, refs now appear tagged with (iframe: ...) and are clickable/fillable via page_click, page_fill, etc. * feat(extension): CDP-based page_click and page_fill via chrome.debugger - Add extension/src/background/cdp-input.ts with cdpClick() and cdpFill() using chrome.debugger.attach/sendCommand/detach for real browser input events - Add 'debugger' permission to extension/manifest.json - Add getElementTabRect() helper in dom-tools.ts that walks iframe chain to produce tab-absolute coordinates for CDP dispatch - Update click() to return bounding rect instead of dispatching synthetic DOM events; DOM .click() kept only for zero-rect (hidden/jsdom) elements - Update fill() to return FillOutcome with rect; DOM .value= kept as belt-and-suspenders (so React/Angular work); CDP fills SAPUI5/custom inputs - Add rect field to ClickOutcome, FillOutcome, PageToolRun, FrameToolResult, PageToolResult types; FrameToolResult.ok made optional (unused by mergeFrameResults) - Update service-worker.ts to import cdpClick/cdpFill, pass rect through mergeFrameResults, and dispatch CDP input after runPageToolAllFrames - Update tests: add rect assertions to page_click/page_fill tests, add new zero-rect fallback test (25 tests pass) - Update docs/architecture/chrome-extension.md with CDP input dispatch section and corrected permission list All 91 tests pass; both Vite bundles build cleanly. * fix: cross-origin iframe coordinate translation for CDP clicks Track winning frameId through mergeFrameResults; in runPageToolInTab, when frameId != 0, query the top frame via chrome.scripting.executeScript to find the iframe element's bounding rect and add it as an offset to the element's iframe-local coordinates before CDP dispatch. The content script's getElementTabRect() cannot traverse frameElement for cross-origin iframes (SecurityError), so it returns only iframe-local coords. This fix closes the gap so Input.dispatchMouseEvent lands at the correct tab-absolute position for elements inside cross-origin iframes (e.g. SAP Fiori CAT2 calendar embedded in an iframe). URL matching uses origin+pathname comparison (not exact URL equality) to handle cases where the frame URL has query params the iframe src does not. * chore(extension): raise MAX_PAGE_TOOL_ROUNDS from 8 to 15 Multi-step tasks like SAP CAT2 booking (snapshot → click day → snapshot → click task → snapshot → fill → save) can easily exceed 8 rounds. 15 gives enough room for complex workflows while still providing a safety ceiling. * build: rebuild extension bundles after MAX_PAGE_TOOL_ROUNDS increase * feat(extension): add stop button and disable input during streaming - Add a red stop button (filled square icon) inside the composer form, matching the Studio chat's variant=destructive stop button styling - Show the stop button while streaming; hide it when idle - Disable the textarea and show 'Agent is responding…' placeholder while streaming, matching the Studio chat behavior - On stop: abort the SSE connection, call POST /sessions/{id}/stop to kill the backend runner, and show a 'Stopped by user.' notice - Add stopChat() to astonish-client.ts for the stop API call - Add .stop-btn CSS class and .composer textarea:disabled opacity * theme(extension): switch from Nova to Classic color theme Update CSS variables to use Astonish Classic theme (indigo/blue) instead of Nova (pink/purple). This is the default theme and makes the extension consistent with the rest of the Astonish UI. Changes: - --brand: #ff6b9d → #6366f1 (Nova pink → Classic indigo) - --background: #160b1f → #0f172a (Nova dark → Classic dark slate) - --card: #1d0f28 → #1e293b - --foreground: #fceef7 → #f1f5f9 - --text-secondary/muted: Nova mauve → Classic slate - --danger: #f05252 → #ef4444 - --info: #b478ff → #a78bfa (purple → violet) * Fix: Stop re-injecting extension page-tool instructions on every round The EXTENSION_PAGE_TOOLS_INSTRUCTIONS were being appended to the context on every page-tool round, causing the system instructions to be echoed back to the user in the visible chat. The instructions are already part of the initial buildSystemContext() call, so they should NOT be re-appended after each tool execution. After page tools run, context should contain only formatPageToolResults(), not the full instructions again. Fixes the verbose 'user[timestamp] The Chrome extension ran...' leak. * feat(extension): add Astonish icon to extension Add Astonish logo as the extension icon in three sizes: - icon-16.png (16x16 for Chrome toolbar) - icon-48.png (48x48 for Chrome menu) - icon-128.png (128x128 for Chrome Web Store) Icons are exported from the Astonish logo SVG and configured in: - manifest.json: action.default_icons and icons sections - vite.config.ts: updated icon path handling for build The extension toolbar button now displays the Astonish branding instead of a generic placeholder icon. * fix(extension): render markdown tables and group tool notices Two UI defects in the extension side panel: 1. Markdown tables showed raw pipe syntax instead of rendered HTML tables. The renderMarkdown() parser now handles table rows (| col | col |), separator rows (| --- | --- |), and emits proper <table>/<thead>/<tbody> elements. Table CSS was already in panel.css; added overflow-x: auto for wide tables in the narrow side panel. 2. Tool call/result notices appeared as separate bubbles cluttering the transcript. Now they are grouped into a single collapsible 'Used N tools' block (matching Studio chat's tool fold pattern). Click to expand shows individual tool names. Both streaming (live) and history rendering use the same grouping. Added 2 new tests for table rendering (93 total tests passing). * feat(extension): per-tab session isolation in side panel * fix(extension): use service worker to communicate tab ID to side panel The previous approach of using chrome.tabs.query() from the side panel context was unreliable and would often return tabId=0, causing all tabs to share the same session storage slot. This fix uses chrome.runtime.sendMessage() to have the service worker directly send the tab ID to the side panel based on the sender context, which is reliable and works correctly across all tabs. * fix(extension): inline extension-sessions into sidepanel bundle; fix tab ID resolution - vite.config.ts: manualChunks routes extension-sessions and messages into the sidepanel entry chunk, eliminating the 'cross-world extension resource mismatch' modulepreload warning Chrome was showing in the extension errors panel - service-worker.ts: MSG_GET_TAB_ID handler now queries active tab via chrome.tabs.query (lastFocusedWindow) instead of reading sender.tab which is undefined for side panel extension pages — fixes the tabId always being 0 and causing all tabs to share the same session storage slot * fix(extension): strip modulepreload links from sidepanel HTML Chrome MV3 flags modulepreload for extension resources as a 'cross-world extension resource mismatch' error. The chunks still load correctly via dynamic imports, but the preload tags cause console warnings. Strip them out in the build plugin's HTML post-processing step. * debug: add console logging to tab ID resolution * fix(extension): use chrome.storage.session for per-tab session isolation Instead of manually keying sessions by tabId in chrome.storage.local, use chrome.storage.session which is automatically isolated per browser tab by Chrome. This is simpler, more reliable, and handles tab cleanup automatically (session storage is cleared when a tab closes). Benefits: - No manual tab ID resolution needed - No message passing between service worker and side panel - No service worker message listener required - Each tab's side panel gets its own storage context by default - Stale entries auto-cleaned when tab closes Changed extension-sessions.ts to use session storage with a single state key instead of a per-tab map in local storage. Updated tests accordingly. * cleanup: remove debug console logging * fix(extension): embed tabId in panel URL for reliable per-tab session isolation All previous approaches failed because there is no API to get a tab ID from inside a side panel page context. The correct solution (per Chrome docs and Chromium extensions mailing list) is to embed the tabId in the side panel URL as a query parameter when the service worker opens the panel. Changes: - service-worker.ts: Disable openPanelOnActionClick (which suppresses action.onClicked). Use chrome.action.onClicked to explicitly open the panel with path 'sidepanel.html?tabId=X', so each tab gets its own URL. - main.ts: Read tabId synchronously from location.search — zero async, zero message passing, 100% reliable. - extension-sessions.ts: Restored per-tab map keyed by tabId in chrome.storage.session (shared in-memory store, keyed by tabId). This gives real isolation: tab 42 reads map[42], tab 99 reads map[99]. - Tests: Restored all per-tab isolation tests including removeTabState and different-tabs tests. * fix(extension): make side panel per-tab like DevTools (F12) Remove default_path from manifest.json so the panel is not globally enabled. Set chrome.sidePanel.setOptions({ enabled: false }) globally. Only enable the panel per-tab when the user clicks the extension icon via action.onClicked. This makes the side panel behave like Chrome DevTools: - Click the icon on a tab → panel opens for that tab only - Switch to another tab → panel is not visible - Each tab that has the panel open has its own independent session Also cleaned up the now-unused MSG_GET_TAB_ID message handler since tab IDs are embedded in the panel URL. * fix(extension): page tools target the panel's tab, not the active tab The side panel now includes its tabId in every sendMessage call (MSG_GET_CONTEXT, MSG_PAGE_TOOL, MSG_APPLY). The service worker reads this tabId and uses chrome.tabs.get(tabId) instead of querying for the active tab. This prevents page tools from drifting to whichever tab the user is currently viewing when they switch away from the tab where the agent is working. * chore: ignore extension build output (dist/) * chore: remove extension/dist from git tracking * feat(extension): categorize extension sessions with dedicated appName - Add optional AppName field to StudioChatRequest; when set, sessions are created under that app name instead of the default 'astonish'. - Add optional ?app= query parameter to GET /api/studio/sessions; defaults to studioChatAppName for backward compatibility. - Define EXTENSION_APP_NAME = 'astonish-extension' constant in the extension. - connectChat now sends appName in the request body so new sessions are tagged. - fetchSessions now appends ?app=astonish-extension so the dropdown shows all extension sessions regardless of which tab created them, not just sessions whose IDs are in the tab's local storage. - Remove client-side filterExtensionSessions call from refreshExtensionSessions; server-side filtering via ?app= replaces it. Sessions created in another tab are now surfaced by adding backend-returned sessions missing from local state. - Add pkg/api/session_handlers_test.go testing StudioChatRequest appName field. * fix(extension): add side_panel.default_path to manifest Chrome MV3 requires the default_path field in the side_panel section. Without it, the extension fails to load with: 'Error at key side_panel.default_path. Manifest key is required.' * fix: propagate appName through session operations and query parameters This fixes critical app-name namespace defects that prevented the extension from reading, persisting, and deleting sessions correctly: Backend fixes: - session_handlers.go: StudioSessionHandler and StudioDeleteSessionHandler now read the optional ?app= query parameter (defaults to studioChatAppName for backward compatibility), so extension sessions (appName=astonish-extension) can be fetched and deleted using the correct namespace - chat_handlers.go: /new and /distill commands now use effectiveApp instead of hardcoded studioChatAppName - chat_utils.go, chat_runner.go: persistRunError and persistSessionMessage now accept an optional appName parameter (empty string uses context fallback), so slash commands persist events under the correct app namespace - chat_handlers.go: Added context helpers (getAppNameFromContext) and pass effectiveApp to handleSlashCommand so all session operations use the caller-provided app name Tests: - Updated handleSlashCommand test call to pass effectiveApp parameter Impact: - Extension sessions created under appName=astonish-extension are now accessible to all handlers that read/modify/delete them - Session history reload (GET detail) works correctly for extension sessions - Slash command persistence (/new, /distill) works with the correct app name - All existing clients (Studio chat) remain unaffected (backward-compatible) * fix: thread effectiveApp through ChatRunner and extension delete/history calls Critical #1+#2 (backend): - newChatRunner now accepts an appName parameter and injects it into cr.ctx via context.WithValue(ctx, appNameContextKey, appName). This makes the context fallback in persistRunError/persistSessionMessage live (previously appNameContextKey was defined and read but never set). - runner.New() now uses getAppNameFromContext(cr.ctx) instead of hardcoded studioChatAppName, so ADK transcript events are persisted under the same app namespace as the session record. - StudioChatHandler passes effectiveApp to newChatRunner. - All test call sites updated to pass studioChatAppName as the appName argument. Medium #3 (extension delete): - Added deleteSession() to astonish-client.ts — sends DELETE /api/studio/sessions/{id}?app=astonish-extension. - main.ts delete button handler now calls deleteSession() before deleteExtensionSession() (best-effort: local cleanup proceeds even on server error so the UI stays consistent). Medium #4 (extension history): - fetchSessionHistory() now appends ?app=astonish-extension to the GET URL so the backend reads from the correct session namespace. Tests: - fetchSessionHistory test updated to assert ?app= is present in the URL. - Two new tests for deleteSession: success (204) and error (404 → throws). * fix(ci+review): wire extension into CI, handler tests, sender check, trust docs - Fix CI Build check: use 'npm test' instead of 'npx vitest run' so the lockfile-pinned vitest@5.0.0 is used; fixes pre-existing TemplateDetail frontend test failure unrelated to PR #510 - Add build-extension job to build.yml: npm ci, tsc --noEmit, npm test, npm run build in extension/ on every push/PR - Wire build-extension into Makefile build-all so 'make build-all' includes the extension bundle - Expand session_handlers_test.go with TestStudioSessionsAppNameQueryParam (tests ?app= defaulting logic) and TestStudioChatRequestEffectiveApp (tests caller-appName vs studioChatAppName precedence) - Add sender.id === chrome.runtime.id guard to service-worker onMessage as defence-in-depth against future onMessageExternal exposure - Add 'Security and trust model' section to chrome-extension.md covering model-driven input + 15-round limit, https://* justification, debugger permission rationale, token storage trade-offs, and sender validation reasoning * fix(review): resolve CI typecheck, validate appName, fix use-before-decl - Add DOM.Iterable to tsconfig.json lib array — fixes all 10 tsc errors for NodeListOf/HTMLCollection iteration (Critical #1) - Add 'typecheck' npm script so local devs can run 'npm run typecheck' matching the CI gate; update build.yml to use it - Fix skipPageTools let-before-declaration in main.ts — move the declaration above its first use (found by tsc after DOM.Iterable fix) - Add validAppName regex (^[a-z][a-z0-9_-]{0,63}$) and enforce it in StudioSessionsHandler, StudioSessionHandler, StudioDeleteSessionHandler, and StudioChatHandler — blocks path traversal, namespace squatting, and unbounded key lengths (Medium #2) - Replace tautological session handler tests with tests that exercise the real validAppName regex, validate the resolution+validation code path end-to-end, and assert rejection of traversal/injection attempts (Medium #4) * fix(review): appName isolation at meta lookup, use npm ci, fix stale studioChatAppName - Defense-in-depth: after GetSessionMeta (which looks up by globally-unique session ID without appName filtering), verify meta.AppName == appName and return 404 on mismatch. Applied to StudioSessionHandler (platform + file store paths) and StudioDeleteSessionHandler (platform path). This makes ?app= a real isolation boundary rather than advisory. (Medium #1) - Replace hardcoded studioChatAppName with resolved appName in the personal-mode file-store paths of StudioSessionHandler: fleet transcript filepath.Join and the non-fleet fs.Get call. (Minor #1) - Use npm ci instead of npm install in Makefile build-extension target so the lockfile is never mutated by a local build. (Medium #3) * fix(review): reorder delete handler to verify namespace before fleet cleanup - Move fleet Stop()/Cleanup()/Unregister() to AFTER the app-namespace verification in both platform-mode and personal-mode delete paths. Previously the fleet registry teardown (which destroys sandbox containers) ran unconditionally before any namespace check. Now the handler verifies meta.AppName == appName first, and only then proceeds to stop the fleet session. (Medium #1) - Fail closed on GetSessionMeta errors in the platform-mode delete path: if the meta lookup errors or returns nil, return 404 instead of skipping the namespace check and falling through to Delete. (Medium #1) - Add namespace verification to the personal-mode delete path: check file store meta.AppName before fleet cleanup and workspace deletion. - Fix hardcoded studioChatAppName in personal-mode Delete call to use the resolved appName variable. (leftover from prior round) * fix(extension): interleave messages and tool groups chronologically - Refactor streamOnce() to track per-segment text: each text bubble only shows text from its current segment (since the last tool group), not the full accumulated assistant text. This fixes the regression where each new bubble showed all prior text concatenated. - Add hadToolsSinceText flag: when a tool_call fires, sets the flag; when the next text chunk arrives, isNewSegment is raised and segmentStart advances to the current position in the full text. - Update outer onText callback to create a new assistantBody when isNewSegment is true, so text arriving after tool groups gets its own bubble rather than appending to the previous one. - Replace per-tool appendNotice() calls with appendOrUpdateToolGroup(): consecutive tool calls share one collapsible <details> element (showing name + truncated args + completion checkmark). A new tool group is started whenever non-tool content appears between tools. - Extend renderHistory() to use the same appendOrUpdateToolGroup() for tool_call messages, so historical sessions render with the same collapsible groups as live streaming. - Add toolArgs field to HistoryMessage type for future args display in history (currently sent by backend but not yet used). - Add CSS for .msg-tool-group, .tool-group-details, .tool-group-summary, .tool-group-item, .tool-call-name, .tool-args-preview, .tool-status. * fix(extension): restore indigo color theme and extension icons Restore the correct indigo color palette that was accidentally corrupted in the working tree (changed from #6366f1 indigo to #ff6b9d pink/magenta). Also restore extension icons to manifest (icon-16, icon-48, icon-128) and the correct side_panel.default_path (src/sidepanel/index.html instead of sidepanel.html). * fix(extension): restore icon file paths in vite build config Update vite.config.ts to correctly copy icons from public/icons source to dist/public/icons output, matching the manifest.json icon references. Also update file name patterns from icon16.png to icon-16.png (with hyphens) to match the actual icon filenames. * fix(extension): restore per-tab session isolation with interleaved tool groups Restore the tab-scoped session architecture (currentTabId / resolveTabId) that was lost when main.ts was edited from a corrupted working tree state. Also re-apply the tool-group interleaving improvements on top of the correct foundation. Key fixes: - Restore currentTabId + resolveTabId() from URL ?tabId= param (tab isolation) - Restore MAX_PAGE_TOOL_ROUNDS = 15 (was reverted to 8) - Restore all loadExtensionChat(currentTabId), replaceExtensionSessions(currentTabId, ...), startNewExtensionSession(currentTabId), rememberExtensionSession(currentTabId, ...) call sites with the tab-scoped API - Re-apply appendOrUpdateToolGroup() for <details>-based collapsible groups with tool name + args preview + completion checkmarks - Re-apply segment-text fix: each assistant bubble shows only text from its segment (since the last tool group), not the full accumulated text - Re-apply isNewSegment signal threading through streamOnce → outer onText so text arriving after tool groups gets a fresh bubble - Update renderHistory() to use appendOrUpdateToolGroup() for history replay * fix(api): inject effectiveApp into slash-command context; fail-closed delete - handleSlashCommand: replace ctx := r.Context() with context.WithValue(r.Context(), appNameContextKey, effectiveApp) so all persistSessionMessage and persistDistillPreview calls inside resolve the correct app namespace instead of always defaulting to 'astonish'. Fixes silent message drop for extension sessions. - StudioDeleteSessionHandler (personal/file-store path): align with the Ent branch by failing closed when GetSessionMeta errors instead of falling through to Delete. Prevents orphaning fleet sandboxes and workspaces on transient meta-lookup errors. Addresses medium and minor issues from PR #510 code review. * ci: include Chrome extension zip in GitHub Releases Add a build-extension job to both release.yml and beta-release.yml that builds the Chrome extension, packages extension/dist into astonish-extension.zip, and uploads it as a workflow artifact. The existing release job already downloads all artifacts and publishes ./release/astonish-* via softprops/action-gh-release, so the zip is automatically included alongside the platform binaries. * docs: add Chrome extension user documentation Add docs/website/docs/studio/chrome-extension.md covering: - Requirements (Chrome 114+, running Astonish instance) - Installation from release zip (unpacked extension) - Sign-in flow (bearer token, Studio URL) - Side panel UI (session picker, page context, transcript) - Page tools reference table (page_snapshot/click/fill/navigate/etc) - Apply-to-page workflow and two-step handshake for read-only fields - Session isolation (astonish-extension appName) - Troubleshooting Wire into sidebar nav under Studio > Chrome Extension and add link to Studio overview's Related Pages section.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.