added button for links

This commit is contained in:
Arjun 2026-04-25 11:12:57 +05:30
parent 9fa2d1d5e3
commit 8cb55a903d
9 changed files with 150 additions and 20 deletions

View file

@ -85,6 +85,8 @@ ${thirdPartyBlock}**Meeting Prep:** When users ask you to prepare for a meeting,
**Tracks (Auto-Updating Note Blocks):** When users ask you to **track**, **monitor**, **watch**, or **keep an eye on** something in a note or say things like "every morning tell me X", "show the current Y in this note", "pin live updates of Z here" load the \`tracks\` skill first. Also load it when a user presses Cmd+K with a note open and requests auto-refreshing content at the cursor. Track blocks are YAML-fenced scheduled blocks whose output is rewritten on each run — useful for weather, news, prices, status pages, and personal dashboards.
**Browser Control:** When users ask you to open a website, browse in-app, search the web in the embedded browser, or interact with a live webpage inside Rowboat, load the \`browser-control\` skill first. It explains the \`read-page -> indexed action -> refreshed page\` workflow for the browser pane.
**Notifications:** When you need to send a desktop notification completion alert after a long task, time-sensitive update, or a clickable result that lands the user on a specific note/view load the \`notify-user\` skill first. It documents the \`notify-user\` tool and the \`rowboat://\` deep links you can attach to it.
## Learning About the User (save-to-memory)

View file

@ -14,6 +14,7 @@ import appNavigationSkill from "./app-navigation/skill.js";
import browserControlSkill from "./browser-control/skill.js";
import composioIntegrationSkill from "./composio-integration/skill.js";
import tracksSkill from "./tracks/skill.js";
import notifyUserSkill from "./notify-user/skill.js";
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
const CATALOG_PREFIX = "src/application/assistant/skills";
@ -112,6 +113,12 @@ const definitions: SkillDefinition[] = [
summary: "Control the embedded browser pane - open sites, inspect page state, and interact with indexed page elements.",
content: browserControlSkill,
},
{
id: "notify-user",
title: "Notify User",
summary: "Send native desktop notifications with optional clickable links — including rowboat:// deep links that open a specific note, chat, or view inside the app.",
content: notifyUserSkill,
},
];
const skillEntries = definitions.map((definition) => ({

View file

@ -0,0 +1,85 @@
export const skill = String.raw`
# Notify User
Load this skill when you need to send a desktop notification to the user e.g. after a long-running task completes, when a track block detects something noteworthy, or when an agent wants to ping the user with a clickable result.
## When to use
- **Use it for**: completion alerts, threshold breaches, status changes, new items the user asked you to watch for, anything time-sensitive.
- **Don't use it for**: routine progress updates, anything the user can already see in the chat, or repeated pings inside a loop (there is no built-in rate limit restraint is on you).
## The tool: \`notify-user\`
Triggers a native macOS notification. The call returns immediately; it does not block waiting for the user to click.
### Parameters
- **\`title\`** (optional, defaults to \`"Rowboat"\`) — bold headline at the top.
- **\`message\`** (required) — body text. Keep it short — macOS truncates after a couple of lines.
- **\`link\`** (optional) — URL to open when the user clicks the notification or its action button. Two kinds accepted:
- **\`https://...\` / \`http://...\`** — opens in the default browser
- **\`rowboat://...\`** — opens a view inside Rowboat (see deep links below)
- If omitted, clicking the notification focuses the Rowboat app.
- **\`actionLabel\`** (optional, defaults to \`"Open"\`) — label for the inline action button. Only shown when \`link\` is set. Keep it to 1-2 words: \`"Open"\`, \`"View"\`, \`"Read"\`, \`"Reply"\`. Pick a verb that names what clicking will do.
### Why the action button matters
When \`link\` is set, an action button is shown inline on the notification (the same way Slack shows "Reply" or Mail shows "Mark as Read"). This button is **the recommended click target** — it's a clear CTA and it's more reliable than expecting the user to click the notification body. Body click also works as a fallback.
### Examples
Plain alert (no link clicking focuses the app):
\`\`\`json
{
"title": "Backup complete",
"message": "All 142 files synced to iCloud."
}
\`\`\`
External link:
\`\`\`json
{
"title": "New email from Monica",
"message": "Re: Q4 planning — needs your input by Friday",
"link": "https://mail.google.com/mail/u/0/#inbox/abc123"
}
\`\`\`
Deep link into a Rowboat note (default "Open" button):
\`\`\`json
{
"message": "Daily brief is ready",
"link": "rowboat://open?type=file&path=knowledge/Daily/2026-04-25.md"
}
\`\`\`
Custom action label:
\`\`\`json
{
"title": "Stripe charge declined",
"message": "Card ending 4242 — retry from the dashboard",
"link": "https://dashboard.stripe.com/payments/pi_abc",
"actionLabel": "Review"
}
\`\`\`
## Deep links: \`rowboat://\`
Use these as the \`link\` parameter to land the user on a specific view in Rowboat instead of an external site. URL-encode paths/names that contain spaces or special characters.
| Target | Format | Example |
|---|---|---|
| Open a file | \`rowboat://open?type=file&path=<workspace-relative path>\` | \`rowboat://open?type=file&path=knowledge/People/Acme.md\` |
| Open chat | \`rowboat://open?type=chat\` (optional \`&runId=<id>\`) | \`rowboat://open?type=chat&runId=abc123\` |
| Knowledge graph | \`rowboat://open?type=graph\` | — |
| Background task view | \`rowboat://open?type=task&name=<task-name>\` | \`rowboat://open?type=task&name=daily-brief\` |
| Suggested topics | \`rowboat://open?type=suggested-topics\` | — |
The \`type=file\` path is workspace-relative (the same path you'd pass to \`workspace-readFile\`).
## Anti-patterns
- **Don't notify per step** of a multi-step task. Notify on completion, not on progress.
- **Don't repeat what's already on screen.** If the result is already in the chat or in a track block the user is viewing, skip the notification.
- **Don't dump the result into \`message\`.** Surface the headline; put the detail behind a deep link or external link.
- **Don't notify silently-failing things either.** If something failed, say so in the message — don't swallow the failure into a generic "done".
`;
export default skill;

