Commit graph

1550 commits

Author SHA1 Message Date
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
Arjun
933df9c4a8 assistant knows middle pane 2026-04-16 17:09:11 +05:30
Arjun
ebc56b5312 removed unused import 2026-04-16 17:09:11 +05:30
Arjun
e71107320c improve instructions for assistant creating notes 2026-04-16 17:09:11 +05:30
Arjun
a240ff777f clipboard copy in assistant works as expected 2026-04-16 17:09:11 +05:30
Arjun
efe2a93d8a move top icons to sidebar buttons 2026-04-16 17:09:11 +05:30
arkml
7dbfcb72f4
Browser2 (#495)
Add tabbed embedded browser and assistant browser control
2026-04-15 13:21:09 +05:30
Ramnique Singh
e2c13f0f6f
Add tracks — auto-updating note blocks with scheduled and event-driven triggers
Track blocks are YAML-fenced sections embedded in markdown notes whose output
is rewritten by a background agent. Three trigger types: manual (Run button or
Copilot), scheduled (cron / window / once with a 2 min grace window), and
event-driven (Gmail/Calendar sync events routed via an LLM classifier with a
second-pass agent decision). Output lives between <!--track-target:ID-->
comment markers that render as editable content in the Tiptap editor so users
can read and extend AI-generated content inline.

Core:
- Schedule and event pipelines run as independent polling loops (15s / 5s),
  both calling the same triggerTrackUpdate orchestrator. Events are FIFO via
  monotonic IDs; a per-track Set guards against duplicate runs.
- Track-run agent builds three message variants (manual/timed/event) — the
  event variant includes a Pass 2 directive to skip updates on false positives
  flagged by the liberal Pass 1 router.
- IPC surface: track:run/get/update/replaceYaml/delete plus tracks:events
  forward of the pub-sub bus to the renderer.
- Gmail emits per-thread events; Calendar bundles a digest per sync.

Copilot:
- New `tracks` skill (auto-generated canonical schema from Zod via
  z.toJSONSchema) teaches block creation, editing, and proactive suggestion.
- `run-track-block` tool with optional `context` parameter for backfills
  (e.g. seeding a new email-tracking block from existing synced emails).

Renderer:
- Tiptap chip (display-only) opens a rich modal with tabs, toggle, schedule
  details, raw YAML editor, and confirm-to-delete. All mutations go through
  IPC so the backend stays the single writer.
- Target regions use two atom marker nodes (open/close) around real editable
  content — custom blocks render natively, users can add their own notes.
- "Edit with Copilot" seeds a chat session with the note attached.

Docs: apps/x/TRACKS.md covers product flows, technical pipeline, and a
catalog of every LLM prompt involved with file+line pointers.
2026-04-14 13:51:45 +05:30
Ramnique Singh
ab0147d475 refactor agent yaml frontmatter parsing 2026-04-14 10:14:00 +05:30
Ramnique Singh
b462643e6d refactor waitForRunCompletion, extractAgentResponse 2026-04-14 10:14:00 +05:30
arkml
2133d7226f
Merge pull request #490 from rowboatlabs/dev
Dev
2026-04-13 22:21:12 +05:30
Arjun
490b14ad58 switch to claude as default 2026-04-13 21:28:01 +05:30
Tushar
2653f6170d
feat(oauth): enhance Rowboat sign-in process to prevent duplicate users (#489)
* feat(oauth): enhance Rowboat sign-in process to prevent duplicate users

Added billing information checks during the Rowboat OAuth connection and onboarding process to ensure user and Stripe customer existence before proceeding. This change mitigates the risk of creating duplicate users due to parallel API calls. Updated error handling for better debugging in case of failures.

* refactor(onboarding): remove billing info check during Rowboat OAuth connection

Eliminated the billing information check that was previously in place to prevent duplicate Stripe customers during the onboarding process. This change simplifies the onboarding flow while maintaining the necessary checks for composio flags after account connection.
2026-04-13 18:15:04 +05:30
Ramnique Singh
b3066a0b7a add cmd+k palette with chat mode that captures editor cursor context
Cmd+K (Ctrl+K on Win/Linux) now opens a unified palette with two
modes: Chat (default) and Search (existing behavior). Tab cycles
between them. In Chat mode, if the user triggered the shortcut from
the markdown editor, the palette auto-attaches a removable chip
showing the note path and precise cursor line. Enter sends the
prompt to the right-sidebar copilot — opening the sidebar if closed
and starting a fresh chat tab — with the chip carried as a
FileMention whose lineNumber is forwarded to the agent as
"... at <path> (line N)" so the agent can use workspace-readFile
with offset to fetch the right slice on demand.

Line numbers are computed against the same getMarkdownWithBlankLines
serializer used to write notes to disk, so the reference is
byte-identical to what the agent reads back.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:00:37 +05:30
Ramnique Singh
4a2dfbf16f pass @-mention notes by reference, not by inlined content
Mentions now route through the structured-attachment path, sending
only path/filename/mimeType. The agent fetches content on demand via
workspace-readFile (line-prefixed, paginated). Avoids freezing a stale
snapshot of the note into the conversation and saves tokens on long
notes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:59:36 +05:30
Ramnique Singh
f4dc5e7db4 prefix line numbers and add offset/limit to workspace-readFile
Returns utf8 reads as `<path>`/`<type>`/`<content>` blocks with each
line prefixed by its 1-indexed line number, plus offset/limit paging
and an end-of-file/truncation footer. Helps the agent reference
specific lines when forming precise edits to knowledge markdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:30:36 +05:30
tusharmagar
1da3223f7d update .gitignore 2026-04-13 09:45:43 +05:30
tusharmagar
05166e791f clean up .claude 2026-04-13 09:45:28 +05:30