-
Notifications
You must be signed in to change notification settings - Fork 24
feat(desktop): add persistent memory recall and conversation search #440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
129373b
ae18fe4
0415130
1ff4e2d
0b582fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| export async function openSettingsWindow() { | ||
| const mainWindowHandle = await browser.getWindowHandle(); | ||
| let settingsHandle = null; | ||
|
|
||
| await browser.waitUntil(async () => { | ||
| return browser.execute(() => Boolean(window.__TOUCHAI_E2E__)); | ||
| }); | ||
|
|
||
| await browser | ||
| .executeAsync((done) => { | ||
| window.__TOUCHAI_E2E__ | ||
| .openSettingsWindow() | ||
| .then(() => done({ ok: true })) | ||
| .catch((error) => done({ ok: false, error: String(error) })); | ||
| }) | ||
| .then((result) => { | ||
| if (!result?.ok) { | ||
| throw new Error( | ||
| `Failed to open settings window: ${result?.error ?? 'unknown error'}` | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| await browser.waitUntil(async () => { | ||
| const handles = await browser.getWindowHandles(); | ||
| for (const handle of handles) { | ||
| if (handle === mainWindowHandle) { | ||
| continue; | ||
| } | ||
|
|
||
| await browser.switchToWindow(handle); | ||
| const currentUrl = await browser.getUrl(); | ||
| if (currentUrl.includes('/settings')) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Consider more precise URL matching for the settings window. The substring check ♻️ More precise URL matching- if (currentUrl.includes('/settings')) {
+ if (currentUrl.endsWith('/settings') || currentUrl.includes('/settings?') || currentUrl.includes('/settings#')) {
settingsHandle = handle;
return true;
}Or use a URL object for more reliable parsing: await browser.switchToWindow(handle);
const currentUrl = await browser.getUrl();
- if (currentUrl.includes('/settings')) {
+ const url = new URL(currentUrl);
+ if (url.pathname === '/settings' || url.pathname.endsWith('/settings')) {
settingsHandle = handle;
return true;
}🤖 Prompt for AI Agents |
||
| settingsHandle = handle; | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| await browser.switchToWindow(mainWindowHandle); | ||
| return false; | ||
| }); | ||
|
|
||
| if (!settingsHandle) { | ||
| throw new Error('Unable to locate settings window handle.'); | ||
| } | ||
|
|
||
| await browser.switchToWindow(settingsHandle); | ||
| return { mainWindowHandle, settingsHandle }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import { openSettingsWindow } from '../helpers/openSettingsWindow.js'; | ||
|
|
||
| describe('TouchAI settings memory acceptance', () => { | ||
| it('persists a completed memory when the user switches sections immediately', async () => { | ||
| const { mainWindowHandle } = await openSettingsWindow(); | ||
| const settingsView = await $("[data-testid='settings-view']"); | ||
| const memoryNav = await $("[data-testid='settings-nav-memory']"); | ||
| const generalNav = await $("[data-testid='settings-nav-general']"); | ||
|
|
||
| await settingsView.waitForDisplayed(); | ||
| await memoryNav.waitForDisplayed(); | ||
| await memoryNav.click(); | ||
|
|
||
| const addButton = await $("[data-testid='settings-memory-add-button']"); | ||
| await addButton.waitForDisplayed(); | ||
| await addButton.click(); | ||
|
|
||
| const titleInput = await $("[data-testid='settings-memory-title-input']"); | ||
| const applicabilityInput = await $("[data-testid='settings-memory-applicability-input']"); | ||
| const contentInput = await $("[data-testid='settings-memory-content-input']"); | ||
|
|
||
| await titleInput.waitForDisplayed(); | ||
| await titleInput.setValue('Desktop workflow'); | ||
| await applicabilityInput.setValue('When TouchAI settings or tray flows matter'); | ||
| await contentInput.setValue( | ||
| 'TouchAI is a desktop agent. Verify settings and tray behavior before answering.' | ||
| ); | ||
|
|
||
| await generalNav.click(); | ||
|
|
||
| const generalSection = await $("[data-testid='settings-general-section']"); | ||
| await generalSection.waitForDisplayed(); | ||
|
|
||
| await memoryNav.click(); | ||
| const persistedTitleInput = await $("[data-testid='settings-memory-title-input']"); | ||
| const persistedApplicabilityInput = await $( | ||
| "[data-testid='settings-memory-applicability-input']" | ||
| ); | ||
| const persistedContentInput = await $("[data-testid='settings-memory-content-input']"); | ||
| await persistedTitleInput.waitForDisplayed(); | ||
|
|
||
| await browser.waitUntil(async () => { | ||
| return (await persistedTitleInput.getValue()) === 'Desktop workflow'; | ||
| }); | ||
| await browser.waitUntil(async () => { | ||
| return ( | ||
| (await persistedApplicabilityInput.getValue()) === | ||
| 'When TouchAI settings or tray flows matter' | ||
| ); | ||
| }); | ||
| await browser.waitUntil(async () => { | ||
| return ( | ||
| (await persistedContentInput.getValue()) === | ||
| 'TouchAI is a desktop agent. Verify settings and tray behavior before answering.' | ||
| ); | ||
| }); | ||
|
|
||
| const memoryItems = await $$("[data-testid^='settings-memory-item-']"); | ||
| if (memoryItems.length !== 1) { | ||
| throw new Error( | ||
| `Expected exactly one persisted memory item, received ${memoryItems.length}.` | ||
| ); | ||
| } | ||
|
|
||
| await browser.closeWindow(); | ||
| await browser.switchToWindow(mainWindowHandle); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| CREATE TABLE `memory_items` ( | ||
| `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, | ||
| `title` text NOT NULL, | ||
| `applicability` text NOT NULL, | ||
| `content` text NOT NULL, | ||
| `enabled` integer DEFAULT 1 NOT NULL, | ||
| `source_session_id` integer, | ||
| `source_message_id` integer, | ||
| `created_at` text DEFAULT (datetime('now')) NOT NULL, | ||
| `updated_at` text DEFAULT (datetime('now')) NOT NULL, | ||
| `last_used_at` text, | ||
| FOREIGN KEY (`source_session_id`) REFERENCES `sessions`(`id`) ON UPDATE no action ON DELETE set null, | ||
| FOREIGN KEY (`source_message_id`) REFERENCES `messages`(`id`) ON UPDATE no action ON DELETE set null | ||
| ); | ||
| --> statement-breakpoint | ||
| CREATE INDEX `memory_items_enabled_idx` ON `memory_items` (`enabled`);--> statement-breakpoint | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | 💤 Low value Minor: Statement-breakpoint comment formatting inconsistency. The statement-breakpoint comment is inline on line 16 but on a separate line at line 15. This is cosmetic and doesn't affect functionality, but creates slight inconsistency in the migration file formatting. 🤖 Prompt for AI Agents |
||
| CREATE INDEX `memory_items_updated_at_idx` ON `memory_items` (`updated_at`); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Add JSDoc documentation for the exported helper.
As a shared E2E helper used across multiple test files, this function should have JSDoc comments explaining its purpose, return value, and error conditions to improve maintainability.
📝 Suggested JSDoc
📝 Committable suggestion
🤖 Prompt for AI Agents