2025-12-29 15:30:57 +05:30
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { RelPath, Encoding, Stat, DirEntry, ReaddirOptions, ReadFileResult, WorkspaceChangeEvent, WriteFileOptions, WriteFileResult, RemoveOptions } from './workspace.js';
|
|
|
|
|
import { ListToolsResponse } from './mcp.js';
|
2026-01-16 09:48:11 +05:30
|
|
|
import { AskHumanResponsePayload, CreateRunOptions, Run, ListRunsResponse, ToolPermissionAuthorizePayload } from './runs.js';
|
2026-06-19 01:12:53 +05:30
|
|
|
import { LlmModelConfig, LlmProvider } from './models.js';
|
2026-02-04 23:21:13 +05:30
|
|
|
import { AgentScheduleConfig, AgentScheduleEntry } from './agent-schedule.js';
|
|
|
|
|
import { AgentScheduleState } from './agent-schedule-state.js';
|
2026-02-05 16:05:10 +05:30
|
|
|
import { ServiceEvent } from './service-events.js';
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
import { LiveNoteAgentEvent, LiveNoteSchema } from './live-note.js';
|
feat: background tasks
Adds Background Tasks — recurring background agents the user can set up to
either keep a digest current (daily email summary, top HN stories, weather
brief) or perform a recurring action (draft a reply, post to Slack, call an
API). Each task is a persistent set of instructions plus optional triggers
(schedule, time-of-day window, or matching incoming Gmail / calendar event).
The agent reads the verbs in the instructions on every run and picks the
right mode automatically.
User-facing surfaces:
- New "Background tasks" entry in the sidebar, with a table listing every
task, its schedule, last run, and an active toggle.
- A detail page per task with a max-width reader showing the task's
current output and a control sidebar for editing instructions, triggers,
and reviewing run history.
- "New task" can open in a free-form box where the user describes what they
want and Copilot sets it up end-to-end, or in a structured form for
manual setup.
- "Edit with Copilot" hand-off from the detail view, pre-seeded with the
task's context.
Under the hood:
- The event pipeline that previously powered live-notes is now a generic
consumer registry. Live-notes and background tasks both subscribe;
incoming events are routed to candidates from both concurrently.
- Schedule helpers and the agent-message trigger block are factored out of
live-notes into shared modules. Both features use the same building
blocks now.
- Copilot's proactive routing is reframed: anything recurring (cadence
words, watch / monitor verbs, action verbs, event-conditional asks) now
flows to background tasks. Live-notes load only on explicit mention.
- A small reliability fix for the run-creation fallback chain: an
empty-string model/provider passed by an LLM tool call now correctly
falls through to the default instead of being persisted as a real value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:43:25 +05:30
|
|
|
import {
|
|
|
|
|
BackgroundTaskAgentEvent,
|
|
|
|
|
BackgroundTaskSchema,
|
|
|
|
|
BackgroundTaskSummarySchema,
|
|
|
|
|
TriggersSchema,
|
|
|
|
|
} from './background-task.js';
|
feat(x): sessions IPC + renderer data layer with testable stores (stage 5a)
The wire and state layer for the UI cutover; App.tsx integration follows
separately.
- shared/ipc.ts: eleven sessions:* invoke channels + the sessions:events
push feed (SessionBusEvent via z.custom, like runs:events). The generic
preload bridge needs no changes.
- main: sessions:* handlers as thin pass-throughs to the DI'd sessions
service; startSessionsWatcher forwards the session bus to all windows;
startup awaits the session-index scan before the renderer can list.
- renderer architecture per review guidance — all logic in framework-
agnostic, dependency-injected modules; hooks are thin
useSyncExternalStore subscriptions; components will consume pre-digested
view models:
- client.ts: narrow SessionsClient over window.ipc (fakeable).
- feed.ts: one shared sessions:events consumer with fan-out (factory
for tests).
- turn-view.ts: pure derivations — live overlay (deltas accumulate,
canonical events clear), TurnState -> ConversationItem[], and the
session chat state (permission/ask-human maps re-manufactured in the
runs-era shapes so existing components render unchanged;
isProcessing/isThinking contract preserved).
- store.ts: SessionChatStore (seed via getSession/getTurn, shared
reduceTurn over live events, prior-turn freezing, unknown-turn
reconciliation, stale-load guard, action routing) + SessionListStore.
- hooks/useSessionChat + useSessions: thin wrappers, deps injectable.
- renderer test infra added from scratch (vitest + jsdom +
testing-library); 29 tests across stores, pure views, feed, and hooks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:10:17 +05:30
|
|
|
import { UserMessage, UserMessageContent } from './message.js';
|
|
|
|
|
import { RequestedAgent, type TurnEvent } from './turns.js';
|
|
|
|
|
import type { SessionBusEvent, SessionIndexEntry, SessionState } from './sessions.js';
|
2026-03-24 11:50:22 +05:30
|
|
|
import { RowboatApiConfig } from './rowboat-account.js';
|
Feature/composio tools library (#461)
* first version of composio
* Enhance error handling in Composio tool execution. Added try-catch block to log errors and return a structured error response when executing tools fails.
* Add tool search functionality to settings dialog
Implemented a debounced search feature for tools within the toolkit, allowing users to filter tools based on a search query. Added state management for search results and loading status. Updated the UI to accommodate the search input and results display. Additionally, modified the API call to use 'query' instead of 'search' for consistency.
* Enhance Composio OAuth flow management and improve tool handling
- Updated the activeFlows management to prevent concurrent OAuth flows for the same toolkit by using toolkitSlug as the key.
- Implemented cleanup logic for existing flows, ensuring proper resource management by aborting and closing servers as needed.
- Introduced a timeout mechanism for abandoned flows, enhancing reliability.
- Refactored the Composio tools repository to use an in-memory cache for improved performance and added methods for persisting changes to disk.
- Updated the detailed tools listing to use a consistent API call structure and improved input parameter handling.
- Made connectionData in the response optional for better flexibility in handling connected accounts.
* Improve error handling in Composio API calls
- Enhanced error reporting by extracting human-readable messages from the JSON response body when the API call fails.
- Added logic to parse the response and include specific error details in the thrown error message, improving debugging and user feedback.
* Refactor Composio integration handling and improve UI components
- Updated composio-handler.ts to invalidate the copilot instructions cache upon connection and disconnection.
- Removed unused functions related to tool management in composio-handler.ts.
- Enhanced IPC handlers in ipc.ts to streamline Composio connection processes.
- Introduced ComposioConnectCard in the renderer to display connection status and handle events.
- Refactored tool rendering in App.tsx and chat-sidebar.tsx to utilize new tabbed content for parameters and results.
- Improved Composio tools prompt generation in instructions.ts to clarify integration usage and discovery flow.
- Cleaned up unused code and improved overall structure for better maintainability.
* Refactor listToolkits function to remove cursor parameter and implement pagination
- Updated listToolkits in composio-handler.ts to paginate through API results, collecting all curated toolkits.
- Adjusted IPC handler in ipc.ts to call the modified listToolkits without cursor argument.
- Made properties in ToolkitInfo optional in settings-dialog.tsx for improved flexibility.
- Removed the unused enabled-tools-repo.ts file to clean up the codebase.
* Refactor Composio toolkit management for improved structure and maintainability
- Consolidated toolkit definitions and display names into a single source of truth in shared/composio.ts.
- Updated core composio/curated-toolkits.ts to re-export types and constants for backward compatibility.
- Enhanced the organization of toolkit data, ensuring clarity and ease of access for future development.
* Refactor Composio integration and improve component structure
- Updated imports in composio-handler.ts and various components to utilize shared/composio.js for consistency.
- Simplified ComposioConnectCard by removing unnecessary state management and improving event handling.
- Enhanced chat-conversation.ts to directly reference COMPOSIO_DISPLAY_NAMES from shared/composio.js.
- Cleaned up unused functions and types in client.ts and types.ts for better maintainability.
- Removed deprecated curated-toolkits.ts file to streamline the codebase.
* Refactor Composio connection handling and improve tool display logic
- Removed the connection bridge for Composio toolkit OAuth, simplifying the connection process.
- Updated ComposioConnectCard to display a more user-friendly connection message.
- Introduced a new utility function, getToolDisplayName, to provide human-friendly names for builtin tools.
- Refactored App and ChatSidebar components to utilize the new getToolDisplayName function for improved clarity in tool titles.
- Cleaned up imports and removed unused code for better maintainability.
* remove from diff
* Address PR review: consolidate types, refactor CopilotAgent, sort toolkits
- Move ZSearchResultTool and ZNormalizedToolResult into composio/types.ts
- Convert CopilotAgent from static const to async buildCopilotAgent()
- Simplify loadAgent to delegate to buildCopilotAgent()
- Sort CURATED_TOOLKITS alphabetically by slug
- Remove inline type annotations in composio-handler, use inferred types
- Bump search limit from 15→50 for unscoped queries
- Add docstrings explaining inferToolkitSlug fallback behavior
- Add IPC schema reference comment for composio channels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Enhance Composio connection handling and improve rendering logic
- Added a 'hidden' property to ComposioConnectCardData to prevent rendering of duplicate connection cards.
- Updated App and ChatSidebar components to skip rendering if the card is marked as hidden.
- Refactored ComposioConnectCard to utilize a ref for callback firing, ensuring onConnected is only called once.
- Improved instructions for Composio integration to clarify usage and loading of the composio-integration skill.
This update streamlines the user experience by avoiding duplicate connection prompts and enhances the overall clarity of integration instructions.
* Address PR round 2: use query param, remove inferToolkitSlug, consolidate types
- Rename deprecated `search` param to `query` per Composio docs
- Remove inferToolkitSlug fallback; make toolkit required in ZSearchResultTool
- Replace inline Awaited<ReturnType<...>> with concrete Toolkit type in handler
- Move ZToolkitMeta/ZToolkitItem/ZListToolkitsResponse to shared/composio.ts
- Reference shared schemas in ipc.ts and core/types.ts (single source of truth)
- Remove unused ZTool import from client.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Toolkit type inference to composio/types.ts
- Introduced a new type `Toolkit` inferred from `ZToolkit` to enhance type safety and clarity in type definitions.
- This addition supports better integration and usage of the toolkit within the Composio framework.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:30:46 +05:30
|
|
|
import { ZListToolkitsResponse } from './composio.js';
|
2026-07-02 19:11:53 +05:30
|
|
|
import { BrowserStateSchema, HttpAuthRequestSchema } from './browser-control.js';
|
2026-05-18 11:12:10 +05:30
|
|
|
import { BillingInfoSchema } from './billing.js';
|
2026-05-18 21:46:26 +05:30
|
|
|
import { EmailBlockSchema, GmailThreadSchema } from './blocks.js';
|
2026-06-12 23:45:38 +05:30
|
|
|
import { PermissionDecision, ApprovalPolicy, CodingAgent } from './code-mode.js';
|
2026-06-11 19:43:26 +05:30
|
|
|
import { NotificationSettingsSchema } from './notification-settings.js';
|
2026-06-21 08:38:49 -07:00
|
|
|
import { CodeProject, CodeSession, CodeSessionMode, CodeSessionStatus, GitRepoInfo, GitStatusFile, CodeAgentModelOptions } from './code-sessions.js';
|
2026-07-03 19:25:06 +05:30
|
|
|
import { ChannelsConfig, ChannelsStatus } from './channels.js';
|
2025-12-29 15:30:57 +05:30
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Runtime Validation Schemas (Single Source of Truth)
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
feat: ship Slack as a knowledge source, hardened for production (#596)
* index slack and add to home page
* filter only useful slack messages in homr
* feat: bundle agent-slack CLI and route all calls through shared executor
Pins agent-slack@0.9.3, bundles it next to main.cjs (replaces the startup npm install -g), adds a structured-result executor with bundled/global/PATH resolution, a slack:cliStatus IPC probe, and a PATH shim so the Copilot skill keeps working.
* feat: surface Slack failures and add cross-OS auth fallbacks
Classify agent-slack errors (not_authed/rate_limited/network/bad_channel), persist per-source sync status with rate-limit backoff, and expose it via slack:knowledgeStatus. Fix the Settings Enable bounce-back with actionable copy, a browser-paste (parse-curl) fallback, and a Windows quit-Slack-and-import button; add home-feed empty/error states.
* feat: rank Slack home feed deterministically by recency
Drop the per-load LLM ranker (cost/latency/model dependency) in favor of a stronger deterministic filter + recency ordering. The filter now removes system messages, emoji/reaction-only posts, bare greetings/acks, and empty bodies, with a durable-signal escape hatch. Expand tests to one describe per noise class plus ordering/cap/volume coverage.
* fix: hide Slack knowledge Save button once saved
Only show the Save button when the channel list or enabled toggle differs from the last-persisted config, so it disappears after a successful save and reappears when a new channel is entered.
---------
Co-authored-by: Gagancreates <gaganp000999@gmail.com>
2026-06-18 01:22:27 +05:30
|
|
|
const KnowledgeSourceScopeSchema = z.object({
|
|
|
|
|
type: z.string(),
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string().optional(),
|
|
|
|
|
workspaceUrl: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mirrors AgentSlackErrorKind in @x/core/slack/agent-slack-exec. Kept as a
|
|
|
|
|
// standalone enum so the renderer can branch on failure cause without
|
|
|
|
|
// importing core.
|
|
|
|
|
const SlackErrorKindSchema = z.enum([
|
|
|
|
|
'not_installed', 'timeout', 'parse_error',
|
|
|
|
|
'not_authed', 'rate_limited', 'network', 'bad_channel', 'unknown',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const KnowledgeSourceConfigSchema = z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
provider: z.enum(['gmail', 'meeting', 'voice_memo', 'slack', 'github', 'linear']),
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
artifactDir: z.string(),
|
|
|
|
|
syncMode: z.enum(['file', 'poll', 'event', 'manual']).default('file'),
|
|
|
|
|
intervalMs: z.number().int().positive().optional(),
|
|
|
|
|
scopes: z.array(KnowledgeSourceScopeSchema).default([]),
|
|
|
|
|
instructions: z.string().optional(),
|
|
|
|
|
filters: z.record(z.string(), z.unknown()).optional(),
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-29 15:30:57 +05:30
|
|
|
const ipcSchemas = {
|
|
|
|
|
'app:getVersions': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
chrome: z.string(),
|
|
|
|
|
node: z.string(),
|
|
|
|
|
electron: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-04-28 19:53:40 +05:30
|
|
|
'analytics:bootstrap': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
installationId: z.string(),
|
|
|
|
|
apiUrl: z.string(),
|
2026-05-29 17:02:01 +05:30
|
|
|
appVersion: z.string(),
|
2026-04-28 19:53:40 +05:30
|
|
|
}),
|
|
|
|
|
},
|
2025-12-29 15:30:57 +05:30
|
|
|
'workspace:getRoot': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
root: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'workspace:exists': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
exists: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'workspace:stat': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
}),
|
|
|
|
|
res: Stat,
|
|
|
|
|
},
|
|
|
|
|
'workspace:readdir': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: z.string(), // Empty string allowed for root directory
|
|
|
|
|
opts: ReaddirOptions.optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.array(DirEntry),
|
|
|
|
|
},
|
|
|
|
|
'workspace:readFile': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
encoding: Encoding.optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: ReadFileResult,
|
|
|
|
|
},
|
|
|
|
|
'workspace:writeFile': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
data: z.string(),
|
|
|
|
|
opts: WriteFileOptions.optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: WriteFileResult,
|
|
|
|
|
},
|
|
|
|
|
'workspace:mkdir': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
recursive: z.boolean().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'workspace:rename': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
from: RelPath,
|
|
|
|
|
to: RelPath,
|
|
|
|
|
overwrite: z.boolean().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'workspace:copy': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
from: RelPath,
|
|
|
|
|
to: RelPath,
|
|
|
|
|
overwrite: z.boolean().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'workspace:remove': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
opts: RemoveOptions.optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'workspace:didChange': {
|
|
|
|
|
req: WorkspaceChangeEvent,
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-05-18 21:46:26 +05:30
|
|
|
'gmail:getImportant': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
cursor: z.string().optional(),
|
|
|
|
|
limit: z.number().int().min(1).max(100).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
threads: z.array(GmailThreadSchema),
|
|
|
|
|
nextCursor: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'gmail:getEverythingElse': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
cursor: z.string().optional(),
|
|
|
|
|
limit: z.number().int().min(1).max(100).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
threads: z.array(GmailThreadSchema),
|
|
|
|
|
nextCursor: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'gmail:triggerSync': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
2026-05-25 09:47:08 +05:30
|
|
|
'gmail:sendReply': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
threadId: z.string().min(1).optional(),
|
|
|
|
|
to: z.string().min(1),
|
|
|
|
|
cc: z.string().optional(),
|
|
|
|
|
bcc: z.string().optional(),
|
|
|
|
|
subject: z.string(),
|
|
|
|
|
bodyHtml: z.string(),
|
|
|
|
|
bodyText: z.string(),
|
|
|
|
|
inReplyTo: z.string().optional(),
|
|
|
|
|
references: z.string().optional(),
|
feat: compose new email with contact autocomplete and AI drafting (#616)
* feat: compose new email with contact autocomplete and AI drafting
- Add a compose-new-email box to the email view with a recipient field
that autocompletes from Gmail contacts (keyboard navigation, match
highlighting, avatar chips)
- Build contact indices in core: gmail_sent_contacts syncs the SENT
label via the Gmail API for full coverage of people you've emailed,
with gmail_contacts as an instant local-snapshot fallback; both are
pre-warmed at startup so the first keystroke is instant
- Add generateOneShot() one-shot text generation for the composer's
"write with AI", resolving to the active default model/provider
- Add getAccountName() (parsed from a recent SENT message's From
header, no extra OAuth scope) so AI drafts sign off with the real name
- New IPC channels: gmail:searchContacts, gmail:getAccountName,
llm:generate, llm:getDefaultModel
* feat: attachments, undo/redo, and unified compose for new emails
- Merge ComposeNewBox into ComposeBox via a new 'new' mode, memoizing the
component so inbox sync ticks no longer jank the open composer.
- Add file attachments: stage files in the renderer (25MB cap), pass raw
base64 over IPC, and build a multipart/mixed MIME on send.
- Add undo/redo buttons to the compose toolbar.
- Single Write/Edit AI bar that generates a draft, then iteratively rewrites
it; drop the hardcoded Gemini Flash model and use the default Copilot model.
- Suppress inbox reloads while the compose-new modal is open.
- Log llm:generate provider/model/output for debugging.
* fix: remove redundant Subject placeholder in composer
The subject row already has a 'Subject' gutter label, so the input's
placeholder repeated the word — an empty field read 'Subject' twice.
Drop the placeholder to match the To/Cc/Bcc fields.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:14:00 +05:30
|
|
|
attachments: z
|
|
|
|
|
.array(
|
|
|
|
|
z.object({
|
|
|
|
|
filename: z.string(),
|
|
|
|
|
mimeType: z.string(),
|
|
|
|
|
contentBase64: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.optional(),
|
2026-05-25 09:47:08 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
messageId: z.string().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat(email): drafts, search, bulk read/unread, configurable backfill
Gmail client enhancements in apps/x, bundling four independent features:
- Drafts: save/autosave, update, delete, and list Gmail drafts. The
composer autosaves to a real Gmail draft (debounced ~1.5s) while
typing, reuses the thread's existing draft so edits update in place,
flushes a final save on close, and deletes the draft on discard. Adds
isDraft/draftId to the shared thread types. New core helpers:
saveThreadDraft, deleteThreadDraft, listDraftThreads,
buildDraftSnapshot, buildRawMimeMessage.
- Search: searchThreads(query, {limit}) backed by an on-disk snapshot
cache (read/writeSearchSnapshot). Extracts parseThreadSnapshot as the
shared parse core reused by both the cache-building sync and search.
- Read state: markThreadRead now takes a `read` flag so it toggles
read/unread; new markSectionRead marks a whole section
(important/other) read/unread and returns the affected count.
- Backfill: the onboarding/recovery sync is now bounded by a
configurable thread COUNT instead of a fixed 7-day window. New
gmail_sync_config.ts (getMaxEmails/setMaxEmails) backed by
~/.rowboat/config/gmail_sync.json, default 500, clamped to 1-5000;
seeded on first run.
New IPC channels: gmail:saveDraft, gmail:deleteDraft, gmail:getDrafts,
gmail:search, gmail:markSectionRead (plus a `read` field on
gmail:markThreadRead).
2026-07-01 16:18:52 +05:30
|
|
|
'gmail:saveDraft': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
// Existing Gmail draft to update; omitted on first save (creates a new one).
|
|
|
|
|
draftId: z.string().min(1).optional(),
|
|
|
|
|
threadId: z.string().min(1).optional(),
|
|
|
|
|
// Recipients may be blank for a draft (unlike a send).
|
|
|
|
|
to: z.string().optional(),
|
|
|
|
|
cc: z.string().optional(),
|
|
|
|
|
bcc: z.string().optional(),
|
|
|
|
|
subject: z.string(),
|
|
|
|
|
bodyHtml: z.string(),
|
|
|
|
|
bodyText: z.string(),
|
|
|
|
|
inReplyTo: z.string().optional(),
|
|
|
|
|
references: z.string().optional(),
|
|
|
|
|
attachments: z
|
|
|
|
|
.array(
|
|
|
|
|
z.object({
|
|
|
|
|
filename: z.string(),
|
|
|
|
|
mimeType: z.string(),
|
|
|
|
|
contentBase64: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
draftId: z.string().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'gmail:deleteDraft': {
|
|
|
|
|
req: z.object({ draftId: z.string().min(1) }),
|
|
|
|
|
res: z.object({ ok: z.boolean(), error: z.string().optional() }),
|
|
|
|
|
},
|
|
|
|
|
'gmail:getDrafts': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
threads: z.array(GmailThreadSchema),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'gmail:search': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
query: z.string(),
|
|
|
|
|
limit: z.number().int().positive().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
threads: z.array(GmailThreadSchema),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-05-25 09:47:08 +05:30
|
|
|
'gmail:getConnectionStatus': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
connected: z.boolean(),
|
|
|
|
|
hasRequiredScope: z.boolean(),
|
|
|
|
|
missingScopes: z.array(z.string()),
|
|
|
|
|
email: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'gmail:getAccountEmail': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
email: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: compose new email with contact autocomplete and AI drafting (#616)
* feat: compose new email with contact autocomplete and AI drafting
- Add a compose-new-email box to the email view with a recipient field
that autocompletes from Gmail contacts (keyboard navigation, match
highlighting, avatar chips)
- Build contact indices in core: gmail_sent_contacts syncs the SENT
label via the Gmail API for full coverage of people you've emailed,
with gmail_contacts as an instant local-snapshot fallback; both are
pre-warmed at startup so the first keystroke is instant
- Add generateOneShot() one-shot text generation for the composer's
"write with AI", resolving to the active default model/provider
- Add getAccountName() (parsed from a recent SENT message's From
header, no extra OAuth scope) so AI drafts sign off with the real name
- New IPC channels: gmail:searchContacts, gmail:getAccountName,
llm:generate, llm:getDefaultModel
* feat: attachments, undo/redo, and unified compose for new emails
- Merge ComposeNewBox into ComposeBox via a new 'new' mode, memoizing the
component so inbox sync ticks no longer jank the open composer.
- Add file attachments: stage files in the renderer (25MB cap), pass raw
base64 over IPC, and build a multipart/mixed MIME on send.
- Add undo/redo buttons to the compose toolbar.
- Single Write/Edit AI bar that generates a draft, then iteratively rewrites
it; drop the hardcoded Gemini Flash model and use the default Copilot model.
- Suppress inbox reloads while the compose-new modal is open.
- Log llm:generate provider/model/output for debugging.
* fix: remove redundant Subject placeholder in composer
The subject row already has a 'Subject' gutter label, so the input's
placeholder repeated the word — an empty field read 'Subject' twice.
Drop the placeholder to match the To/Cc/Bcc fields.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:14:00 +05:30
|
|
|
'gmail:getAccountName': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
name: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-07-05 02:36:01 +05:30
|
|
|
// User explicitly flips a thread's importance verdict. Sticky on the thread
|
|
|
|
|
// (re-classification never overrides) and recorded as a correction the
|
|
|
|
|
// importance classifier learns from.
|
|
|
|
|
'gmail:setImportance': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
threadId: z.string().min(1),
|
|
|
|
|
importance: z.enum(['important', 'other']),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
previous: z.enum(['important', 'other']).optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-05-25 09:47:08 +05:30
|
|
|
'gmail:archiveThread': {
|
|
|
|
|
req: z.object({ threadId: z.string().min(1) }),
|
|
|
|
|
res: z.object({ ok: z.boolean(), error: z.string().optional() }),
|
|
|
|
|
},
|
|
|
|
|
'gmail:trashThread': {
|
|
|
|
|
req: z.object({ threadId: z.string().min(1) }),
|
|
|
|
|
res: z.object({ ok: z.boolean(), error: z.string().optional() }),
|
|
|
|
|
},
|
|
|
|
|
'gmail:markThreadRead': {
|
feat(email): drafts, search, bulk read/unread, configurable backfill
Gmail client enhancements in apps/x, bundling four independent features:
- Drafts: save/autosave, update, delete, and list Gmail drafts. The
composer autosaves to a real Gmail draft (debounced ~1.5s) while
typing, reuses the thread's existing draft so edits update in place,
flushes a final save on close, and deletes the draft on discard. Adds
isDraft/draftId to the shared thread types. New core helpers:
saveThreadDraft, deleteThreadDraft, listDraftThreads,
buildDraftSnapshot, buildRawMimeMessage.
- Search: searchThreads(query, {limit}) backed by an on-disk snapshot
cache (read/writeSearchSnapshot). Extracts parseThreadSnapshot as the
shared parse core reused by both the cache-building sync and search.
- Read state: markThreadRead now takes a `read` flag so it toggles
read/unread; new markSectionRead marks a whole section
(important/other) read/unread and returns the affected count.
- Backfill: the onboarding/recovery sync is now bounded by a
configurable thread COUNT instead of a fixed 7-day window. New
gmail_sync_config.ts (getMaxEmails/setMaxEmails) backed by
~/.rowboat/config/gmail_sync.json, default 500, clamped to 1-5000;
seeded on first run.
New IPC channels: gmail:saveDraft, gmail:deleteDraft, gmail:getDrafts,
gmail:search, gmail:markSectionRead (plus a `read` field on
gmail:markThreadRead).
2026-07-01 16:18:52 +05:30
|
|
|
req: z.object({ threadId: z.string().min(1), read: z.boolean().optional() }),
|
2026-05-25 09:47:08 +05:30
|
|
|
res: z.object({ ok: z.boolean(), error: z.string().optional() }),
|
|
|
|
|
},
|
2026-07-01 23:51:08 +05:30
|
|
|
'gmail:downloadAttachment': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
messageId: z.string().min(1),
|
|
|
|
|
savedPath: z.string().min(1),
|
|
|
|
|
attachmentId: z.string().optional(),
|
|
|
|
|
}),
|
2026-05-25 09:47:08 +05:30
|
|
|
res: z.object({ ok: z.boolean(), error: z.string().optional() }),
|
|
|
|
|
},
|
2026-05-18 21:46:26 +05:30
|
|
|
'gmail:saveMessageHeight': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
threadId: z.string().min(1),
|
|
|
|
|
messageId: z.string().min(1),
|
|
|
|
|
height: z.number().int().positive(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
2026-06-10 14:58:13 +05:30
|
|
|
'gmail:searchContacts': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
query: z.string(),
|
|
|
|
|
limit: z.number().int().positive().optional(),
|
|
|
|
|
excludeEmails: z.array(z.string()).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
contacts: z.array(z.object({
|
|
|
|
|
name: z.string(),
|
|
|
|
|
email: z.string(),
|
|
|
|
|
count: z.number(),
|
|
|
|
|
lastSeenMs: z.number(),
|
|
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2025-12-29 15:30:57 +05:30
|
|
|
'mcp:listTools': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
serverName: z.string(),
|
|
|
|
|
cursor: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: ListToolsResponse,
|
|
|
|
|
},
|
|
|
|
|
'mcp:executeTool': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
serverName: z.string(),
|
|
|
|
|
toolName: z.string(),
|
|
|
|
|
input: z.record(z.string(), z.unknown()),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
result: z.unknown(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'runs:create': {
|
|
|
|
|
req: CreateRunOptions,
|
|
|
|
|
res: Run,
|
|
|
|
|
},
|
|
|
|
|
'runs:createMessage': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string(),
|
2026-02-26 21:41:17 +05:30
|
|
|
message: UserMessageContent,
|
2026-03-13 10:26:08 +05:30
|
|
|
voiceInput: z.boolean().optional(),
|
|
|
|
|
voiceOutput: z.enum(['summary', 'full']).optional(),
|
2026-03-13 13:58:30 +05:30
|
|
|
searchEnabled: z.boolean().optional(),
|
Code Mode: in-chat toggle, settings tab, and permission/command UX (#572)
* feat: add in-chat code mode toggle with claude/codex swap
* feat: show agent and add swap-and-retry on acpx permission card
* style: reorder permission card buttons (approve, deny, swap)
* feat: add tooltips to composer plus and web search buttons
* feat: add code mode settings tab with agent install/auth checks
* feat: show sign-in command when agent installed but signed out
* style: refine code-mode permission and command block UX
- Render permission block before the command block
- Collapse permission details after a response; click header to expand
- Drop status icons/badge; use minimal green / bold red blocks
- Auto-collapse the running command block once it completes
* feat: rotating progress labels for code-mode commands; darker tool borders
- Code-mode (acpx) command block shows status-aware labels: rotating
'Working on the task…' phrases (5s each, holding on the last) while
running, then 'Completed the task' / "Couldn't complete the task"
- Darken outer border on all tool blocks in light and dark modes
* fix: detect Claude Code sign-in via macOS Keychain
On macOS, Claude Code stores OAuth credentials in the login Keychain
(service 'Claude Code-credentials'), not in ~/.claude/.credentials.json.
Read the Keychain as a fallback so signed-in Mac users are detected.
* feat: persistent per-chat sessions for code-mode coding agents
- Use a named acpx session (rowboat-<runId>) per chat so follow-up
coding requests resume the same agent and keep context
- Create the session once at chat start (sessions new --name), then
prompt with -s <name>; reuse on follow-ups (no re-create)
- Drop the redundant in-chat 'reply yes' confirmation (the executeCommand
permission card is the confirmation)
- Code-mode output uses plain-text paths (overrides global filepath rule)
- On not-installed/auth errors, point user to Settings -> Code Mode
* fix: code-mode session creation uses idempotent ensure, run sequentially
- Use 'sessions ensure --name' instead of 'sessions new' so reopening a
chat resumes the existing session instead of erroring on a name clash
- Create the session and run the prompt as separate sequential calls so
the permission/command blocks render one at a time (not all at once)
* fix: reliable Claude Code session resume on Windows (avoid claude.cmd EINVAL)
Resuming a code-mode chat after restarting the app spawns a fresh ACP
agent. On Windows + Node >=20.12 the bridge spawning claude.cmd throws
EINVAL, so the session queue owner fails to start. Rowboat injects
CLAUDE_CODE_EXECUTABLE=claude.exe to dodge this, but the override didn't
reliably reach the spawn. Windows-only; no-op on macOS/Linux.
- executeCommand now accepts an env override and the non-abortable
fallback path passes it through (was silently dropped)
- resolveClaudeExeOnWindows also scans known npm/pnpm/volta global bin
dirs, not just PATH (Electron's runtime PATH can omit them)
- add --timeout 600 to acpx prompt commands so a genuine stall fails
cleanly instead of hanging on 'Running' forever
2026-05-28 14:52:09 +05:30
|
|
|
codeMode: z.enum(['claude', 'codex']).optional(),
|
2026-06-12 23:45:38 +05:30
|
|
|
// Code-section sessions pin the coding agent's working directory and
|
|
|
|
|
// approval policy for the whole turn (see code_agent_run overrides).
|
|
|
|
|
codeCwd: z.string().optional(),
|
|
|
|
|
codePolicy: ApprovalPolicy.optional(),
|
2026-04-16 16:24:26 +05:30
|
|
|
middlePaneContext: z.discriminatedUnion('kind', [
|
|
|
|
|
z.object({
|
|
|
|
|
kind: z.literal('note'),
|
|
|
|
|
path: z.string(),
|
|
|
|
|
content: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
z.object({
|
|
|
|
|
kind: z.literal('browser'),
|
|
|
|
|
url: z.string(),
|
|
|
|
|
title: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
]).optional(),
|
2025-12-29 15:30:57 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
messageId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'runs:authorizePermission': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string(),
|
|
|
|
|
authorization: ToolPermissionAuthorizePayload,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'runs:provideHumanInput': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string(),
|
|
|
|
|
reply: AskHumanResponsePayload,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'runs:stop': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string(),
|
2026-01-30 06:53:50 +05:30
|
|
|
force: z.boolean().optional().default(false),
|
2025-12-29 15:30:57 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-01-16 09:48:11 +05:30
|
|
|
'runs:fetch': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: Run,
|
|
|
|
|
},
|
|
|
|
|
'runs:list': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
cursor: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: ListRunsResponse,
|
|
|
|
|
},
|
2026-06-29 12:27:43 +05:30
|
|
|
'runs:listByWorkDir': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
dir: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: ListRunsResponse,
|
|
|
|
|
},
|
2026-02-16 15:28:20 +05:30
|
|
|
'runs:delete': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ success: z.boolean() }),
|
|
|
|
|
},
|
2026-05-27 23:17:47 +05:30
|
|
|
'runs:downloadLog': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
runId: z.string().min(1),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2025-12-29 15:30:57 +05:30
|
|
|
'runs:events': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.null(),
|
2026-01-06 06:56:42 +05:30
|
|
|
},
|
feat(x): sessions IPC + renderer data layer with testable stores (stage 5a)
The wire and state layer for the UI cutover; App.tsx integration follows
separately.
- shared/ipc.ts: eleven sessions:* invoke channels + the sessions:events
push feed (SessionBusEvent via z.custom, like runs:events). The generic
preload bridge needs no changes.
- main: sessions:* handlers as thin pass-throughs to the DI'd sessions
service; startSessionsWatcher forwards the session bus to all windows;
startup awaits the session-index scan before the renderer can list.
- renderer architecture per review guidance — all logic in framework-
agnostic, dependency-injected modules; hooks are thin
useSyncExternalStore subscriptions; components will consume pre-digested
view models:
- client.ts: narrow SessionsClient over window.ipc (fakeable).
- feed.ts: one shared sessions:events consumer with fan-out (factory
for tests).
- turn-view.ts: pure derivations — live overlay (deltas accumulate,
canonical events clear), TurnState -> ConversationItem[], and the
session chat state (permission/ask-human maps re-manufactured in the
runs-era shapes so existing components render unchanged;
isProcessing/isThinking contract preserved).
- store.ts: SessionChatStore (seed via getSession/getTurn, shared
reduceTurn over live events, prior-turn freezing, unknown-turn
reconciliation, stale-load guard, action routing) + SessionListStore.
- hooks/useSessionChat + useSessions: thin wrappers, deps injectable.
- renderer test infra added from scratch (vitest + jsdom +
testing-library); 29 tests across stores, pure views, feed, and hooks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:10:17 +05:30
|
|
|
// ── New runtime: sessions + turns (session-design.md) ────────────────────
|
|
|
|
|
// Turn-mutating calls return quickly; the renderer follows progress through
|
|
|
|
|
// the sessions:events feed and the shared reduceTurn reducer.
|
|
|
|
|
'sessions:create': {
|
|
|
|
|
req: z.object({ title: z.string().optional() }),
|
|
|
|
|
res: z.object({ sessionId: z.string() }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:list': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.object({ sessions: z.array(z.custom<SessionIndexEntry>()) }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:get': {
|
|
|
|
|
req: z.object({ sessionId: z.string() }),
|
|
|
|
|
res: z.custom<SessionState>(),
|
|
|
|
|
},
|
|
|
|
|
'sessions:getTurn': {
|
|
|
|
|
// Events are strictly validated at the repository read; typed via
|
|
|
|
|
// z.custom to avoid re-validating potentially large logs per IPC hop.
|
|
|
|
|
req: z.object({ turnId: z.string() }),
|
|
|
|
|
res: z.custom<{ turnId: string; events: Array<z.infer<typeof TurnEvent>> }>(),
|
|
|
|
|
},
|
|
|
|
|
'sessions:sendMessage': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
input: UserMessage,
|
|
|
|
|
config: z.object({
|
|
|
|
|
agent: RequestedAgent,
|
|
|
|
|
autoPermission: z.boolean().optional(),
|
|
|
|
|
maxModelCalls: z.number().int().positive().optional(),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ turnId: z.string() }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:respondToPermission': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
turnId: z.string(),
|
|
|
|
|
toolCallId: z.string(),
|
|
|
|
|
decision: z.enum(['allow', 'deny']),
|
|
|
|
|
metadata: z.json().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:respondToAskHuman': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
turnId: z.string(),
|
|
|
|
|
toolCallId: z.string(),
|
|
|
|
|
answer: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:stopTurn': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
turnId: z.string(),
|
|
|
|
|
reason: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:resumeTurn': {
|
|
|
|
|
req: z.object({ sessionId: z.string() }),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:setTitle': {
|
|
|
|
|
req: z.object({ sessionId: z.string(), title: z.string() }),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
2026-07-02 13:53:47 +05:30
|
|
|
'sessions:downloadLog': {
|
|
|
|
|
// Concatenates the session's turn logs into one JSONL for debugging.
|
|
|
|
|
req: z.object({ sessionId: z.string() }),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat(x): sessions IPC + renderer data layer with testable stores (stage 5a)
The wire and state layer for the UI cutover; App.tsx integration follows
separately.
- shared/ipc.ts: eleven sessions:* invoke channels + the sessions:events
push feed (SessionBusEvent via z.custom, like runs:events). The generic
preload bridge needs no changes.
- main: sessions:* handlers as thin pass-throughs to the DI'd sessions
service; startSessionsWatcher forwards the session bus to all windows;
startup awaits the session-index scan before the renderer can list.
- renderer architecture per review guidance — all logic in framework-
agnostic, dependency-injected modules; hooks are thin
useSyncExternalStore subscriptions; components will consume pre-digested
view models:
- client.ts: narrow SessionsClient over window.ipc (fakeable).
- feed.ts: one shared sessions:events consumer with fan-out (factory
for tests).
- turn-view.ts: pure derivations — live overlay (deltas accumulate,
canonical events clear), TurnState -> ConversationItem[], and the
session chat state (permission/ask-human maps re-manufactured in the
runs-era shapes so existing components render unchanged;
isProcessing/isThinking contract preserved).
- store.ts: SessionChatStore (seed via getSession/getTurn, shared
reduceTurn over live events, prior-turn freezing, unknown-turn
reconciliation, stale-load guard, action routing) + SessionListStore.
- hooks/useSessionChat + useSessions: thin wrappers, deps injectable.
- renderer test infra added from scratch (vitest + jsdom +
testing-library); 29 tests across stores, pure views, feed, and hooks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:10:17 +05:30
|
|
|
'sessions:delete': {
|
|
|
|
|
req: z.object({ sessionId: z.string() }),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'sessions:events': {
|
|
|
|
|
// Typed via z.custom so the renderer's `on` handler is typed without
|
|
|
|
|
// runtime validation (the broadcast path bypasses preload validation,
|
|
|
|
|
// like runs:events).
|
|
|
|
|
req: z.custom<SessionBusEvent>(),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-02-05 16:05:10 +05:30
|
|
|
'services:events': {
|
|
|
|
|
req: ServiceEvent,
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note-agent:events': {
|
|
|
|
|
req: LiveNoteAgentEvent,
|
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
|
|
|
res: z.null(),
|
|
|
|
|
},
|
feat: background tasks
Adds Background Tasks — recurring background agents the user can set up to
either keep a digest current (daily email summary, top HN stories, weather
brief) or perform a recurring action (draft a reply, post to Slack, call an
API). Each task is a persistent set of instructions plus optional triggers
(schedule, time-of-day window, or matching incoming Gmail / calendar event).
The agent reads the verbs in the instructions on every run and picks the
right mode automatically.
User-facing surfaces:
- New "Background tasks" entry in the sidebar, with a table listing every
task, its schedule, last run, and an active toggle.
- A detail page per task with a max-width reader showing the task's
current output and a control sidebar for editing instructions, triggers,
and reviewing run history.
- "New task" can open in a free-form box where the user describes what they
want and Copilot sets it up end-to-end, or in a structured form for
manual setup.
- "Edit with Copilot" hand-off from the detail view, pre-seeded with the
task's context.
Under the hood:
- The event pipeline that previously powered live-notes is now a generic
consumer registry. Live-notes and background tasks both subscribe;
incoming events are routed to candidates from both concurrently.
- Schedule helpers and the agent-message trigger block are factored out of
live-notes into shared modules. Both features use the same building
blocks now.
- Copilot's proactive routing is reframed: anything recurring (cadence
words, watch / monitor verbs, action verbs, event-conditional asks) now
flows to background tasks. Live-notes load only on explicit mention.
- A small reliability fix for the run-creation fallback chain: an
empty-string model/provider passed by an LLM tool call now correctly
falls through to the default instead of being persisted as a real value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:43:25 +05:30
|
|
|
'bg-task-agent:events': {
|
|
|
|
|
req: BackgroundTaskAgentEvent,
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-02-04 01:12:06 +05:30
|
|
|
'models:list': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
providers: z.array(z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
models: z.array(z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string().optional(),
|
|
|
|
|
release_date: z.string().optional(),
|
|
|
|
|
})),
|
|
|
|
|
})),
|
|
|
|
|
lastUpdated: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'models:test': {
|
|
|
|
|
req: LlmModelConfig,
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-06-19 01:12:53 +05:30
|
|
|
'models:listForProvider': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
provider: LlmProvider,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
models: z.array(z.string()).optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: compose new email with contact autocomplete and AI drafting (#616)
* feat: compose new email with contact autocomplete and AI drafting
- Add a compose-new-email box to the email view with a recipient field
that autocompletes from Gmail contacts (keyboard navigation, match
highlighting, avatar chips)
- Build contact indices in core: gmail_sent_contacts syncs the SENT
label via the Gmail API for full coverage of people you've emailed,
with gmail_contacts as an instant local-snapshot fallback; both are
pre-warmed at startup so the first keystroke is instant
- Add generateOneShot() one-shot text generation for the composer's
"write with AI", resolving to the active default model/provider
- Add getAccountName() (parsed from a recent SENT message's From
header, no extra OAuth scope) so AI drafts sign off with the real name
- New IPC channels: gmail:searchContacts, gmail:getAccountName,
llm:generate, llm:getDefaultModel
* feat: attachments, undo/redo, and unified compose for new emails
- Merge ComposeNewBox into ComposeBox via a new 'new' mode, memoizing the
component so inbox sync ticks no longer jank the open composer.
- Add file attachments: stage files in the renderer (25MB cap), pass raw
base64 over IPC, and build a multipart/mixed MIME on send.
- Add undo/redo buttons to the compose toolbar.
- Single Write/Edit AI bar that generates a draft, then iteratively rewrites
it; drop the hardcoded Gemini Flash model and use the default Copilot model.
- Suppress inbox reloads while the compose-new modal is open.
- Log llm:generate provider/model/output for debugging.
* fix: remove redundant Subject placeholder in composer
The subject row already has a 'Subject' gutter label, so the input's
placeholder repeated the word — an empty field read 'Subject' twice.
Drop the placeholder to match the To/Cc/Bcc fields.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:14:00 +05:30
|
|
|
'llm:getDefaultModel': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
model: z.string(),
|
|
|
|
|
provider: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'llm:generate': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
prompt: z.string().min(1),
|
|
|
|
|
system: z.string().optional(),
|
|
|
|
|
model: z.string().optional(),
|
|
|
|
|
provider: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
text: z.string().optional(),
|
|
|
|
|
model: z.string().optional(),
|
|
|
|
|
provider: z.string().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-02-04 01:12:06 +05:30
|
|
|
'models:saveConfig': {
|
|
|
|
|
req: LlmModelConfig,
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-01-06 06:56:42 +05:30
|
|
|
'oauth:connect': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
provider: z.string(),
|
2026-02-03 23:01:23 +05:30
|
|
|
clientId: z.string().optional(),
|
2026-04-10 00:42:59 +05:30
|
|
|
clientSecret: z.string().optional(),
|
2026-01-06 06:56:42 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'oauth:disconnect': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
provider: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'oauth:list-providers': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
providers: z.array(z.string()),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-02-17 09:54:34 +05:30
|
|
|
'oauth:getState': {
|
2026-01-06 06:56:42 +05:30
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
2026-02-17 09:54:34 +05:30
|
|
|
config: z.record(z.string(), z.object({
|
|
|
|
|
connected: z.boolean(),
|
2026-02-21 22:58:38 +05:30
|
|
|
error: z.string().nullable().optional(),
|
2026-04-07 12:38:19 +05:30
|
|
|
userId: z.string().optional(),
|
2026-04-07 11:24:25 -07:00
|
|
|
clientId: z.string().nullable().optional(),
|
2026-02-17 09:54:34 +05:30
|
|
|
})),
|
2026-01-06 06:56:42 +05:30
|
|
|
}),
|
|
|
|
|
},
|
2026-03-24 11:50:22 +05:30
|
|
|
'account:getRowboat': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
signedIn: z.boolean(),
|
|
|
|
|
accessToken: z.string().nullable(),
|
|
|
|
|
config: RowboatApiConfig.nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-01-20 15:18:47 +05:30
|
|
|
'oauth:didConnect': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
provider: z.string(),
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
2026-04-07 12:38:19 +05:30
|
|
|
userId: z.string().optional(),
|
2026-01-20 15:18:47 +05:30
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-05-04 15:47:30 +05:30
|
|
|
'app:openUrl': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
url: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-07-04 14:20:13 +05:30
|
|
|
// Bring the main app window to the foreground (e.g. the assistant navigated
|
|
|
|
|
// the UI during a call while the user was in another app).
|
|
|
|
|
'app:focusMainWindow': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
2026-05-04 15:47:30 +05:30
|
|
|
'app:takeMeetingNotes': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
// Pass the raw calendar event JSON through; renderer adapts to its existing flow.
|
|
|
|
|
event: z.unknown(),
|
|
|
|
|
// When true, the renderer should also open the meeting URL (Zoom/Meet/etc.)
|
|
|
|
|
// in addition to triggering the take-notes flow.
|
|
|
|
|
openMeeting: z.boolean().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
|
|
|
|
'app:consumePendingDeepLink': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
url: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-01-08 11:19:43 +05:30
|
|
|
'granola:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
Code Mode: in-chat toggle, settings tab, and permission/command UX (#572)
* feat: add in-chat code mode toggle with claude/codex swap
* feat: show agent and add swap-and-retry on acpx permission card
* style: reorder permission card buttons (approve, deny, swap)
* feat: add tooltips to composer plus and web search buttons
* feat: add code mode settings tab with agent install/auth checks
* feat: show sign-in command when agent installed but signed out
* style: refine code-mode permission and command block UX
- Render permission block before the command block
- Collapse permission details after a response; click header to expand
- Drop status icons/badge; use minimal green / bold red blocks
- Auto-collapse the running command block once it completes
* feat: rotating progress labels for code-mode commands; darker tool borders
- Code-mode (acpx) command block shows status-aware labels: rotating
'Working on the task…' phrases (5s each, holding on the last) while
running, then 'Completed the task' / "Couldn't complete the task"
- Darken outer border on all tool blocks in light and dark modes
* fix: detect Claude Code sign-in via macOS Keychain
On macOS, Claude Code stores OAuth credentials in the login Keychain
(service 'Claude Code-credentials'), not in ~/.claude/.credentials.json.
Read the Keychain as a fallback so signed-in Mac users are detected.
* feat: persistent per-chat sessions for code-mode coding agents
- Use a named acpx session (rowboat-<runId>) per chat so follow-up
coding requests resume the same agent and keep context
- Create the session once at chat start (sessions new --name), then
prompt with -s <name>; reuse on follow-ups (no re-create)
- Drop the redundant in-chat 'reply yes' confirmation (the executeCommand
permission card is the confirmation)
- Code-mode output uses plain-text paths (overrides global filepath rule)
- On not-installed/auth errors, point user to Settings -> Code Mode
* fix: code-mode session creation uses idempotent ensure, run sequentially
- Use 'sessions ensure --name' instead of 'sessions new' so reopening a
chat resumes the existing session instead of erroring on a name clash
- Create the session and run the prompt as separate sequential calls so
the permission/command blocks render one at a time (not all at once)
* fix: reliable Claude Code session resume on Windows (avoid claude.cmd EINVAL)
Resuming a code-mode chat after restarting the app spawns a fresh ACP
agent. On Windows + Node >=20.12 the bridge spawning claude.cmd throws
EINVAL, so the session queue owner fails to start. Rowboat injects
CLAUDE_CODE_EXECUTABLE=claude.exe to dodge this, but the override didn't
reliably reach the spawn. Windows-only; no-op on macOS/Linux.
- executeCommand now accepts an env override and the non-abortable
fallback path passes it through (was silently dropped)
- resolveClaudeExeOnWindows also scans known npm/pnpm/volta global bin
dirs, not just PATH (Electron's runtime PATH can omit them)
- add --timeout 600 to acpx prompt commands so a genuine stall fails
cleanly instead of hanging on 'Running' forever
2026-05-28 14:52:09 +05:30
|
|
|
'codeMode:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
feat: run code mode on an in-app ACP client with live approvals (#593)
* feat(code-mode): add ACP client engine (Layer 2 core)
Own the Agent Client Protocol client instead of shelling out to `acpx`, so code
mode can stream structured events (tool calls, diffs, plan) and surface live
permission requests. Headless acpx can't do live approvals (it only supports
--approve-all), which is why we drive the agent adapters ourselves.
- code-mode/acp/{agents,client,permission-broker,session-store,manager,types}.ts:
headless engine driving the Claude/Codex ACP adapters; one warm session per chat
with create-or-resume via session/load; approval policy (ask | auto-approve-reads
| yolo) in the broker.
- claude-exec.ts: cross-platform claude resolver (Windows .cmd EINVAL fix + macOS/Linux
GUI-PATH safety net) shared with the legacy acpx path in builtin-tools.ts.
- add @agentclientprotocol/sdk + claude/codex adapters to core.
* feat(code-mode): route code mode through code_agent_run tool + live approvals
Replace the acpx shell-out with a structured code_agent_run tool that drives the
ACP engine directly, streaming the agent's tool calls / diffs / plan into the chat
and surfacing permission requests inline.
- shared: code-mode.ts zod schemas; add code-run-event + code-run-permission-request
RunEvent variants (stream to the renderer over the existing runs:events channel);
codeRun:resolvePermission IPC channel.
- core: CodePermissionRegistry (promise-based mid-run approvals — the LLM tool-loop's
pre-call gate can't model a mid-execution wait); register codeModeManager +
codePermissionRegistry in awilix.
- core: code_agent_run builtin tool (streams via ctx.publish, asks via the registry,
cancels on ctx.signal, returns the agent summary). CodeModeConfig.approvalPolicy
(ask | auto-approve-reads | yolo; default ask). Exclude the tool from the headless
background-task / live-note / inline-task agents so they can't block on an approval.
- main: codeRun:resolvePermission handler -> registry.resolve.
- rewrite the code-with-agents skill and the runtime "Code Mode (Active)" block to call
code_agent_run instead of emitting npx acpx commands.
* feat(code-mode): render coding runs inline (live timeline + permission card)
Render a code_agent_run tool call as a live CodingRun block instead of generic
tool output: the agent's text, tool-call rows (kind icon + status + changed-file
names from diffs), a plan checklist, and resolved-permission lines — plus an
inline Allow / Always-allow / Deny card wired to codeRun:resolvePermission.
- chat-conversation.ts: ToolCall carries codeRunEvents + pendingCodePermission;
code_agent_run is excluded from tool-grouping so it renders standalone.
- App.tsx: handle code-run-event / code-run-permission-request, clear the pending
card on tool-result, handleCodePermissionResponse, render via CodingRunBlock.
* fix(code-mode): run the ACP adapter as Node under Electron + resolve it from main
Two runtime failures that only surfaced inside the packaged/bundled Electron app
(the headless harness used real node, so neither showed there):
- "ACP connection closed": the main process spawns the adapter via
process.execPath, which inside Electron is the Electron binary, not node — so
the child never ran as Node and its ACP stdio stream closed immediately. Set
ELECTRON_RUN_AS_NODE=1 on the adapter env (a no-op under real node).
- "Cannot find module '@agentclientprotocol/claude-agent-acp'": the adapters were
transitive (core) deps, unreachable from the esbuild-bundled main.cjs. Add them
as direct deps of the main app so require.resolve finds them at runtime (and so
they ship when packaged).
Also capture the adapter's stderr + exit code and enrich connection errors, so a
future failure reports the real cause instead of the opaque "ACP connection closed".
* chore(code-mode): remove dead acpx code paths and stale copy
Code mode now runs through the code_agent_run tool (owning the ACP client), so the
legacy acpx shell-out paths are dead. Remove them:
- core: envForCommand (acpx-only CLAUDE_CODE_EXECUTABLE injection) from
executeCommand; getCodeModeCommandLabel (acpx run-status label).
- renderer: the acpx-detection "switch agent / auto-flip the code-mode chip" flow —
App.tsx executeCommand detection, the permission-request onSwitchAgent button +
badge, and the composer's code-mode-detected listener.
- copy: Settings -> Code Mode and the code-with-agents skill summary no longer
mention acpx; tidy stale comments (claude-exec, command-executor).
No behavior change for code mode; the general executeCommand tool is unaffected.
* feat(code-mode): approval-policy selector in Settings
Surface the approval policy (Ask every time / Auto-approve reads / YOLO) in
Settings -> Code Mode, instead of being config-file only. The broker already
reads CodeModeConfig.approvalPolicy; this plumbs it through the
codeMode:getConfig / setConfig IPC + main handlers and adds the picker UI
(with a one-line explanation of each level). Defaults to "ask".
* fix(code-mode): harden ACP engine — turn-scoped connections, chip-authoritative agent, reliable stop
Three robustness fixes that co-modify manager.runPrompt and the code_agent_run
tool, so they land together:
- Lifecycle: scope each ACP adapter connection to the agent turn. Dispose it a
short grace (60s) after the turn ends instead of holding it for the app's life;
the next turn resumes via session/load (both agents support it). Wire
disposeAll() on app quit (was dead code). Fixes the unbounded per-chat leak of
booted agent processes.
- Agent selection: make the composer chip the source of truth. Thread codeMode
into ToolContext; code_agent_run uses it instead of the model's guessed `agent`
arg, which anchored on the thread's earlier agent and ignored a chip change.
Prompts updated to match; the run is labelled by the agent that actually ran.
- Stop/abort: guarantee a stopped turn unwinds. On abort the manager sends ACP
session/cancel, then force-kills the adapter after a 2s grace and resolves the
turn as cancelled — a wedged adapter can no longer hang the run and lock the
chat. code_agent_run returns a clean cancelled result.
* fix(code-mode): hide Codex's native console window on Windows
Codex's engine ships as a native console-subsystem binary (codex.exe). Launched
from our console-less Electron process tree, Windows allocated a fresh *visible*
console window for it; closing that window wedged the run in a pending state.
(Claude Code is a Node CLI, so it never triggers this.)
The window is created by @openai/codex's launcher (bin/codex.js), which spawns
codex.exe with no windowsHide. Patch it via pnpm to pass windowsHide: true
(CREATE_NO_WINDOW) so the console stays hidden — no window, nothing to close.
* refactor(code-mode): move ACP session files out of WorkDir/config
Per-run ACP session state is runtime state that accumulates one file per
chat run, not user/app config. Relocate it from WorkDir/config to a
dedicated WorkDir/code-mode/sessions/ so it can be listed, cleaned up, and
managed on its own without crowding config. Drop the now-redundant
codesession- filename prefix (the directory conveys it).
2026-06-05 14:45:08 +05:30
|
|
|
approvalPolicy: ApprovalPolicy.optional(),
|
Code Mode: in-chat toggle, settings tab, and permission/command UX (#572)
* feat: add in-chat code mode toggle with claude/codex swap
* feat: show agent and add swap-and-retry on acpx permission card
* style: reorder permission card buttons (approve, deny, swap)
* feat: add tooltips to composer plus and web search buttons
* feat: add code mode settings tab with agent install/auth checks
* feat: show sign-in command when agent installed but signed out
* style: refine code-mode permission and command block UX
- Render permission block before the command block
- Collapse permission details after a response; click header to expand
- Drop status icons/badge; use minimal green / bold red blocks
- Auto-collapse the running command block once it completes
* feat: rotating progress labels for code-mode commands; darker tool borders
- Code-mode (acpx) command block shows status-aware labels: rotating
'Working on the task…' phrases (5s each, holding on the last) while
running, then 'Completed the task' / "Couldn't complete the task"
- Darken outer border on all tool blocks in light and dark modes
* fix: detect Claude Code sign-in via macOS Keychain
On macOS, Claude Code stores OAuth credentials in the login Keychain
(service 'Claude Code-credentials'), not in ~/.claude/.credentials.json.
Read the Keychain as a fallback so signed-in Mac users are detected.
* feat: persistent per-chat sessions for code-mode coding agents
- Use a named acpx session (rowboat-<runId>) per chat so follow-up
coding requests resume the same agent and keep context
- Create the session once at chat start (sessions new --name), then
prompt with -s <name>; reuse on follow-ups (no re-create)
- Drop the redundant in-chat 'reply yes' confirmation (the executeCommand
permission card is the confirmation)
- Code-mode output uses plain-text paths (overrides global filepath rule)
- On not-installed/auth errors, point user to Settings -> Code Mode
* fix: code-mode session creation uses idempotent ensure, run sequentially
- Use 'sessions ensure --name' instead of 'sessions new' so reopening a
chat resumes the existing session instead of erroring on a name clash
- Create the session and run the prompt as separate sequential calls so
the permission/command blocks render one at a time (not all at once)
* fix: reliable Claude Code session resume on Windows (avoid claude.cmd EINVAL)
Resuming a code-mode chat after restarting the app spawns a fresh ACP
agent. On Windows + Node >=20.12 the bridge spawning claude.cmd throws
EINVAL, so the session queue owner fails to start. Rowboat injects
CLAUDE_CODE_EXECUTABLE=claude.exe to dodge this, but the override didn't
reliably reach the spawn. Windows-only; no-op on macOS/Linux.
- executeCommand now accepts an env override and the non-abortable
fallback path passes it through (was silently dropped)
- resolveClaudeExeOnWindows also scans known npm/pnpm/volta global bin
dirs, not just PATH (Electron's runtime PATH can omit them)
- add --timeout 600 to acpx prompt commands so a genuine stall fails
cleanly instead of hanging on 'Running' forever
2026-05-28 14:52:09 +05:30
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeMode:setConfig': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
feat: run code mode on an in-app ACP client with live approvals (#593)
* feat(code-mode): add ACP client engine (Layer 2 core)
Own the Agent Client Protocol client instead of shelling out to `acpx`, so code
mode can stream structured events (tool calls, diffs, plan) and surface live
permission requests. Headless acpx can't do live approvals (it only supports
--approve-all), which is why we drive the agent adapters ourselves.
- code-mode/acp/{agents,client,permission-broker,session-store,manager,types}.ts:
headless engine driving the Claude/Codex ACP adapters; one warm session per chat
with create-or-resume via session/load; approval policy (ask | auto-approve-reads
| yolo) in the broker.
- claude-exec.ts: cross-platform claude resolver (Windows .cmd EINVAL fix + macOS/Linux
GUI-PATH safety net) shared with the legacy acpx path in builtin-tools.ts.
- add @agentclientprotocol/sdk + claude/codex adapters to core.
* feat(code-mode): route code mode through code_agent_run tool + live approvals
Replace the acpx shell-out with a structured code_agent_run tool that drives the
ACP engine directly, streaming the agent's tool calls / diffs / plan into the chat
and surfacing permission requests inline.
- shared: code-mode.ts zod schemas; add code-run-event + code-run-permission-request
RunEvent variants (stream to the renderer over the existing runs:events channel);
codeRun:resolvePermission IPC channel.
- core: CodePermissionRegistry (promise-based mid-run approvals — the LLM tool-loop's
pre-call gate can't model a mid-execution wait); register codeModeManager +
codePermissionRegistry in awilix.
- core: code_agent_run builtin tool (streams via ctx.publish, asks via the registry,
cancels on ctx.signal, returns the agent summary). CodeModeConfig.approvalPolicy
(ask | auto-approve-reads | yolo; default ask). Exclude the tool from the headless
background-task / live-note / inline-task agents so they can't block on an approval.
- main: codeRun:resolvePermission handler -> registry.resolve.
- rewrite the code-with-agents skill and the runtime "Code Mode (Active)" block to call
code_agent_run instead of emitting npx acpx commands.
* feat(code-mode): render coding runs inline (live timeline + permission card)
Render a code_agent_run tool call as a live CodingRun block instead of generic
tool output: the agent's text, tool-call rows (kind icon + status + changed-file
names from diffs), a plan checklist, and resolved-permission lines — plus an
inline Allow / Always-allow / Deny card wired to codeRun:resolvePermission.
- chat-conversation.ts: ToolCall carries codeRunEvents + pendingCodePermission;
code_agent_run is excluded from tool-grouping so it renders standalone.
- App.tsx: handle code-run-event / code-run-permission-request, clear the pending
card on tool-result, handleCodePermissionResponse, render via CodingRunBlock.
* fix(code-mode): run the ACP adapter as Node under Electron + resolve it from main
Two runtime failures that only surfaced inside the packaged/bundled Electron app
(the headless harness used real node, so neither showed there):
- "ACP connection closed": the main process spawns the adapter via
process.execPath, which inside Electron is the Electron binary, not node — so
the child never ran as Node and its ACP stdio stream closed immediately. Set
ELECTRON_RUN_AS_NODE=1 on the adapter env (a no-op under real node).
- "Cannot find module '@agentclientprotocol/claude-agent-acp'": the adapters were
transitive (core) deps, unreachable from the esbuild-bundled main.cjs. Add them
as direct deps of the main app so require.resolve finds them at runtime (and so
they ship when packaged).
Also capture the adapter's stderr + exit code and enrich connection errors, so a
future failure reports the real cause instead of the opaque "ACP connection closed".
* chore(code-mode): remove dead acpx code paths and stale copy
Code mode now runs through the code_agent_run tool (owning the ACP client), so the
legacy acpx shell-out paths are dead. Remove them:
- core: envForCommand (acpx-only CLAUDE_CODE_EXECUTABLE injection) from
executeCommand; getCodeModeCommandLabel (acpx run-status label).
- renderer: the acpx-detection "switch agent / auto-flip the code-mode chip" flow —
App.tsx executeCommand detection, the permission-request onSwitchAgent button +
badge, and the composer's code-mode-detected listener.
- copy: Settings -> Code Mode and the code-with-agents skill summary no longer
mention acpx; tidy stale comments (claude-exec, command-executor).
No behavior change for code mode; the general executeCommand tool is unaffected.
* feat(code-mode): approval-policy selector in Settings
Surface the approval policy (Ask every time / Auto-approve reads / YOLO) in
Settings -> Code Mode, instead of being config-file only. The broker already
reads CodeModeConfig.approvalPolicy; this plumbs it through the
codeMode:getConfig / setConfig IPC + main handlers and adds the picker UI
(with a one-line explanation of each level). Defaults to "ask".
* fix(code-mode): harden ACP engine — turn-scoped connections, chip-authoritative agent, reliable stop
Three robustness fixes that co-modify manager.runPrompt and the code_agent_run
tool, so they land together:
- Lifecycle: scope each ACP adapter connection to the agent turn. Dispose it a
short grace (60s) after the turn ends instead of holding it for the app's life;
the next turn resumes via session/load (both agents support it). Wire
disposeAll() on app quit (was dead code). Fixes the unbounded per-chat leak of
booted agent processes.
- Agent selection: make the composer chip the source of truth. Thread codeMode
into ToolContext; code_agent_run uses it instead of the model's guessed `agent`
arg, which anchored on the thread's earlier agent and ignored a chip change.
Prompts updated to match; the run is labelled by the agent that actually ran.
- Stop/abort: guarantee a stopped turn unwinds. On abort the manager sends ACP
session/cancel, then force-kills the adapter after a 2s grace and resolves the
turn as cancelled — a wedged adapter can no longer hang the run and lock the
chat. code_agent_run returns a clean cancelled result.
* fix(code-mode): hide Codex's native console window on Windows
Codex's engine ships as a native console-subsystem binary (codex.exe). Launched
from our console-less Electron process tree, Windows allocated a fresh *visible*
console window for it; closing that window wedged the run in a pending state.
(Claude Code is a Node CLI, so it never triggers this.)
The window is created by @openai/codex's launcher (bin/codex.js), which spawns
codex.exe with no windowsHide. Patch it via pnpm to pass windowsHide: true
(CREATE_NO_WINDOW) so the console stays hidden — no window, nothing to close.
* refactor(code-mode): move ACP session files out of WorkDir/config
Per-run ACP session state is runtime state that accumulates one file per
chat run, not user/app config. Relocate it from WorkDir/config to a
dedicated WorkDir/code-mode/sessions/ so it can be listed, cleaned up, and
managed on its own without crowding config. Drop the now-redundant
codesession- filename prefix (the directory conveys it).
2026-06-05 14:45:08 +05:30
|
|
|
approvalPolicy: ApprovalPolicy.optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// Answer a mid-run permission request from a code_agent_run coding turn.
|
|
|
|
|
'codeRun:resolvePermission': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
requestId: z.string(),
|
|
|
|
|
decision: PermissionDecision,
|
Code Mode: in-chat toggle, settings tab, and permission/command UX (#572)
* feat: add in-chat code mode toggle with claude/codex swap
* feat: show agent and add swap-and-retry on acpx permission card
* style: reorder permission card buttons (approve, deny, swap)
* feat: add tooltips to composer plus and web search buttons
* feat: add code mode settings tab with agent install/auth checks
* feat: show sign-in command when agent installed but signed out
* style: refine code-mode permission and command block UX
- Render permission block before the command block
- Collapse permission details after a response; click header to expand
- Drop status icons/badge; use minimal green / bold red blocks
- Auto-collapse the running command block once it completes
* feat: rotating progress labels for code-mode commands; darker tool borders
- Code-mode (acpx) command block shows status-aware labels: rotating
'Working on the task…' phrases (5s each, holding on the last) while
running, then 'Completed the task' / "Couldn't complete the task"
- Darken outer border on all tool blocks in light and dark modes
* fix: detect Claude Code sign-in via macOS Keychain
On macOS, Claude Code stores OAuth credentials in the login Keychain
(service 'Claude Code-credentials'), not in ~/.claude/.credentials.json.
Read the Keychain as a fallback so signed-in Mac users are detected.
* feat: persistent per-chat sessions for code-mode coding agents
- Use a named acpx session (rowboat-<runId>) per chat so follow-up
coding requests resume the same agent and keep context
- Create the session once at chat start (sessions new --name), then
prompt with -s <name>; reuse on follow-ups (no re-create)
- Drop the redundant in-chat 'reply yes' confirmation (the executeCommand
permission card is the confirmation)
- Code-mode output uses plain-text paths (overrides global filepath rule)
- On not-installed/auth errors, point user to Settings -> Code Mode
* fix: code-mode session creation uses idempotent ensure, run sequentially
- Use 'sessions ensure --name' instead of 'sessions new' so reopening a
chat resumes the existing session instead of erroring on a name clash
- Create the session and run the prompt as separate sequential calls so
the permission/command blocks render one at a time (not all at once)
* fix: reliable Claude Code session resume on Windows (avoid claude.cmd EINVAL)
Resuming a code-mode chat after restarting the app spawns a fresh ACP
agent. On Windows + Node >=20.12 the bridge spawning claude.cmd throws
EINVAL, so the session queue owner fails to start. Rowboat injects
CLAUDE_CODE_EXECUTABLE=claude.exe to dodge this, but the override didn't
reliably reach the spawn. Windows-only; no-op on macOS/Linux.
- executeCommand now accepts an env override and the non-abortable
fallback path passes it through (was silently dropped)
- resolveClaudeExeOnWindows also scans known npm/pnpm/volta global bin
dirs, not just PATH (Electron's runtime PATH can omit them)
- add --timeout 600 to acpx prompt commands so a genuine stall fails
cleanly instead of hanging on 'Running' forever
2026-05-28 14:52:09 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeMode:checkAgentStatus': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
claude: z.object({ installed: z.boolean(), signedIn: z.boolean() }),
|
|
|
|
|
codex: z.object({ installed: z.boolean(), signedIn: z.boolean() }),
|
|
|
|
|
}),
|
|
|
|
|
},
|
Code mode: make packaged builds work via managed engine provisioning (#625)
* fix(code-mode): make packaged code mode work via on-demand engine provisioning
Packaged builds could never run code mode: the Claude/Codex ACP adapters are
spawned as separate `node <entry>` processes resolved at runtime, but esbuild
can't inline a dynamic spawn target and Forge strips the workspace node_modules,
so every release threw `Cannot find module '@agentclientprotocol/...'`. Dev
worked only because of the pnpm symlink.
Rather than bundle the ~400 MB of native engines (one claude + one codex binary
per OS), provision them on demand:
- forge.config.cjs: stage the two ACP adapters + their JS dependency closure into
.package/acp/node_modules (npm-style nested layout, native engines skipped),
exempt .package from the node_modules ignore rule, and only sign/notarize when
APPLE_ID is set so unsigned local/CI builds can package.
- agents.ts: resolve the adapter from the staged location first (node_modules
fallback in dev); provision the pinned engine and point the adapter at it via
CLAUDE_CODE_EXECUTABLE / CODEX_PATH. No dependence on a user's global install.
- engine-provisioner.ts: ensureEngine() downloads the per-platform engine package
from npm AT THE EXACT VERSION THE ADAPTER WAS BUILT AGAINST, verifies its sha512
integrity, extracts atomically into ~/.rowboat/engines/<agent>/<version>/, and
caches it. Version-pinning keeps the ACP handshake compatible.
- engine-manifest.ts + scripts/gen-engine-manifest.mjs: committed manifest of
tarball URLs + integrity for all platforms, regenerated from the adapters'
pinned versions on a bump.
Verified on macOS arm64: both engines provision and run, and both adapters
complete the ACP initialize handshake from the packaged .app against the
provisioned engines. Installer drops from ~790 MB to 390 MB.
* feat(code-mode): explicit per-agent Enable in Settings; no silent chat download
Code mode now requires the user to explicitly enable an agent before use, instead
of silently downloading a ~200 MB engine on the first chat message.
- Settings → Code Mode: each agent shows "Not enabled" + an Enable button that
downloads its engine with a live progress indicator (download % → verify →
install), then flips to "Engine ready". Driven by a new codeMode:provisionEngine
IPC call + a codeMode:engineProgress push channel. The section now states the
prerequisite explicitly: the agent must be installed (Enable) and logged in
(claude login / codex login — code mode reuses that saved credential).
- Chat path no longer auto-downloads: getProvisionedEnginePath() returns the
enabled engine or throws a clear "enable it in Settings → Code Mode" error, so
there's never a surprise mid-conversation download. getAgentLaunchSpec is sync
again.
- Agent status: `installed` now means "engine provisioned" (downloaded), driving
the Enable/Ready state; the new-session dialog shows "Enable in Settings" and
disables un-enabled agents. Dropped the dead PATH-probing for a global CLI.
Verified: empty cache -> status installed=false and the chat path throws the
enable-in-Settings error (no download); core, renderer, and main typecheck/build;
no new lint errors.
* fix(code-mode): show only percentage during engine download in Settings
* feat(code-mode): prune superseded engine versions after install
After a successful provision, remove any other version dirs (and their .meta) for
that agent so old ~200 MB engines don't accumulate across version bumps. Best-effort;
never fails a good install. Verified: a planted stale version dir + meta are both
removed after provisioning the current version.
* fix(code-mode): keep showing engine download % after reopening Settings
Provisioning state lived in the row component, which unmounts when the Settings
dialog closes — so reopening mid-download showed the Enable button again even though
the download was still running in the main process. Move provisioning state to a
module-level store with one persistent listener on codeMode:engineProgress, so a row
remounting (dialog reopened) reflects the live % and resolves to Ready on completion.
* fix(code-mode): flip Enable row straight to Ready after install (no Enable flash)
On successful provision the in-flight flag was cleared before the async status
refresh completed, so the row briefly (or until reopen) showed the Enable button
again. Await the status refresh before clearing the flag so it transitions directly
to Ready.
* fix(code-mode): optimistically show Ready right after Enable completes
Awaiting the status refresh wasn't enough — setStatus re-renders the parent
separately from the row, leaving a window where the in-flight flag was cleared but
the status prop was stale, so the row flashed/stuck on the Enable button until
reopen. Track just-enabled agents in a module-level set and treat them as installed
immediately; loadStatus still syncs the real status in the background.
* fix(code-mode): graft login-shell PATH + add startup deadline
#1 (the gh/git "command not found" in packaged builds): GUI/Finder launches inherit
launchd's stripped PATH (/usr/bin:/bin:...), so tools the engine spawns — gh, git,
rg, bash — fail even though they work from a terminal (e.g. Homebrew's
/opt/homebrew/bin/gh). Probe the user's login-shell PATH and graft it onto the
engine's env before spawn (shell-env.ts; no-op on Windows / probe failure).
#2: add a 60s startup deadline (initialize / session create+load) so a wedged engine
fails with a clear, stderr-enriched error instead of an infinite "(pending...)".
Overridable via ROWBOAT_ACP_STARTUP_TIMEOUT_MS. Manager now disposes the client on
startup failure so the spawned adapter doesn't leak.
Verified: getAgentLaunchSpec's env.PATH now includes /opt/homebrew/bin (where gh
lives); core builds; no new lint errors.
* chore(code-mode): comment out signing/notarization for local builds
Revert to the explicit comment-out approach for osxSign/osxNotarize: uncomment them
(with APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID) for a signed release build.
* chore(code-mode): keep signing/notarization active in committed config
The repo's forge.config ships with osxSign/osxNotarize enabled (release-ready).
Developers comment them out locally for unsigned test builds and don't commit that.
* chore: approve workspace build scripts so packaging runs non-interactively
The allowBuilds entries were left as "set this to true or false" placeholders, so
`pnpm install` / the pre-build deps check aborted with ERR_PNPM_IGNORED_BUILDS and
`npm run package` failed. Set them to true (and add node-pty, used by the code-mode
embedded terminal) so build scripts are approved and packaging works without a manual
`pnpm approve-builds`.
2026-06-17 09:23:15 -07:00
|
|
|
// Download + install an agent's native engine (the Settings "Enable" action).
|
|
|
|
|
// Streams progress over the 'codeMode:engineProgress' push channel while it runs.
|
|
|
|
|
'codeMode:provisionEngine': {
|
|
|
|
|
req: z.object({ agent: z.enum(['claude', 'codex']) }),
|
|
|
|
|
res: z.object({ success: z.boolean(), error: z.string().optional() }),
|
|
|
|
|
},
|
|
|
|
|
// Push (main -> renderer): engine provisioning progress for the Settings UI.
|
|
|
|
|
'codeMode:engineProgress': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
agent: z.enum(['claude', 'codex']),
|
|
|
|
|
phase: z.enum(['download', 'verify', 'extract', 'done']),
|
|
|
|
|
receivedBytes: z.number().optional(),
|
|
|
|
|
totalBytes: z.number().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-06-12 23:45:38 +05:30
|
|
|
// ==========================================================================
|
|
|
|
|
// Code section: project registry + coding sessions
|
|
|
|
|
// ==========================================================================
|
|
|
|
|
'codeProject:add': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
project: CodeProject,
|
|
|
|
|
git: GitRepoInfo,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeProject:remove': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
projectId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeProject:list': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
projects: z.array(z.object({
|
|
|
|
|
project: CodeProject,
|
|
|
|
|
git: GitRepoInfo,
|
|
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:create': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
projectId: z.string(),
|
|
|
|
|
title: z.string().optional(),
|
|
|
|
|
agent: CodingAgent,
|
|
|
|
|
mode: CodeSessionMode,
|
|
|
|
|
policy: ApprovalPolicy,
|
|
|
|
|
isolation: z.enum(['in-repo', 'worktree']),
|
|
|
|
|
// LLM for Rowboat-mode turns. Unset = the configured default. Like any
|
|
|
|
|
// chat, the model is fixed once the session's run exists.
|
|
|
|
|
model: z.string().optional(),
|
|
|
|
|
provider: z.string().optional(),
|
2026-06-21 08:38:49 -07:00
|
|
|
// The coding agent's own model + reasoning effort (ACP engine). Unlike the
|
|
|
|
|
// Rowboat model these are re-applied each turn, so they stay editable.
|
|
|
|
|
agentModel: z.string().optional(),
|
|
|
|
|
agentEffort: z.string().optional(),
|
2026-06-12 23:45:38 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
session: CodeSession,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:list': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
sessions: z.array(CodeSession),
|
|
|
|
|
statuses: z.record(z.string(), CodeSessionStatus),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:update': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
2026-06-21 08:38:49 -07:00
|
|
|
patch: CodeSession.pick({ title: true, mode: true, policy: true, agent: true, agentModel: true, agentEffort: true }).partial(),
|
2026-06-12 23:45:38 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
session: CodeSession,
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-06-21 08:38:49 -07:00
|
|
|
// Live model + effort choices for a coding agent, discovered from the engine
|
|
|
|
|
// (cached per agent in the main process). Mirrors what `/model` would show.
|
|
|
|
|
'codeMode:listModelOptions': {
|
|
|
|
|
req: z.object({ agent: CodingAgent }),
|
|
|
|
|
res: CodeAgentModelOptions,
|
|
|
|
|
},
|
2026-06-12 23:45:38 +05:30
|
|
|
'codeSession:delete': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
removeWorktree: z.boolean().optional(),
|
|
|
|
|
deleteBranch: z.boolean().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// Direct-drive: send the user's message straight to the session's ACP agent
|
|
|
|
|
// (no copilot LLM in between). Streams back over `runs:events`.
|
|
|
|
|
'codeSession:sendMessage': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
text: z.string().min(1),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
accepted: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:stop': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:gitStatus': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
isRepo: z.boolean(),
|
|
|
|
|
branch: z.string().nullable(),
|
|
|
|
|
hasCommits: z.boolean(),
|
|
|
|
|
files: z.array(GitStatusFile),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:fileDiff': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
path: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
oldText: z.string(),
|
|
|
|
|
newText: z.string(),
|
|
|
|
|
isBinary: z.boolean(),
|
|
|
|
|
tooLarge: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:readdir': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
relPath: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
entries: z.array(z.object({
|
|
|
|
|
name: z.string(),
|
|
|
|
|
kind: z.enum(['file', 'dir']),
|
|
|
|
|
size: z.number().optional(),
|
|
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:readFile': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
relPath: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
content: z.string(),
|
|
|
|
|
isBinary: z.boolean(),
|
|
|
|
|
tooLarge: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:mergeBack': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
conflict: z.boolean().optional(),
|
|
|
|
|
message: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'codeSession:cleanupWorktree': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
deleteBranch: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// main → renderer: live session status transitions from the status tracker.
|
|
|
|
|
'codeSession:status': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
status: CodeSessionStatus,
|
|
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-06-12 23:45:38 +05:30
|
|
|
// ==========================================================================
|
|
|
|
|
// Embedded terminal (Code section): one PTY per coding session
|
|
|
|
|
// ==========================================================================
|
|
|
|
|
// Create-or-attach. Returns the scrollback backlog so a remounted view can
|
|
|
|
|
// repaint what happened while it was closed.
|
|
|
|
|
'terminal:ensure': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
cwd: z.string(),
|
|
|
|
|
cols: z.number().int().positive(),
|
|
|
|
|
rows: z.number().int().positive(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
backlog: z.string(),
|
|
|
|
|
running: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'terminal:input': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
data: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'terminal:resize': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
cols: z.number().int().positive(),
|
|
|
|
|
rows: z.number().int().positive(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'terminal:dispose': {
|
|
|
|
|
req: z.object({ id: z.string() }),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
// main → renderer streams
|
|
|
|
|
'terminal:data': {
|
|
|
|
|
req: z.object({ id: z.string(), data: z.string() }),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
|
|
|
|
'terminal:exit': {
|
|
|
|
|
req: z.object({ id: z.string(), exitCode: z.number() }),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-01-08 11:19:43 +05:30
|
|
|
'granola:setConfig': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-07-03 19:25:06 +05:30
|
|
|
// ── Mobile channels (WhatsApp / Telegram bridge) ─────────────
|
|
|
|
|
'channels:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: ChannelsConfig,
|
|
|
|
|
},
|
|
|
|
|
'channels:setConfig': {
|
|
|
|
|
req: ChannelsConfig,
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'channels:getStatus': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: ChannelsStatus,
|
|
|
|
|
},
|
|
|
|
|
'channels:whatsappLogout': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
// Push: main → renderer status updates (QR rotation, connect/disconnect).
|
|
|
|
|
'channels:status': {
|
|
|
|
|
req: ChannelsStatus,
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-03-10 16:26:51 +05:30
|
|
|
'slack:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:setConfig': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: ship Slack as a knowledge source, hardened for production (#596)
* index slack and add to home page
* filter only useful slack messages in homr
* feat: bundle agent-slack CLI and route all calls through shared executor
Pins agent-slack@0.9.3, bundles it next to main.cjs (replaces the startup npm install -g), adds a structured-result executor with bundled/global/PATH resolution, a slack:cliStatus IPC probe, and a PATH shim so the Copilot skill keeps working.
* feat: surface Slack failures and add cross-OS auth fallbacks
Classify agent-slack errors (not_authed/rate_limited/network/bad_channel), persist per-source sync status with rate-limit backoff, and expose it via slack:knowledgeStatus. Fix the Settings Enable bounce-back with actionable copy, a browser-paste (parse-curl) fallback, and a Windows quit-Slack-and-import button; add home-feed empty/error states.
* feat: rank Slack home feed deterministically by recency
Drop the per-load LLM ranker (cost/latency/model dependency) in favor of a stronger deterministic filter + recency ordering. The filter now removes system messages, emoji/reaction-only posts, bare greetings/acks, and empty bodies, with a durable-signal escape hatch. Expand tests to one describe per noise class plus ordering/cap/volume coverage.
* fix: hide Slack knowledge Save button once saved
Only show the Save button when the channel list or enabled toggle differs from the last-persisted config, so it disappears after a successful save and reappears when a new channel is entered.
---------
Co-authored-by: Gagancreates <gaganp000999@gmail.com>
2026-06-18 01:22:27 +05:30
|
|
|
'slack:cliStatus': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
available: z.boolean(),
|
|
|
|
|
version: z.string().optional(),
|
|
|
|
|
source: z.enum(['bundled', 'global', 'path']).optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-03-10 16:26:51 +05:30
|
|
|
'slack:listWorkspaces': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
|
|
|
|
error: z.string().optional(),
|
feat: ship Slack as a knowledge source, hardened for production (#596)
* index slack and add to home page
* filter only useful slack messages in homr
* feat: bundle agent-slack CLI and route all calls through shared executor
Pins agent-slack@0.9.3, bundles it next to main.cjs (replaces the startup npm install -g), adds a structured-result executor with bundled/global/PATH resolution, a slack:cliStatus IPC probe, and a PATH shim so the Copilot skill keeps working.
* feat: surface Slack failures and add cross-OS auth fallbacks
Classify agent-slack errors (not_authed/rate_limited/network/bad_channel), persist per-source sync status with rate-limit backoff, and expose it via slack:knowledgeStatus. Fix the Settings Enable bounce-back with actionable copy, a browser-paste (parse-curl) fallback, and a Windows quit-Slack-and-import button; add home-feed empty/error states.
* feat: rank Slack home feed deterministically by recency
Drop the per-load LLM ranker (cost/latency/model dependency) in favor of a stronger deterministic filter + recency ordering. The filter now removes system messages, emoji/reaction-only posts, bare greetings/acks, and empty bodies, with a durable-signal escape hatch. Expand tests to one describe per noise class plus ordering/cap/volume coverage.
* fix: hide Slack knowledge Save button once saved
Only show the Save button when the channel list or enabled toggle differs from the last-persisted config, so it disappears after a successful save and reappears when a new channel is entered.
---------
Co-authored-by: Gagancreates <gaganp000999@gmail.com>
2026-06-18 01:22:27 +05:30
|
|
|
errorKind: SlackErrorKindSchema.optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:importDesktopAuth': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
errorKind: SlackErrorKindSchema.optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:quitAndImportDesktop': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
errorKind: SlackErrorKindSchema.optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:parseCurlAuth': {
|
|
|
|
|
req: z.object({ curl: z.string() }),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
errorKind: SlackErrorKindSchema.optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:knowledgeStatus': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
cli: z.object({
|
|
|
|
|
available: z.boolean(),
|
|
|
|
|
version: z.string().optional(),
|
|
|
|
|
source: z.enum(['bundled', 'global', 'path']).optional(),
|
|
|
|
|
}),
|
|
|
|
|
sources: z.array(z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
lastSyncAt: z.string().optional(),
|
|
|
|
|
lastStatus: z.enum(['ok', 'error']).optional(),
|
|
|
|
|
lastError: z.object({ kind: z.string(), message: z.string() }).optional(),
|
|
|
|
|
nextDueAt: z.string().optional(),
|
|
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:listChannels': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
workspaceUrl: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
channels: z.array(z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
isPrivate: z.boolean().optional(),
|
|
|
|
|
isMember: z.boolean().optional(),
|
|
|
|
|
})),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'slack:getRecentMessages': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
limit: z.number().int().positive().max(20).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
enabled: z.boolean(),
|
|
|
|
|
messages: z.array(z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
workspaceName: z.string().optional(),
|
|
|
|
|
workspaceUrl: z.string().optional(),
|
|
|
|
|
channelId: z.string().optional(),
|
|
|
|
|
channelName: z.string().optional(),
|
|
|
|
|
author: z.string().optional(),
|
|
|
|
|
text: z.string(),
|
|
|
|
|
ts: z.string(),
|
|
|
|
|
url: z.string().optional(),
|
|
|
|
|
})),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
errorKind: SlackErrorKindSchema.optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'knowledgeSources:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
sources: z.array(KnowledgeSourceConfigSchema),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'knowledgeSources:upsert': {
|
|
|
|
|
req: KnowledgeSourceConfigSchema,
|
|
|
|
|
res: z.object({
|
|
|
|
|
sources: z.array(KnowledgeSourceConfigSchema),
|
2026-03-10 16:26:51 +05:30
|
|
|
}),
|
|
|
|
|
},
|
2026-01-21 20:29:24 +05:30
|
|
|
'onboarding:getStatus': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
showOnboarding: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'onboarding:markComplete': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-02-02 21:34:47 +05:30
|
|
|
// Composio integration channels
|
|
|
|
|
'composio:is-configured': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
configured: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'composio:set-api-key': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
apiKey: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'composio:initiate-connection': {
|
2026-02-03 07:25:33 +05:30
|
|
|
req: z.object({
|
2026-02-02 21:34:47 +05:30
|
|
|
toolkitSlug: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
redirectUrl: z.string().optional(),
|
|
|
|
|
connectedAccountId: z.string().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'composio:get-connection-status': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
toolkitSlug: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
isConnected: z.boolean(),
|
|
|
|
|
status: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'composio:sync-connection': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
toolkitSlug: z.string(),
|
|
|
|
|
connectedAccountId: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
status: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'composio:disconnect': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
toolkitSlug: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'composio:list-connected': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
toolkits: z.array(z.string()),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-05-05 14:28:46 +05:30
|
|
|
'migration:check-composio-google': {
|
2026-03-02 17:52:23 +05:30
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
2026-05-05 14:28:46 +05:30
|
|
|
shouldShow: z.boolean(),
|
2026-03-17 12:04:57 +05:30
|
|
|
}),
|
|
|
|
|
},
|
2026-02-02 21:34:47 +05:30
|
|
|
'composio:didConnect': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
toolkitSlug: z.string(),
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
2026-02-03 07:25:33 +05:30
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
Feature/composio tools library (#461)
* first version of composio
* Enhance error handling in Composio tool execution. Added try-catch block to log errors and return a structured error response when executing tools fails.
* Add tool search functionality to settings dialog
Implemented a debounced search feature for tools within the toolkit, allowing users to filter tools based on a search query. Added state management for search results and loading status. Updated the UI to accommodate the search input and results display. Additionally, modified the API call to use 'query' instead of 'search' for consistency.
* Enhance Composio OAuth flow management and improve tool handling
- Updated the activeFlows management to prevent concurrent OAuth flows for the same toolkit by using toolkitSlug as the key.
- Implemented cleanup logic for existing flows, ensuring proper resource management by aborting and closing servers as needed.
- Introduced a timeout mechanism for abandoned flows, enhancing reliability.
- Refactored the Composio tools repository to use an in-memory cache for improved performance and added methods for persisting changes to disk.
- Updated the detailed tools listing to use a consistent API call structure and improved input parameter handling.
- Made connectionData in the response optional for better flexibility in handling connected accounts.
* Improve error handling in Composio API calls
- Enhanced error reporting by extracting human-readable messages from the JSON response body when the API call fails.
- Added logic to parse the response and include specific error details in the thrown error message, improving debugging and user feedback.
* Refactor Composio integration handling and improve UI components
- Updated composio-handler.ts to invalidate the copilot instructions cache upon connection and disconnection.
- Removed unused functions related to tool management in composio-handler.ts.
- Enhanced IPC handlers in ipc.ts to streamline Composio connection processes.
- Introduced ComposioConnectCard in the renderer to display connection status and handle events.
- Refactored tool rendering in App.tsx and chat-sidebar.tsx to utilize new tabbed content for parameters and results.
- Improved Composio tools prompt generation in instructions.ts to clarify integration usage and discovery flow.
- Cleaned up unused code and improved overall structure for better maintainability.
* Refactor listToolkits function to remove cursor parameter and implement pagination
- Updated listToolkits in composio-handler.ts to paginate through API results, collecting all curated toolkits.
- Adjusted IPC handler in ipc.ts to call the modified listToolkits without cursor argument.
- Made properties in ToolkitInfo optional in settings-dialog.tsx for improved flexibility.
- Removed the unused enabled-tools-repo.ts file to clean up the codebase.
* Refactor Composio toolkit management for improved structure and maintainability
- Consolidated toolkit definitions and display names into a single source of truth in shared/composio.ts.
- Updated core composio/curated-toolkits.ts to re-export types and constants for backward compatibility.
- Enhanced the organization of toolkit data, ensuring clarity and ease of access for future development.
* Refactor Composio integration and improve component structure
- Updated imports in composio-handler.ts and various components to utilize shared/composio.js for consistency.
- Simplified ComposioConnectCard by removing unnecessary state management and improving event handling.
- Enhanced chat-conversation.ts to directly reference COMPOSIO_DISPLAY_NAMES from shared/composio.js.
- Cleaned up unused functions and types in client.ts and types.ts for better maintainability.
- Removed deprecated curated-toolkits.ts file to streamline the codebase.
* Refactor Composio connection handling and improve tool display logic
- Removed the connection bridge for Composio toolkit OAuth, simplifying the connection process.
- Updated ComposioConnectCard to display a more user-friendly connection message.
- Introduced a new utility function, getToolDisplayName, to provide human-friendly names for builtin tools.
- Refactored App and ChatSidebar components to utilize the new getToolDisplayName function for improved clarity in tool titles.
- Cleaned up imports and removed unused code for better maintainability.
* remove from diff
* Address PR review: consolidate types, refactor CopilotAgent, sort toolkits
- Move ZSearchResultTool and ZNormalizedToolResult into composio/types.ts
- Convert CopilotAgent from static const to async buildCopilotAgent()
- Simplify loadAgent to delegate to buildCopilotAgent()
- Sort CURATED_TOOLKITS alphabetically by slug
- Remove inline type annotations in composio-handler, use inferred types
- Bump search limit from 15→50 for unscoped queries
- Add docstrings explaining inferToolkitSlug fallback behavior
- Add IPC schema reference comment for composio channels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Enhance Composio connection handling and improve rendering logic
- Added a 'hidden' property to ComposioConnectCardData to prevent rendering of duplicate connection cards.
- Updated App and ChatSidebar components to skip rendering if the card is marked as hidden.
- Refactored ComposioConnectCard to utilize a ref for callback firing, ensuring onConnected is only called once.
- Improved instructions for Composio integration to clarify usage and loading of the composio-integration skill.
This update streamlines the user experience by avoiding duplicate connection prompts and enhances the overall clarity of integration instructions.
* Address PR round 2: use query param, remove inferToolkitSlug, consolidate types
- Rename deprecated `search` param to `query` per Composio docs
- Remove inferToolkitSlug fallback; make toolkit required in ZSearchResultTool
- Replace inline Awaited<ReturnType<...>> with concrete Toolkit type in handler
- Move ZToolkitMeta/ZToolkitItem/ZListToolkitsResponse to shared/composio.ts
- Reference shared schemas in ipc.ts and core/types.ts (single source of truth)
- Remove unused ZTool import from client.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Toolkit type inference to composio/types.ts
- Introduced a new type `Toolkit` inferred from `ZToolkit` to enhance type safety and clarity in type definitions.
- This addition supports better integration and usage of the toolkit within the Composio framework.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:30:46 +05:30
|
|
|
// Composio Tools Library channels
|
|
|
|
|
'composio:list-toolkits': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: ZListToolkitsResponse,
|
|
|
|
|
},
|
2026-02-04 23:21:13 +05:30
|
|
|
// Agent schedule channels
|
|
|
|
|
'agent-schedule:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: AgentScheduleConfig,
|
|
|
|
|
},
|
|
|
|
|
'agent-schedule:getState': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: AgentScheduleState,
|
|
|
|
|
},
|
|
|
|
|
'agent-schedule:updateAgent': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
agentName: z.string(),
|
|
|
|
|
entry: AgentScheduleEntry,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'agent-schedule:deleteAgent': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
agentName: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-02-07 12:53:43 +05:30
|
|
|
// Shell integration channels
|
|
|
|
|
'shell:openPath': {
|
|
|
|
|
req: z.object({ path: z.string() }),
|
|
|
|
|
res: z.object({ error: z.string().optional() }),
|
|
|
|
|
},
|
2026-05-15 12:11:50 +05:30
|
|
|
'shell:showItemInFolder': {
|
|
|
|
|
req: z.object({ path: z.string() }),
|
|
|
|
|
res: z.object({ success: z.literal(true) }),
|
|
|
|
|
},
|
2026-02-07 12:53:43 +05:30
|
|
|
'shell:readFileBase64': {
|
|
|
|
|
req: z.object({ path: z.string() }),
|
|
|
|
|
res: z.object({ data: z.string(), mimeType: z.string(), size: z.number() }),
|
|
|
|
|
},
|
2026-05-06 23:14:00 +05:30
|
|
|
// Native dialog channels
|
|
|
|
|
'dialog:openDirectory': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
defaultPath: z.string().optional(),
|
|
|
|
|
title: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
path: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-06-12 23:45:38 +05:30
|
|
|
'dialog:openFiles': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
defaultPath: z.string().optional(),
|
|
|
|
|
title: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
paths: z.array(z.string()),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-02-27 20:22:54 +05:30
|
|
|
// Knowledge version history channels
|
|
|
|
|
'knowledge:history': {
|
|
|
|
|
req: z.object({ path: RelPath }),
|
|
|
|
|
res: z.object({
|
|
|
|
|
commits: z.array(z.object({
|
|
|
|
|
oid: z.string(),
|
|
|
|
|
message: z.string(),
|
|
|
|
|
timestamp: z.number(),
|
|
|
|
|
author: z.string(),
|
|
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'knowledge:fileAtCommit': {
|
|
|
|
|
req: z.object({ path: RelPath, oid: z.string() }),
|
|
|
|
|
res: z.object({ content: z.string() }),
|
|
|
|
|
},
|
|
|
|
|
'knowledge:restore': {
|
|
|
|
|
req: z.object({ path: RelPath, oid: z.string() }),
|
|
|
|
|
res: z.object({ ok: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'knowledge:didCommit': {
|
|
|
|
|
req: z.object({}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
Drive (#583)
* add drive sync up and down
* add drive button
* google doc icon
* icon changes
* show error state with retry in google doc picker
* feat(google-docs): import and sync down as Markdown, record remote revision
* feat(google-docs): structure-preserving sync up with remote-conflict guard
* feat(google-docs): overwrite-confirm on sync conflict and last-synced indicator
* feat(google-docs): store linked docs as .docx, edit in docx editor, sync via Drive
* feat(google-docs): offer BYOK connect in picker so signed-in users can grant Drive/Docs scopes
* fix(google-docs): request full drive scope so .docx sync-up can write back
* fix(google-docs): search all drives in doc picker, log result count
* feat(google-docs): import native Docs AND uploaded .docx files from Drive
* chore(google-docs): drop dev-only test file
* feat(google-docs): use Google Picker + drive.file scope instead of full-drive listing
* fix(google-oauth): request offline access so BYOK tokens refresh
BYOK never requested access_type=offline/prompt=consent so no refresh token was issued and tokens died after ~1h; also stop handing back expired tokens and extend the connect timeout to 10m.
* feat(google-docs): pick docs via system-browser Google Picker
Runs the Picker in the user's real browser (it 403s inside Electron), sets appId so the drive.file grant attaches to the picked file, and downloads + opens the selected doc.
* feat(google-docs): managed OAuth-redirect Picker (no API key, no BYOK) (#620)
* feat(google-docs): managed OAuth-redirect Picker (no API key, no BYOK)
Adds the managed (rowboat-mode) Google Docs picker via Google's trigger_onepick
flow. The Rowboat backend runs a standalone drive.file OAuth with the company
client, renders the Picker inside the browser consent screen, and deep-links the
selection back; the desktop downloads the picked doc with the fresh drive.file
token the backend returns. No Picker API key, appId, or BYOK credentials on the
desktop.
- core: importGoogleDocWithToken downloads a picked doc with an explicit token;
fetch/metadata helpers take an optional Drive client and share writeDocxAndLink.
claimPickedFilesViaBackend claims the parked file ids + token from the api.
- main: google-picker-managed.ts opens the backend start URL and resolves on the
rowboat://oauth/google/picker/done deep link; deeplink.ts routes that completion.
- ipc: google-docs:pickViaManaged.
- renderer: the picker dialog gates on Rowboat sign-in (the picker grants
drive.file per-file, so no pre-existing connection or scope is required).
Backend contract: rowboatlabs/rowboatx-backend#7
(GET /oauth/google/picker/{start,callback}, POST /v1/google-oauth/claim-picked).
* chore(google-docs): remove the dead API-key/system-browser Picker
The managed picker replaced the only consumer (the picker dialog), so the
experimental API-key Picker is now unused. Removes:
- main: google-docs:openPicker handler (system-browser loopback Picker)
- shared: google-docs:openPicker + google-docs:getAccessToken IPC schemas
- core: getGoogleAccessToken (token plumbing for the client-side Picker)
- renderer: lib/google-picker.ts (Picker JS SDK loader)
Kept GoogleClientIdModal / google-credentials-store — still used by the
general BYOK Google connect in onboarding, connectors, and settings.
---------
Co-authored-by: Gagancreates <gaganp000999@gmail.com>
2026-06-23 03:13:58 +05:30
|
|
|
// Google Docs linked knowledge files
|
|
|
|
|
'google-docs:getStatus': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
connected: z.boolean(),
|
|
|
|
|
hasRequiredScopes: z.boolean(),
|
|
|
|
|
missingScopes: z.array(z.string()),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'google-docs:import': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
fileId: z.string().min(1),
|
|
|
|
|
targetFolder: RelPath,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
doc: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
url: z.string(),
|
|
|
|
|
modifiedTime: z.string().nullable(),
|
|
|
|
|
owner: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// Managed OAuth-redirect Picker: the Rowboat backend runs the pick with the
|
|
|
|
|
// company Google client; the desktop opens the start URL, waits for the deep
|
|
|
|
|
// link, and imports with the existing managed token. No API key or BYOK creds.
|
|
|
|
|
'google-docs:pickViaManaged': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
targetFolder: RelPath,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
doc: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
url: z.string(),
|
|
|
|
|
modifiedTime: z.string().nullable(),
|
|
|
|
|
owner: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
}).nullable(),
|
|
|
|
|
},
|
|
|
|
|
'google-docs:refreshSnapshot': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.literal(true),
|
|
|
|
|
syncedAt: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'google-docs:sync': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
// Overwrite the Google Doc even if it changed remotely since last sync.
|
|
|
|
|
force: z.boolean().optional(),
|
|
|
|
|
// Legacy field from the markdown-link path; ignored by the .docx sync.
|
|
|
|
|
markdown: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
synced: z.boolean(),
|
|
|
|
|
syncedAt: z.string().optional(),
|
|
|
|
|
// True when a remote edit was detected and the push was held back.
|
|
|
|
|
conflict: z.boolean().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// Is this local .docx linked to a Google Doc? Drives the sync UI in the viewer.
|
|
|
|
|
'google-docs:getLink': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
link: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
url: z.string(),
|
|
|
|
|
title: z.string(),
|
|
|
|
|
syncedAt: z.string(),
|
|
|
|
|
remoteModifiedTime: z.string().optional(),
|
|
|
|
|
}).nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-02-18 18:08:24 +05:30
|
|
|
// Search channels
|
|
|
|
|
'search:query': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
query: z.string(),
|
|
|
|
|
limit: z.number().optional(),
|
|
|
|
|
types: z.array(z.enum(['knowledge', 'chat'])).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
results: z.array(z.object({
|
|
|
|
|
type: z.enum(['knowledge', 'chat']),
|
|
|
|
|
title: z.string(),
|
|
|
|
|
preview: z.string(),
|
|
|
|
|
path: z.string(),
|
|
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-03-13 10:26:08 +05:30
|
|
|
// Voice mode channels
|
|
|
|
|
'voice:getConfig': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
deepgram: z.object({ apiKey: z.string() }).nullable(),
|
|
|
|
|
elevenlabs: z.object({ apiKey: z.string(), voiceId: z.string().optional() }).nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'voice:synthesize': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
text: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
audioBase64: z.string(),
|
|
|
|
|
mimeType: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-07-04 12:51:06 +05:30
|
|
|
// Streaming TTS: main starts the synthesis and pushes audio chunks over
|
|
|
|
|
// 'voice:tts-chunk' as they arrive, so playback can begin on the first
|
|
|
|
|
// chunk instead of after the full body (~0.5-1s earlier first-audio).
|
|
|
|
|
'voice:synthesizeStreamStart': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
requestId: z.string(),
|
|
|
|
|
text: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'voice:synthesizeStreamCancel': {
|
|
|
|
|
req: z.object({ requestId: z.string() }),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
|
|
|
|
// Push channel: main → renderer with streaming TTS audio. `done: true`
|
|
|
|
|
// (possibly with a final chunk) ends the stream; `error` aborts it.
|
|
|
|
|
'voice:tts-chunk': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
requestId: z.string(),
|
|
|
|
|
chunkBase64: z.string().optional(),
|
|
|
|
|
done: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-06-11 02:07:18 +05:30
|
|
|
// Ensures the OS-level microphone permission is settled before capturing.
|
|
|
|
|
// On first-ever use (macOS) the permission is 'not-determined'; resolving
|
|
|
|
|
// the native prompt up front prevents the in-flight getUserMedia from
|
|
|
|
|
// rejecting on the first mic click.
|
|
|
|
|
'voice:ensureMicAccess': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
granted: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-07-03 22:29:10 +05:30
|
|
|
// Same as ensureMicAccess but for the camera — settles the macOS TCC
|
|
|
|
|
// permission before video mode calls getUserMedia({ video: true }).
|
|
|
|
|
'voice:ensureCameraAccess': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
granted: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-07-04 00:25:55 +05:30
|
|
|
// Video-mode popout: show/hide the small always-on-top window (user +
|
|
|
|
|
// mascot tiles) that floats over everything for the duration of a screen
|
|
|
|
|
// share, Meet-style.
|
2026-07-04 00:09:46 +05:30
|
|
|
'video:setPopout': {
|
|
|
|
|
req: z.object({ show: z.boolean() }),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
|
|
|
|
// Main-window renderer pushes the current call state; the main process
|
|
|
|
|
// caches it and relays to the popout window (replayed on popout load).
|
|
|
|
|
'video:popoutState': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
ttsState: z.enum(['idle', 'synthesizing', 'speaking']),
|
|
|
|
|
status: z.enum(['listening', 'thinking', 'speaking']).nullable(),
|
|
|
|
|
cameraOn: z.boolean(),
|
2026-07-04 12:06:32 +05:30
|
|
|
screenSharing: z.boolean(),
|
|
|
|
|
// Live transcript of the in-progress utterance.
|
2026-07-04 00:25:55 +05:30
|
|
|
interimText: z.string().nullable(),
|
2026-07-04 00:09:46 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
2026-07-04 12:06:32 +05:30
|
|
|
// Popout window → fetch the latest cached call state on mount. The
|
|
|
|
|
// did-finish-load replay can race the React listener registration, and the
|
|
|
|
|
// popout must never guess (a wrong camera-on default flashes the user's
|
|
|
|
|
// video before the first state push corrects it).
|
|
|
|
|
'video:getPopoutState': {
|
2026-07-04 00:09:46 +05:30
|
|
|
req: z.null(),
|
2026-07-04 12:06:32 +05:30
|
|
|
res: z.object({
|
|
|
|
|
state: z
|
|
|
|
|
.object({
|
|
|
|
|
ttsState: z.enum(['idle', 'synthesizing', 'speaking']),
|
|
|
|
|
status: z.enum(['listening', 'thinking', 'speaking']).nullable(),
|
|
|
|
|
cameraOn: z.boolean(),
|
|
|
|
|
screenSharing: z.boolean(),
|
|
|
|
|
interimText: z.string().nullable(),
|
|
|
|
|
})
|
|
|
|
|
.nullable(),
|
|
|
|
|
}),
|
2026-07-04 00:09:46 +05:30
|
|
|
},
|
2026-07-04 00:25:55 +05:30
|
|
|
// Popout control bar → main process → relayed to the app window, which
|
2026-07-04 12:06:32 +05:30
|
|
|
// executes the action on the live call. 'expand' additionally focuses the
|
|
|
|
|
// main app window (handled in the main process).
|
2026-07-04 00:25:55 +05:30
|
|
|
'video:popoutAction': {
|
|
|
|
|
req: z.object({
|
2026-07-04 13:24:32 +05:30
|
|
|
action: z.enum(['toggle-camera', 'toggle-share', 'stop-speaking', 'end-call', 'expand']),
|
2026-07-04 00:25:55 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({}),
|
|
|
|
|
},
|
2026-07-04 00:09:46 +05:30
|
|
|
// Push channel: main → popout window with the latest call state.
|
|
|
|
|
'video:popout-state': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
ttsState: z.enum(['idle', 'synthesizing', 'speaking']),
|
|
|
|
|
status: z.enum(['listening', 'thinking', 'speaking']).nullable(),
|
|
|
|
|
cameraOn: z.boolean(),
|
2026-07-04 12:06:32 +05:30
|
|
|
screenSharing: z.boolean(),
|
2026-07-04 00:25:55 +05:30
|
|
|
interimText: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
|
|
|
|
// Push channel: main → app window with a popout control-bar action.
|
|
|
|
|
'video:popout-action': {
|
|
|
|
|
req: z.object({
|
2026-07-04 13:24:32 +05:30
|
|
|
action: z.enum(['toggle-camera', 'toggle-share', 'stop-speaking', 'end-call', 'expand']),
|
2026-07-04 00:09:46 +05:30
|
|
|
}),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-03-30 22:31:49 +05:30
|
|
|
'meeting:checkScreenPermission': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
granted: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'meeting:openScreenRecordingSettings': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({ success: z.boolean() }),
|
|
|
|
|
},
|
2026-03-19 22:12:42 +05:30
|
|
|
'meeting:summarize': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
transcript: z.string(),
|
|
|
|
|
meetingStartTime: z.string().optional(),
|
2026-03-20 15:03:34 +05:30
|
|
|
calendarEventJson: z.string().optional(),
|
2026-03-19 22:12:42 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
notes: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
Meeting prep: attendee notes + proactive 6h-ahead prep briefs (#636)
* feat(meetings): inline meeting prep under next meeting
Resolve a meeting's attendees against the knowledge base and render each
attendee's existing person.md inline beneath the next upcoming meeting in
the Meetings view. Deterministic (email-exact match, then unambiguous
name/alias), no LLM in the hot path.
- core: resolveMeetingPrep() + meeting-prep:resolve IPC
- renderer: notes-first rows with expandable person.md; unmatched attendees
collapse into a 'no notes yet' row with a Create note action that hands
off to the Copilot
- prep re-resolves when a People note changes
* feat(meetings): per-meeting prep toggle
Add a Prep toggle to every eligible meeting row, not just the next one.
The next meeting still auto-expands; other meetings resolve their attendee
notes lazily when their toggle is opened. All-day and solo events get no
toggle.
* feat(meetings): org matching, cached index, and field-bleed fix
#2 Matching: resolve the meeting's external companies from attendee email
domains (and matched people's Organization field), excluding the user's own
domain, and surface those org notes in a Companies section. Normalize display
names (strip '(via ...)' suffixes) before name/alias matching.
#3 Perf: meeting prep reads a cached knowledge index instead of rescanning the
whole knowledge dir on every resolve; invalidated whenever a file under
knowledge/ changes (wired to the workspace watcher).
Fix: extractField in the knowledge index consumed newlines after the label, so
an empty field (e.g. **Role:**) bled the next line's value. Restrict it to
spaces/tabs so empty fields resolve to undefined. Also strip the folder prefix
from link targets for display (Organizations/Rowboat Labs -> Rowboat Labs).
* feat(meetings): proactive prep notes generated 6h ahead
Generate a meeting prep note ~6h before each meeting and surface its brief
in the prep card.
- Generator (meeting_prep_brief.ts): assembles roster + 'last time' recap
(extracts the prior instance's Action items) + agenda, ordered by meeting
type (recurring -> recap first, one-off -> agenda first), then one model
call for a 'what matters' brief. Writes knowledge/Meetings/prep/<slug>-<date>.md
with eventId/recurringEventId frontmatter. Reuses the summarizeMeeting LLM
path (configured model, useCase: meeting_prep).
- Scheduler (meeting_prep_scheduler.ts): calendar-aware tick (5m), generates
prep within the 6h lead window, state file dedupes, self-heals on changes.
- Card: shows the brief (bulleted, compact) above a People list whose rows
link out to each person's note instead of rendering it inline. Generated
prep notes are kept out of the past-notes table.
* chore(meetings): poll prep scheduler every 15m instead of 5m
2026-07-02 00:35:35 +05:30
|
|
|
// Resolve a meeting's attendees against the knowledge base — returns each
|
|
|
|
|
// attendee's existing person note (or null). Deterministic, no LLM; powers
|
|
|
|
|
// the ambient "Next up" prep card.
|
|
|
|
|
'meeting-prep:resolve': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
attendees: z.array(z.object({
|
|
|
|
|
email: z.string().optional(),
|
|
|
|
|
displayName: z.string().optional(),
|
|
|
|
|
self: z.boolean().optional(),
|
|
|
|
|
})),
|
|
|
|
|
// When provided, the response includes any pre-generated prep note for
|
|
|
|
|
// this calendar event (matched by the eventId stamped in frontmatter).
|
|
|
|
|
eventId: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
attendees: z.array(z.object({
|
|
|
|
|
label: z.string(),
|
|
|
|
|
email: z.string().optional(),
|
|
|
|
|
displayName: z.string().optional(),
|
|
|
|
|
note: z.object({
|
|
|
|
|
path: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
role: z.string().optional(),
|
|
|
|
|
organization: z.string().optional(),
|
|
|
|
|
markdown: z.string(),
|
|
|
|
|
}).nullable(),
|
|
|
|
|
})),
|
|
|
|
|
organizations: z.array(z.object({
|
|
|
|
|
path: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
markdown: z.string(),
|
|
|
|
|
})),
|
|
|
|
|
// The pre-generated prep note (brief + path), if one exists for eventId.
|
|
|
|
|
prepNote: z.object({
|
|
|
|
|
path: z.string(),
|
|
|
|
|
brief: z.string(),
|
|
|
|
|
}).nullable(),
|
|
|
|
|
matchedCount: z.number().int().nonnegative(),
|
|
|
|
|
unmatchedCount: z.number().int().nonnegative(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-03-04 22:15:15 +05:30
|
|
|
// Inline task schedule classification
|
2026-03-14 21:29:53 +05:30
|
|
|
'export:note': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
markdown: z.string(),
|
|
|
|
|
format: z.enum(['md', 'pdf', 'docx']),
|
|
|
|
|
title: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-03-04 22:15:15 +05:30
|
|
|
'inline-task:classifySchedule': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
instruction: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
schedule: z.union([
|
|
|
|
|
z.object({ type: z.literal('cron'), expression: z.string(), startDate: z.string(), endDate: z.string(), label: z.string() }),
|
|
|
|
|
z.object({ type: z.literal('window'), cron: z.string(), startTime: z.string(), endTime: z.string(), startDate: z.string(), endDate: z.string(), label: z.string() }),
|
|
|
|
|
z.object({ type: z.literal('once'), runAt: z.string(), label: z.string() }),
|
|
|
|
|
]).nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-03-19 01:34:10 +05:30
|
|
|
'inline-task:process': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
instruction: z.string(),
|
|
|
|
|
noteContent: z.string(),
|
|
|
|
|
notePath: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
instruction: z.string(),
|
|
|
|
|
schedule: z.union([
|
|
|
|
|
z.object({ type: z.literal('cron'), expression: z.string(), startDate: z.string(), endDate: z.string() }),
|
|
|
|
|
z.object({ type: z.literal('window'), cron: z.string(), startTime: z.string(), endTime: z.string(), startDate: z.string(), endDate: z.string() }),
|
|
|
|
|
z.object({ type: z.literal('once'), runAt: z.string() }),
|
|
|
|
|
]).nullable(),
|
|
|
|
|
scheduleLabel: z.string().nullable(),
|
|
|
|
|
response: z.string().nullable(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
// Live-note channels
|
|
|
|
|
'live-note:run': {
|
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
|
|
|
req: z.object({
|
|
|
|
|
filePath: z.string(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
context: z.string().optional(),
|
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
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
runId: z.string().nullable().optional(),
|
|
|
|
|
action: z.enum(['replace', 'no_update']).optional(),
|
|
|
|
|
summary: z.string().nullable().optional(),
|
|
|
|
|
contentAfter: z.string().nullable().optional(),
|
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
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note:get': {
|
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
|
|
|
req: z.object({
|
|
|
|
|
filePath: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
// Fresh, authoritative live-note object from frontmatter, or null when
|
|
|
|
|
// the note is passive. Renderer should use this for display/edit —
|
|
|
|
|
// never a stale cached copy.
|
|
|
|
|
live: LiveNoteSchema.nullable().optional(),
|
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
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note:set': {
|
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
|
|
|
req: z.object({
|
|
|
|
|
filePath: z.string(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
live: LiveNoteSchema,
|
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
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
live: LiveNoteSchema.nullable().optional(),
|
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
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note:setActive': {
|
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
|
|
|
req: z.object({
|
|
|
|
|
filePath: z.string(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
active: z.boolean(),
|
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
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
live: LiveNoteSchema.nullable().optional(),
|
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
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note:delete': {
|
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
|
|
|
req: z.object({
|
|
|
|
|
filePath: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note:stop': {
|
2026-05-06 11:59:37 +05:30
|
|
|
req: z.object({
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
filePath: z.string(),
|
2026-05-06 11:59:37 +05:30
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
'live-note:listNotes': {
|
2026-05-06 11:59:37 +05:30
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({
|
|
|
|
|
notes: z.array(z.object({
|
|
|
|
|
path: RelPath,
|
|
|
|
|
createdAt: z.string().nullable(),
|
|
|
|
|
lastRunAt: z.string().nullable(),
|
|
|
|
|
isActive: z.boolean(),
|
feat: live notes — single objective per note replaces multi-track model
Folds the multi-`track:`-array model into one `live:` block per note: a single
persistent objective the live-note agent maintains, plus an optional triggers
object (`cronExpr` / `windows` / `eventMatchCriteria`, each independently
optional). A note is now passive or live — no per-track scopes, no section
ownership contract, no `once` trigger. The agent owns the whole body and makes
patch-style incremental edits per run.
Highlights:
- Schema: `track:` array → single `live:` object (`packages/shared/src/live-note.ts`).
- Runtime: scheduler / event processor / runner under `core/knowledge/live-note/`,
with split `lastAttemptAt` (every run, drives 5-min backoff) vs `lastRunAt`
(success only, anchors cycles). `throwOnError` on agent runs surfaces LLM /
billing failures into `lastRunError`.
- Today.md: regenerated by template v2 (single objective covering overview /
calendar / emails / what-you-missed / priorities; existing files renamed to
`Today.md.bkp.<stamp>`).
- Renderer: `LiveNoteSidebar` mounts inside the editor row (no chat overlap,
auto-closes on note switch); toolbar Radio button becomes a status pill;
`LiveNotesView` replaces background-agents view.
- Copilot: new `live-note` skill with act-first stance, default folder/cadence
pickers, and a non-negotiable rule to extend an existing objective rather
than add a second one. Shared `KNOWLEDGE_NOTE_STYLE_GUIDE` enforces
terse-and-scannable writing across `doc-collab` and the live-note agent.
- Analytics: `track_block` use-case → `live_note_agent`; trigger
(`manual` / `cron` / `window` / `event`) becomes the Pass-2 sub-use-case,
alongside `routing` for Pass 1. Legacy run files with the old value are
read-mapped via `LegacyStartEvent` so they stay openable in the runs list.
Hard cutover — no back-compat shims for legacy `track:` frontmatter arrays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 00:26:46 +05:30
|
|
|
objective: z.string(),
|
2026-05-06 11:59:37 +05:30
|
|
|
})),
|
|
|
|
|
}),
|
|
|
|
|
},
|
feat: background tasks
Adds Background Tasks — recurring background agents the user can set up to
either keep a digest current (daily email summary, top HN stories, weather
brief) or perform a recurring action (draft a reply, post to Slack, call an
API). Each task is a persistent set of instructions plus optional triggers
(schedule, time-of-day window, or matching incoming Gmail / calendar event).
The agent reads the verbs in the instructions on every run and picks the
right mode automatically.
User-facing surfaces:
- New "Background tasks" entry in the sidebar, with a table listing every
task, its schedule, last run, and an active toggle.
- A detail page per task with a max-width reader showing the task's
current output and a control sidebar for editing instructions, triggers,
and reviewing run history.
- "New task" can open in a free-form box where the user describes what they
want and Copilot sets it up end-to-end, or in a structured form for
manual setup.
- "Edit with Copilot" hand-off from the detail view, pre-seeded with the
task's context.
Under the hood:
- The event pipeline that previously powered live-notes is now a generic
consumer registry. Live-notes and background tasks both subscribe;
incoming events are routed to candidates from both concurrently.
- Schedule helpers and the agent-message trigger block are factored out of
live-notes into shared modules. Both features use the same building
blocks now.
- Copilot's proactive routing is reframed: anything recurring (cadence
words, watch / monitor verbs, action verbs, event-conditional asks) now
flows to background tasks. Live-notes load only on explicit mention.
- A small reliability fix for the run-creation fallback chain: an
empty-string model/provider passed by an LLM tool call now correctly
falls through to the default instead of being persisted as a real value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:43:25 +05:30
|
|
|
// Background-task channels
|
|
|
|
|
'bg-task:run': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
context: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
runId: z.string().nullable().optional(),
|
|
|
|
|
summary: z.string().nullable().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'bg-task:get': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
task: BackgroundTaskSchema.nullable().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'bg-task:patch': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
partial: BackgroundTaskSchema.partial(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
task: BackgroundTaskSchema.nullable().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'bg-task:create': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
name: z.string(),
|
|
|
|
|
instructions: z.string(),
|
|
|
|
|
triggers: TriggersSchema.optional(),
|
feat(bg-tasks): coding-from-meetings — auto-implement coding action items (#630)
* feat(bg-tasks): coding-from-meetings — auto-implement coding action items
A background-task flavor that watches for meeting notes, scans them for
actionable coding items, and autonomously implements them in isolated git
worktrees, summarizing results in the task's index.md.
- Emit `meeting.notes_ready` when Fireflies/Granola first write a meeting note
- Add optional `projectId` to BackgroundTask (pins a coding task to a repo)
- New `launch-code-task` builtin tool: per group of items, create a
worktree-isolated, yolo, direct code session, wrap the prompt in an
autonomous scaffold, run async, and finalize a per-session row in index.md
- Group code sessions under their meeting heading in index.md
- Summary from the code agent's `## Summary` section; file counts from
`git diff` vs the worktree fork point (counts committed work, not just dirty)
- Guardrails: self-heal projectId across runs, cap launches per run, and bar
the bg-task agent from managing/spawning tasks
- UI: "View available templates" -> Coding-from-meetings preset (repo picker,
prefilled trigger + instructions)
See plan.md for the full design.
* let the copilot able to configure a coding background agent
* Delete plan.md
---------
Co-authored-by: Arjun <6592213+arkml@users.noreply.github.com>
2026-06-18 22:56:43 -07:00
|
|
|
projectId: z.string().optional(),
|
feat: background tasks
Adds Background Tasks — recurring background agents the user can set up to
either keep a digest current (daily email summary, top HN stories, weather
brief) or perform a recurring action (draft a reply, post to Slack, call an
API). Each task is a persistent set of instructions plus optional triggers
(schedule, time-of-day window, or matching incoming Gmail / calendar event).
The agent reads the verbs in the instructions on every run and picks the
right mode automatically.
User-facing surfaces:
- New "Background tasks" entry in the sidebar, with a table listing every
task, its schedule, last run, and an active toggle.
- A detail page per task with a max-width reader showing the task's
current output and a control sidebar for editing instructions, triggers,
and reviewing run history.
- "New task" can open in a free-form box where the user describes what they
want and Copilot sets it up end-to-end, or in a structured form for
manual setup.
- "Edit with Copilot" hand-off from the detail view, pre-seeded with the
task's context.
Under the hood:
- The event pipeline that previously powered live-notes is now a generic
consumer registry. Live-notes and background tasks both subscribe;
incoming events are routed to candidates from both concurrently.
- Schedule helpers and the agent-message trigger block are factored out of
live-notes into shared modules. Both features use the same building
blocks now.
- Copilot's proactive routing is reframed: anything recurring (cadence
words, watch / monitor verbs, action verbs, event-conditional asks) now
flows to background tasks. Live-notes load only on explicit mention.
- A small reliability fix for the run-creation fallback chain: an
empty-string model/provider passed by an LLM tool call now correctly
falls through to the default instead of being persisted as a real value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:43:25 +05:30
|
|
|
model: z.string().optional(),
|
|
|
|
|
provider: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
slug: z.string().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'bg-task:delete': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'bg-task:stop': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'bg-task:list': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
offset: z.number().int().nonnegative().optional(),
|
|
|
|
|
limit: z.number().int().positive().optional(),
|
|
|
|
|
sort: z.enum(['createdAt:desc', 'createdAt:asc', 'name:asc']).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
items: z.array(BackgroundTaskSummarySchema),
|
|
|
|
|
total: z.number().int().nonnegative(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// Returns the runIds recorded in `bg-tasks/<slug>/runs.log` (newest first).
|
|
|
|
|
// The renderer turns each id into a full Run via the existing `runs:fetch`
|
|
|
|
|
// channel — bg-task transcripts now live at the global $WorkDir/runs/.
|
|
|
|
|
'bg-task:listRunIds': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
limit: z.number().int().positive().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
runIds: z.array(z.string()),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2026-04-15 13:21:09 +05:30
|
|
|
// Embedded browser (WebContentsView) channels
|
|
|
|
|
'browser:setBounds': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
x: z.number().int(),
|
|
|
|
|
y: z.number().int(),
|
|
|
|
|
width: z.number().int().nonnegative(),
|
|
|
|
|
height: z.number().int().nonnegative(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ ok: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'browser:setVisible': {
|
|
|
|
|
req: z.object({ visible: z.boolean() }),
|
|
|
|
|
res: z.object({ ok: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'browser:newTab': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
url: z.string().min(1).refine(
|
|
|
|
|
(u) => {
|
|
|
|
|
const lower = u.trim().toLowerCase();
|
|
|
|
|
if (lower.startsWith('javascript:')) return false;
|
|
|
|
|
if (lower.startsWith('file://')) return false;
|
|
|
|
|
if (lower.startsWith('chrome://')) return false;
|
|
|
|
|
if (lower.startsWith('chrome-extension://')) return false;
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
{ message: 'Unsafe URL scheme' },
|
|
|
|
|
).optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
tabId: z.string().optional(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'browser:switchTab': {
|
|
|
|
|
req: z.object({ tabId: z.string().min(1) }),
|
|
|
|
|
res: z.object({ ok: z.boolean() }),
|
|
|
|
|
},
|
|
|
|
|
'browser:closeTab': {
|
|
|
|
|
req: z.object({ tabId: z.string().min(1) }),
|
|
|
|
|
res: z.object({ ok: z.boolean() }),
|
|
|
|
|
},
|
|
|
|
|
'browser:navigate': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
url: z.string().min(1).refine(
|
|
|
|
|
(u) => {
|
|
|
|
|
const lower = u.trim().toLowerCase();
|
|
|
|
|
if (lower.startsWith('javascript:')) return false;
|
|
|
|
|
if (lower.startsWith('file://')) return false;
|
|
|
|
|
if (lower.startsWith('chrome://')) return false;
|
|
|
|
|
if (lower.startsWith('chrome-extension://')) return false;
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
{ message: 'Unsafe URL scheme' },
|
|
|
|
|
),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
error: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'browser:back': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({ ok: z.boolean() }),
|
|
|
|
|
},
|
|
|
|
|
'browser:forward': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({ ok: z.boolean() }),
|
|
|
|
|
},
|
|
|
|
|
'browser:reload': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: z.object({ ok: z.literal(true) }),
|
|
|
|
|
},
|
|
|
|
|
'browser:getState': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: BrowserStateSchema,
|
|
|
|
|
},
|
|
|
|
|
'browser:didUpdateState': {
|
|
|
|
|
req: BrowserStateSchema,
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
2026-07-02 19:11:53 +05:30
|
|
|
// HTTP basic/proxy auth challenge from a page in the embedded browser
|
|
|
|
|
// (main → renderer push). The renderer shows a credential prompt and
|
|
|
|
|
// answers via browser:httpAuthResponse.
|
|
|
|
|
'browser:httpAuthRequest': {
|
|
|
|
|
req: HttpAuthRequestSchema,
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
|
|
|
|
// Main → renderer: a pending auth challenge was resolved without the
|
|
|
|
|
// renderer answering (timed out, or its tab/window was destroyed), so the
|
|
|
|
|
// renderer must drop the corresponding dialog from its queue.
|
|
|
|
|
'browser:httpAuthResolved': {
|
|
|
|
|
req: z.object({ requestId: z.string() }),
|
|
|
|
|
res: z.null(),
|
|
|
|
|
},
|
|
|
|
|
// Renderer → main. Omit username to cancel the challenge; provide it (even
|
|
|
|
|
// empty, for token-style auth) to submit credentials.
|
|
|
|
|
'browser:httpAuthResponse': {
|
|
|
|
|
req: z.object({
|
|
|
|
|
requestId: z.string(),
|
|
|
|
|
username: z.string().optional(),
|
|
|
|
|
password: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
res: z.object({ ok: z.boolean() }),
|
|
|
|
|
},
|
2026-03-17 12:04:57 +05:30
|
|
|
// Billing channels
|
|
|
|
|
'billing:getInfo': {
|
|
|
|
|
req: z.null(),
|
2026-05-18 11:12:10 +05:30
|
|
|
res: BillingInfoSchema,
|
2026-03-17 12:04:57 +05:30
|
|
|
},
|
2026-06-11 19:43:26 +05:30
|
|
|
// Notification settings channels
|
|
|
|
|
'notifications:getSettings': {
|
|
|
|
|
req: z.null(),
|
|
|
|
|
res: NotificationSettingsSchema,
|
|
|
|
|
},
|
|
|
|
|
'notifications:setSettings': {
|
|
|
|
|
req: NotificationSettingsSchema,
|
|
|
|
|
res: z.object({
|
|
|
|
|
success: z.literal(true),
|
|
|
|
|
}),
|
|
|
|
|
},
|
2025-12-29 15:30:57 +05:30
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Type Helpers
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export type IPCChannels = {
|
|
|
|
|
[K in keyof typeof ipcSchemas]: {
|
|
|
|
|
req: z.infer<typeof ipcSchemas[K]['req']>;
|
|
|
|
|
res: z.infer<typeof ipcSchemas[K]['res']>;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Channels that use invoke/handle (request/response pattern)
|
|
|
|
|
* These are channels with non-null responses
|
|
|
|
|
*/
|
|
|
|
|
export type InvokeChannels = {
|
|
|
|
|
[K in keyof IPCChannels]:
|
|
|
|
|
IPCChannels[K]['res'] extends null ? never : K
|
|
|
|
|
}[keyof IPCChannels];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Channels that use send/on (fire-and-forget pattern)
|
|
|
|
|
* These are channels with null responses (no response expected)
|
|
|
|
|
*/
|
|
|
|
|
export type SendChannels = {
|
|
|
|
|
[K in keyof IPCChannels]:
|
|
|
|
|
IPCChannels[K]['res'] extends null ? K : never
|
|
|
|
|
}[keyof IPCChannels];
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Type Guards
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export function validateRequest<K extends keyof IPCChannels>(
|
|
|
|
|
channel: K,
|
|
|
|
|
data: unknown
|
|
|
|
|
): IPCChannels[K]['req'] {
|
|
|
|
|
const schema = ipcSchemas[channel].req;
|
|
|
|
|
return schema.parse(data) as IPCChannels[K]['req'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function validateResponse<K extends keyof IPCChannels>(
|
|
|
|
|
channel: K,
|
|
|
|
|
data: unknown
|
|
|
|
|
): IPCChannels[K]['res'] {
|
|
|
|
|
const schema = ipcSchemas[channel].res;
|
|
|
|
|
return schema.parse(data) as IPCChannels[K]['res'];
|
2026-02-04 01:12:06 +05:30
|
|
|
}
|