mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-24 20:28:16 +02:00
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>
This commit is contained in:
parent
2ddec07712
commit
79162ebc69
21 changed files with 2979 additions and 66 deletions
|
|
@ -27,6 +27,33 @@ import { CodeProject, CodeSession, CodeSessionMode, CodeSessionStatus, GitRepoIn
|
|||
// Runtime Validation Schemas (Single Source of Truth)
|
||||
// ============================================================================
|
||||
|
||||
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(),
|
||||
});
|
||||
|
||||
const ipcSchemas = {
|
||||
'app:getVersions': {
|
||||
req: z.null(),
|
||||
|
|
@ -733,11 +760,112 @@ const ipcSchemas = {
|
|||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
'slack:cliStatus': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
available: z.boolean(),
|
||||
version: z.string().optional(),
|
||||
source: z.enum(['bundled', 'global', 'path']).optional(),
|
||||
}),
|
||||
},
|
||||
'slack:listWorkspaces': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
error: z.string().optional(),
|
||||
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),
|
||||
}),
|
||||
},
|
||||
'onboarding:getStatus': {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue