Commit graph

1567 commits

Author SHA1 Message Date
arkml
72ed4bd6d9
pull browser-harness skills (#519)
use browser-harness skill without eval or http-fetch
2026-05-06 12:25:10 +05:30
arkml
e54b5cd27f
Background agents (#530)
a common place to track and add background agents
2026-05-06 11:59:37 +05:30
Arjun
7b119fbfcd refine note-writing instructions for self-reference and relationship phrasing 2026-05-05 19:56:57 +05:30
Arjun
c6083de054 show errors in activity tab for knowledge graph 2026-05-05 19:21:32 +05:30
Arjun
c382e3ee8a use gemini as default kg model 2026-05-05 16:08:57 +05:30
Ramnique Singh
d4850dace7 feat: native google sign-in for signed-in users
Signed-in users can now connect Gmail and Calendar directly through
Rowboat instead of going through Composio. Cleaner connection, no
third-party in the data path.

How it works:
- Click "Connect Google" anywhere it appears (sidebar, onboarding,
  settings) and the system browser opens to a Rowboat-hosted page.
  Authorize Google there and the app picks up the connection
  automatically — no client id or secret to paste.
- Token refresh happens through Rowboat's backend, so Google
  credentials never need to live on the user's machine.
- Disconnect cleanly revokes access on Google's side too.

Migration for existing Composio users:
- A one-time modal explains that we've moved off Composio and asks the
  user to reconnect Google directly.
- Their old Composio Gmail / Calendar connections are disconnected
  automatically when the modal first appears.
- All previously-synced emails and calendar events are preserved on
  disk — the new connection picks up where Composio left off rather
  than re-downloading the last week from scratch.
- "I'll do this later" dismisses the modal permanently; the user can
  still reconnect anytime via the connectors UI. (Sync stops in the
  meantime; nothing is deleted.)

Other coverage:
- BYOK mode (users who paste their own Google client id + secret) is
  unchanged — same modal, same local OAuth flow, same behavior.
- Composio integrations for non-Google services (Slack, Linear, etc.)
  are unaffected. Only the Gmail and Calendar paths moved.
- The "Connect Google" button label and connection state now apply
  uniformly to Gmail + Calendar (one OAuth grant covers both).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:29:13 +05:30
Arjun
a76f8bae14 fix sticky browser issue 2026-05-05 11:41:08 +05:30
Arjun
0bd234ddf6 fix browser reload issue 2026-05-04 17:28:04 +05:30
Arjun
93feee15a0 fixed collapsed sidebar issue on chat 2026-05-04 17:20:19 +05:30
arkml
1c2b2ac1fc
feat: native desktop notifications + rowboat:// deep links
Adds INotificationService with an Electron implementation, plus a deep-link
dispatcher (rowboat://) for routing notification clicks back into the app.

Notifications:
- New `notify-user` skill + builtin tool. Title, message, optional primary
    link, optional secondary actions. Supports https:// (opens in browser) and
    rowboat:// (opens in app) targets.
- ElectronNotificationService holds strong refs to active Notification
    instances so click handlers survive GC (otherwise macOS click silently
    no-ops).
- Calendar meeting notifier fires 1-min warnings with "take notes" /
    "join + take notes" actions backed by deep links.

Deep links (rowboat://):
- forge.config.cjs declares the protocol; main.ts wires single-instance
    lock, setAsDefaultProtocolClient, open-url (mac), second-instance (win/
    linux), and first-launch argv extraction.
- New deeplink.ts dispatcher with dispatchUrl(url): main-handled actions
    (rowboat://action?type=...) vs renderer navigation (rowboat://open?...)
    via app:openUrl IPC. Includes pending-URL buffering for first-launch
    delivery before the renderer is ready.
- Renderer parseDeepLink supports file / chat / graph / task /
    suggested-topics targets.
- New app:consumePendingDeepLink IPC for renderer one-time drain on mount.

Refactor: extractConferenceLink moved out of calendar-block.tsx into
shared lib/calendar-event.ts (used by both the block and the take-notes
deep-link handler)
2026-05-04 15:47:30 +05:30
Ramnique Singh
9ed54e2b94
Merge pull request #525 from rowboatlabs/feat/tool-call-grouping
feat: group consecutive tool calls into collapsible summary
2026-04-29 11:07:50 +05:30
Ramnique Singh
17afc935bf
Merge pull request #524 from rowboatlabs/dev
identify signed-in users on every app startup
2026-04-28 20:22:26 +05:30
Ramnique Singh
de176ec458 identify signed-in users on every app startup
Previously identify() only fired during the OAuth completion flow, so
existing installs (signed in before analytics shipped) and every cold
start of v0.3.4+ would emit main-process events under the anonymous
installation_id until the user happened to re-sign-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:21:37 +05:30
Gagancreates
4ca03daa4c feat: group consecutive tool calls into collapsible summary
Consecutive plain tool calls are now grouped into a single collapsible
row instead of rendering as individual items.

- Header shows the currently-executing tool name live with a vertical
  ticker animation, then switches to "Ran N tools" on completion
- Expanding the group reveals each tool call individually collapsible
- Tool calls with pending permission requests render individually
- Special cards (web search, composio connect, app actions) excluded
2026-04-28 20:10:13 +05:30
Ramnique Singh
0dff57e8f7
Merge pull request #523 from rowboatlabs/dev
add posthog analytics for llm usage and auth events
2026-04-28 20:10:13 +05:30
Ramnique Singh
43c1ba719f add posthog analytics for llm usage and auth events
Captures per-LLM-call token usage tagged by feature (copilot chat,
track block, meeting note, knowledge sync), plus sign-in / sign-out
and identity. Renderer and main share one PostHog identity so events
from either process resolve to the same user.

See apps/x/ANALYTICS.md for the event catalog, person properties,
use-case taxonomy, and how to add new events.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:53:40 +05:30
arkml
f14f3b0347
Merge pull request #520 from rowboatlabs/dev
Dev
2026-04-24 18:44:24 +05:30
Ramnique Singh
d42fb26bcc allow per-track model + provider overrides
Track block YAML gains optional `model` and `provider` fields. When set,
the track runner passes them through to `createRun` so this specific
track runs on the chosen model/provider; when unset the global default
flows through (`getTrackBlockModel()` + the resolved provider).

The track skill picks up the new fields automatically via the embedded
`z.toJSONSchema(TrackBlockSchema)` and adds an explicit "Do Not Set"
section: copilot leaves them omitted unless the user named a specific
model or provider for the track. Common bad reasons ("might be faster",
"in case it matters", complex instruction) are called out so the
defaults stay the path of least resistance.

Track modal Details tab shows the values when set, in the same
conditional `<dt>/<dd>` style as the lastRun fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:58:18 +05:30
Ramnique Singh
caf00fae0c configurable kg / meeting / track-block model overrides
Bring back per-category model selection that 5c4aa772 dropped, plus add a
new track-block category. Each is a BYOK-only override on `LlmModelConfig`
(`knowledgeGraphModel`, `meetingNotesModel`, `trackBlockModel`); signed-in
users always get the curated gateway default and never hit the on-disk
config.

Three helpers in core/models/defaults.ts — `getKgModel`,
`getTrackBlockModel`, `getMeetingNotesModel` — each check `isSignedIn`
first (fast path) and fall through to `cfg.<field> ?? cfg.model` for BYOK.

The model is now picked at the invocation site rather than via runtime
agent-name branching: each top-level `createRun` for a polling KG agent
or a track-block update passes `model: await getXxxModel()`. The `model:`
declarations on the affected agent YAMLs are dropped — they were dead
code under the per-call override. Standalone (non-run) callers
`track/routing` and `summarize_meeting` use the helpers inline.

Settings dialog and the two onboarding flows surface the two new fields
("Meeting Notes Model", "Track Block Model") next to the existing
"Knowledge Graph Model"; `repo.setConfig` persists all three per-provider.

Note: the signed-in `RowboatModelSettings` panel still has its
now-defunct kg selector; that's a UI cleanup for a later pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:44:02 +05:30
Ramnique Singh
bdf270b7a1 convert Today.md track blocks to event-driven and batch Gmail sync events
Removes polling schedules from the up-next and calendar track blocks on
Today.md so they refresh only on calendar.synced events, and rewrites
the emails track instruction to consume a multi-thread digest payload.
Batches Gmail sync so one email.synced event covers a whole sync run
(capped at 10 threads per digest) instead of one event per thread,
which collapses Pass 1 routing calls for multi-thread syncs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:15:56 +05:30
Arjun
0bb256879c preserve formatting in chat input text 2026-04-23 21:29:51 +05:30
Arjun
75842fa06b assistant chat ui shows the model name properly 2026-04-23 00:49:06 +05:30
Arjun
f4dbb58a77 add rowboat meeting notes to graph 2026-04-23 00:35:08 +05:30
Ramnique Singh
5c4aa77255 freeze model + provider per run at creation time
The model dropdown was broken in two ways: it wrote to ~/.rowboat/config/models.json
(the BYOK creds file, stamped with a fake `flavor: 'openrouter'` to satisfy zod
when signed in), and the runtime ignored that write entirely for signed-in users
because `streamAgent` hard-coded `gpt-5.4`. Model selection was also globally
scoped, so every chat shared one brain.

This change moves model + provider out of the global config and onto the run
itself, resolved once at runs:create and frozen for the run's lifetime.

## Resolution

`runsCore.createRun` resolves per-field, falling through:

  run.model    = opts.model    ?? agent.model    ?? defaults.model
  run.provider = opts.provider ?? agent.provider ?? defaults.provider

A new `core/models/defaults.ts` is the only place in the codebase that branches
on signed-in state. `getDefaultModelAndProvider()` returns name strings;
`resolveProviderConfig(name)` does the name → full LlmProvider lookup at
runtime. `createProvider` learns about `flavor: 'rowboat'` so the gateway is
just another flavor.

`provider` is stored as a name (e.g. `"rowboat"`, `"openai"`), not a full
LlmProvider object. API keys never get written into the JSONL log; rotating a
key in models.json applies to existing runs without re-creation. Cost: deleting
a provider from settings breaks runs that referenced it (clear error surfaced
via `resolveProviderConfig`).

## Runtime

`streamAgent` no longer resolves anything — it reads `state.runModel` /
`state.runProvider`, looks up the provider config, instantiates. Subflows
inherit the parent run's pair, so KG / inline-task subagents run on whatever
the main run resolved to at creation. The `knowledgeGraphAgents` array,
`isKgAgent`, and the per-agent default constants are gone.

KG / inline-task / pre-built agents declare their preferred model in YAML
frontmatter (claude-haiku-4.5 / claude-sonnet-4.6) — used at resolution time
when those agents are themselves the top-level agent of a run (background
triggers, scheduled tasks, etc.).

## Standalone callers

Non-run LLM call sites (summarize_meeting, track/routing, builtin-tools
parseFile) and `agent-schedule/runner` were branching on signed-in
independently. They all route through `getDefaultModelAndProvider` +
`resolveProviderConfig` + `createProvider` now; `agent-schedule/runner`
switched from raw `runsRepo.create` to `runsCore.createRun` so resolution
applies to scheduled-agent runs too.

## UI

`chat-input-with-mentions` stops calling `models:saveConfig`. The dropdown
notifies the parent via `onSelectedModelChange` ({provider, model} as names);
App.tsx stashes selection per-tab and passes it to the next `runs:create`.
When a run already exists, the input fetches it and renders a static label —
model can't change mid-run.

## Legacy runs

A lenient zod schema in `repo.ts` (`StartEvent.extend(...optional)` plus
`RunEvent.or(LegacyStartEvent)`) parses pre-existing runs. `repo.fetch` fills
missing model/provider from current defaults and returns the strict canonical
`Run` type. No file-rewriting migration; no impact on the canonical schema in
`@x/shared`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 12:26:01 +05:30
Ramnique Singh
51f2ad6e8a
Merge pull request #517 from rowboatlabs/dev
Dev
2026-04-21 14:39:46 +05:30
Ramnique Singh
15567cd1dd let tool failures be observed by the model instead of killing the run
streamAgent executed tools with no try/catch around the call. A throw
from execTool or from a subflow agent streamed up through streamAgent,
out of trigger's inner catch (which rethrows non-abort errors), and
into the new top-level catch that the previous commit added. That
surfaces the failure — but it ends the run. One misbehaving tool took
down the whole conversation.

Wrap the tool-execution block in a try/catch. On abort, rethrow so the
existing AbortError path still fires. On any other error, convert the
exception into a tool-result payload ({ success: false, error, toolName })
and keep going. The model then sees a tool-result message saying the
tool failed with a specific message and can apologize, retry with
different arguments, pick a different tool, or explain to the user —
the normal recovery moves it already knows how to make.

No change to happy-path tool execution, no change to abort handling,
no change to subflow agent semantics (subflows that themselves error
are treated identically to regular tool errors at the call site).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:38:19 +05:30
Ramnique Singh
c81d3cb27b surface silent runtime failures as error events
AgentRuntime.trigger() wrapped its body in try/finally with no outer
catch. An inner catch around the streamAgent for-await only handled
AbortError and rethrew everything else. Call sites fire-and-forget
trigger (runs.ts:26,60,72), so any thrown error became an unhandled
promise rejection. The finally still ran and published
run-processing-end, but nothing told the renderer why — the chat
showed the spinner, then an empty assistant bubble.

Provider misconfig, invalid API keys, unknown model ids, streamText
setup throws, runsRepo.fetch or loadAgent failing, and provider
auth/rate-limit rejections on the first chunk all hit this path on a
first message. All invisible.

Add a top-level catch that formats the error to a string and emits a
{type: "error"} RunEvent via the existing runsRepo/bus path. The
renderer already renders those as a chat bubble plus toast
(App.tsx:2069) — no UI work needed.

No changes to the abort path: user-initiated stops still flow through
the existing inner catch and the signal.aborted branch that emits
run-stopped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:36:00 +05:30
Ramnique Singh
32b6b2f1c0 Merge branch 'main' into dev 2026-04-21 13:36:58 +05:30
tusharmagar
0f051ea467 fix: duplicate navigation button 2026-04-21 13:02:44 +05:30
Ramnique Singh
7ad1a91ea8
Merge pull request #515 from rowboatlabs/dev
Dev changes
2026-04-21 11:12:40 +05:30
Ramnique Singh
ae296c7723 serialize knowledge file writes behind a per-path mutex
Concurrent track runs on the same note were corrupting the file. In a
fresh workspace, four tracks fired on cron at 05:09:17Z (all failed on
AI_LoadAPIKeyError, but each still wrote lastRunAt/lastRunId before the
agent ran) and three more fired at 05:09:32Z. The resulting Today.md
ended with stray fragments "\n>\nes-->\n-->" — tail pieces of
<!--/track-target:priorities--> that a mis-aimed splice had truncated —
and the priorities YAML lost its lastRunId entirely.

Two compounding issues in knowledge/track/fileops.ts:

1. updateTrackBlock read the file twice: once via fetch() to resolve
   fenceStart/fenceEnd, and again via fs.readFile to get the bytes to
   splice. If another writer landed between the reads, the line indices
   from read #1 pointed into unrelated content in read #2, so the
   splice replaced the wrong range and left tag fragments behind.

2. None of the mutators (updateContent, updateTrackBlock,
   replaceTrackBlockYaml, deleteTrackBlock) held any lock, so
   concurrent read-modify-writes clobbered each other's updates. The
   missing lastRunId was exactly that: set by one run, overwritten by
   another run's stale snapshot.

The fix: introduce withFileLock(absPath, fn) in knowledge/file-lock.ts,
a per-path Promise-chain mutex modeled on the commitLock pattern in
knowledge/version_history.ts. Callers append onto that file's chain
and await — wait-queue semantics, FIFO, no timeout. The map self-cleans
when a file's chain goes idle so it stays bounded across a long-running
process.

Wrap all four fileops mutators in it, and also wrap workspace.writeFile
(which can touch the same files from the agent's tool surface and
previously raced with fileops). Both callers key on the resolved
absolute path so they share the same lock for the same file.

Reads (fetchAll, fetch, fetchYaml) stay lock-free — fs.writeFile on
files this size is atomic enough that readers see either pre- or
post-state, never corruption, and stale reads are not a correctness
issue for the callers that use them (scheduler, event dispatcher).

The debounced version-history commit in workspace.writeFile stays
outside the lock; it's deferred work that shouldn't hold up the write.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:11:33 +05:30
Ramnique Singh
fbbaeea1df refactor ensure-daily-note 2026-04-21 11:06:09 +05:30
Ramnique Singh
a86f555cbb refresh rowboat access token on every gateway request
Wire a custom fetch into the OpenRouter gateway provider so each outbound
request resolves a fresh access token, instead of baking one token into
the provider at turn start. Add a 60s expiry margin and serialize
concurrent refreshes behind a single in-flight promise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 10:13:40 +05:30
Ramnique Singh
a80ef4d320 Revert "add suggested topics using track blocks"
This reverts commit 93054066fa.
2026-04-20 22:21:58 +05:30
Ramnique Singh
dc3e25c98b add today.md using track blocks 2026-04-20 17:20:30 +05:30
Ramnique Singh
93054066fa add suggested topics using track blocks 2026-04-20 17:20:21 +05:30
Ramnique Singh
4c46bf4c25 serialise prompt blocks to markdown 2026-04-20 17:19:36 +05:30
Ramnique Singh
e8a7cd59c1 fix \n repitition in markdown editor 2026-04-20 17:19:21 +05:30
Ramnique Singh
1306b7f442 improve prompting around output blocks 2026-04-20 16:22:53 +05:30
Ramnique Singh
56edc5a730 clean up invisible chars in yaml parse 2026-04-20 16:22:31 +05:30
Ramnique Singh
5d65616cfb add prompt block 2026-04-20 14:42:13 +05:30
Ramnique Singh
9f776ce526 improve track run prompts 2026-04-20 14:30:50 +05:30
Ramnique Singh
8e0a3e2991 render tables in markdown 2026-04-20 10:43:27 +05:30
Ramnique Singh
0d71ad33f5 improve track skill re: yaml strings 2026-04-20 10:27:13 +05:30
Arjun
5aedec2db9 inline-expand knowledge folders via hover chevron 2026-04-18 12:22:54 +05:30
arkml
acc655172d
Iframe (#502)
Added iframe block
2026-04-18 12:10:40 +05:30
Arjun
1f58c1f6cb fix build issue 2026-04-18 00:22:03 +05:30
Arjun
eaab438666 feat(suggested-topics): populate and integrate suggested topics 2026-04-17 23:09:54 +05:30
tusharmagar
e9cdd3f6eb feat(ui): add Suggested Topics feature 2026-04-17 23:09:54 +05:30
tusharmagar
50df9ed178 feat(composio): hide composio from copilot whilst the API key is not set in isSignedIn is false
- Added a function to invalidate the Copilot instructions cache when setting the API key.
- Updated the Composio tools prompt to return an empty string if Composio is not configured, simplifying the user experience.
- Refactored the Copilot instructions to conditionally include Composio-related guidance based on configuration status, improving clarity on third-party service interactions.
- Introduced a new function to build a skill catalog string, allowing for optional exclusion of specific skills, enhancing the skill management capabilities.
2026-04-16 17:12:43 +05:30