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';
|
2026-02-26 21:41:17 +05:30
|
|
|
import { UserMessageContent } from './message.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-04-15 13:21:09 +05:30
|
|
|
import { BrowserStateSchema } 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';
|
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(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
'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-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': {
|
|
|
|
|
req: z.object({ threadId: z.string().min(1) }),
|
|
|
|
|
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-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
|
|
|
},
|
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(),
|
|
|
|
|
},
|
|
|
|
|
'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-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(),
|
|
|
|
|
},
|
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-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-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(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
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-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
|
|
|
}
|