rowboat/apps/x/packages/shared/src/service-events.ts
arkml 79162ebc69
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-17 12:52:27 -07:00

69 lines
1.9 KiB
TypeScript

import z from 'zod';
export const ServiceName = z.enum([
'graph',
'gmail',
'calendar',
'fireflies',
'granola',
'slack',
'voice_memo',
'email_labeling',
'note_tagging',
'agent_notes',
]);
const ServiceEventBase = z.object({
service: ServiceName,
runId: z.string(),
ts: z.iso.datetime(),
level: z.enum(['info', 'warn', 'error']),
message: z.string(),
});
export const ServiceRunStartEvent = ServiceEventBase.extend({
type: z.literal('run_start'),
trigger: z.enum(['timer', 'manual', 'startup']).optional(),
config: z.record(z.string(), z.unknown()).optional(),
});
export const ServiceChangesIdentifiedEvent = ServiceEventBase.extend({
type: z.literal('changes_identified'),
counts: z.record(z.string(), z.number()).optional(),
items: z.array(z.string()).optional(),
truncated: z.boolean().optional(),
});
export const ServiceProgressEvent = ServiceEventBase.extend({
type: z.literal('progress'),
step: z.string().optional(),
current: z.number().optional(),
total: z.number().optional(),
details: z.record(z.string(), z.unknown()).optional(),
});
export const ServiceRunCompleteEvent = ServiceEventBase.extend({
type: z.literal('run_complete'),
durationMs: z.number(),
outcome: z.enum(['ok', 'idle', 'skipped', 'error']),
summary: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
items: z.array(z.string()).optional(),
truncated: z.boolean().optional(),
});
export const ServiceErrorEvent = ServiceEventBase.extend({
type: z.literal('error'),
error: z.string(),
context: z.record(z.string(), z.unknown()).optional(),
});
export const ServiceEvent = z.union([
ServiceRunStartEvent,
ServiceChangesIdentifiedEvent,
ServiceProgressEvent,
ServiceRunCompleteEvent,
ServiceErrorEvent,
]);
export type ServiceNameType = z.infer<typeof ServiceName>;
export type ServiceEventType = z.infer<typeof ServiceEvent>;