From fbc9b76022d1061aae9590f132b9c39cb0daa5bf Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Fri, 2 Oct 2026 13:20:06 +0330 Subject: [PATCH 1/2] feat: add inbox and URL watchers --- README.md | 4 + docs/SETUP.md | 10 ++ src/client/App.tsx | 232 +++++++++++++++++++++++++++++++++-- src/client/style.css | 100 +++++++++++++++ src/server/app.ts | 72 +++++++++++ src/server/index.ts | 8 +- src/server/store.ts | 183 +++++++++++++++++++++++++++ src/server/watcher-runner.ts | 188 ++++++++++++++++++++++++++++ src/shared/types.ts | 26 ++++ tests/store.test.ts | 31 +++++ 10 files changed, 842 insertions(+), 12 deletions(-) create mode 100644 src/server/watcher-runner.ts diff --git a/README.md b/README.md index b1ea8f2..c6248d4 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,10 @@ https://github.com/user-attachments/assets/3c06cf71-39ed-4e2b-b846-5463b2722389 _Connect, talk, mute, minimize, and return to chat. This is a silent screen capture of a real call, with waiting time trimmed and playback accelerated._ +### Inbox and Watchers + +OpenDots includes a small proactive layer on top of scheduled work. The Inbox collects completed and failed task outcomes and watcher triggers in one place. Watchers monitor public HTTP(S) URLs and queue a normal task in an existing conversation when content changes. + ### Slack Mention a Dot through a managed Slack connection using Channels SDK, then continue in its thread. The integration follows [OpenTag](https://github.com/CopilotKit/OpenTag), with an explicit workspace/user allowlist and a selected specialist. See [Slack setup](docs/SETUP.md#slack) to connect your deployment. diff --git a/docs/SETUP.md b/docs/SETUP.md index 5d883ce..b3b5bfb 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -186,3 +186,13 @@ npm run build ``` Automated tests use service fixtures. Live model, Intelligence, Slack, and voice verification requires your own configured services. + +## Inbox and Watchers + +The Inbox collects completed and failed background-task outcomes, plus notifications when a Watcher detects a change. + +A Watcher monitors a public HTTP(S) URL at an interval from one minute to 24 hours. The first successful check establishes a baseline. Later content changes queue a normal OpenDots task in the selected conversation, so the existing Dot, tools, permissions, and task history handle the follow-up. + +Open **Scheduled & activity** to add a Watcher. Choose an existing conversation, enter the URL and investigation prompt, and choose the interval. Watchers can be paused, resumed, or deleted. + +Watcher requests reject local/private destinations, URL credentials, oversized responses, and excessive redirects. Keep remote OpenDots deployments protected with owner authentication and HTTPS. diff --git a/src/client/App.tsx b/src/client/App.tsx index 353b631..9fe6766 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -6,6 +6,7 @@ import { CopilotKitProvider } from '@copilotkit/react-core/v2'; import { ArrowUp, ArrowUpRight, + Bell, BookOpen, Clock3, Code2, @@ -45,9 +46,9 @@ export function App() { const [workspace, setWorkspace] = useState(); const [selectedDot, setSelectedDot] = useState(''); const [selectedThread, setSelectedThread] = useState(); - const [view, rawSetView] = useState<'chat' | 'tasks' | 'memories' | 'space'>( - 'chat', - ); + const [view, rawSetView] = useState< + 'chat' | 'tasks' | 'memories' | 'space' | 'inbox' + >('chat'); const dirtyPage = useRef(false); const [spaceId, setSpaceId] = useState(''); const [pageId, setPageId] = useState(); @@ -289,8 +290,8 @@ export function App() { > - )} - {view === 'memories' ? ( + {view === 'inbox' ? ( + <> +
+ {(state.inbox ?? []).map((item) => ( +
+
+ +
+
+ {item.title} + {new Date(item.createdAt).toLocaleString()} +

{item.body}

+
+ {item.taskId && ( + + )} + + +
+
+
+ ))} +
+ {!(state.inbox ?? []).length && ( +
+ +

Nothing waiting for you.

+

Completed work, failures, and watcher triggers appear here.

+
+ )} + + ) : view === 'memories' ? ( <>
{state.memories.map((memory) => ( @@ -773,6 +859,130 @@ export function App() { ) : ( <> +
+
+

Watchers

+ Queue a task when a public page changes. +
+ {workspace.conversations.length ? ( +
{ + event.preventDefault(); + const form = event.currentTarget; + const data = new FormData(form); + const minutes = Number(data.get('minutes') ?? 15); + const url = String(data.get('url') ?? '').trim(); + const prompt = String(data.get('prompt') ?? '').trim(); + const threadId = String(data.get('threadId') ?? ''); + if ( + !url || + !prompt || + !threadId || + !Number.isFinite(minutes) || + minutes < 1 || + minutes > 1440 + ) { + setError('Complete the watcher fields with a 1–1440 minute interval.'); + return; + } + const ok = await mutate('/watchers', 'POST', { + url, + prompt, + threadId, + intervalSeconds: Math.round(minutes * 60), + }); + if (ok) form.reset(); + }} + > + +