View file

@ -1521,9 +1521,10 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
inputSchema: z.object({
title: z.string().min(1).max(120).optional().describe("Bold headline shown at the top of the notification. Defaults to 'Rowboat'."),
message: z.string().min(1).describe("Body text of the notification."),
link: z.string().url().refine((v) => /^https?:\/\//i.test(v), {
message: "link must be an http:// or https:// URL",
}).optional().describe("Optional http(s) URL opened when the user clicks the notification."),
link: z.string().url().refine((v) => /^(https?|rowboat):\/\//i.test(v), {
message: "link must be an http(s):// or rowboat:// URL",
}).optional().describe("Optional URL opened when the user clicks the notification. Accepts http(s):// (opens in browser) or rowboat:// (opens a view inside Rowboat — see the notify-user skill for deep-link shapes)."),
actionLabel: z.string().min(1).max(20).optional().describe("Label for the action button shown when `link` is set. Defaults to 'Open'. Keep it short — 1-2 words like 'Open', 'View', 'Read', 'Reply'. Ignored when no link is provided."),
}),
isAvailable: async () => {
try {
@ -1532,13 +1533,13 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
return false;
}
},
execute: async ({ title, message, link }: { title?: string; message: string; link?: string }) => {
execute: async ({ title, message, link, actionLabel }: { title?: string; message: string; link?: string; actionLabel?: string }) => {
try {
const service = container.resolve<INotificationService>('notificationService');
if (!service.isSupported()) {
return { success: false, error: 'Notifications are not supported on this system' };
}
service.notify({ title, message, link });
service.notify({ title, message, link, actionLabel });
return { success: true };
} catch (error) {
return {

View file

@ -2,6 +2,7 @@ export interface NotifyInput {
title?: string;
message: string;
link?: string;
actionLabel?: string;
}
export interface INotificationService {

View file

@ -263,6 +263,7 @@ You have the full workspace toolkit. Quick reference for common cases:
- **\`parseFile\`, \`LLMParse\`** — parse PDFs, spreadsheets, Word docs if a track aggregates from attached files.
- **\`composio-*\`, \`listMcpTools\`, \`executeMcpTool\`** — user-connected integrations (Gmail, Calendar, etc.). Prefer these when a track needs structured data from a connected service the user has authorized.
- **\`browser-control\`** — only when a required source has no API / search alternative and requires JS rendering.
- **\`notify-user\`** — send a native desktop notification when this run produces something time-sensitive (threshold breach, urgent change, "the thing the user asked you to watch for just happened"). Skip it for routine refreshes — the note itself is the artifact. Load the \`notify-user\` skill via \`loadSkill\` for parameters and \`rowboat://\` deep-link shapes (so the click lands on the right note/view).
# The Knowledge Graph