mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
feat: Implement Skills Manager View and Notifier Module
- Added `skills-manager.js` for managing Claude Code skills with functionalities for loading, displaying, and editing skills. - Introduced a Notifier module in `notifier.ts` for CLI to server communication, enabling notifications for UI updates on data changes. - Created comprehensive documentation for the Chain Search implementation, including usage examples and performance tips. - Developed a test suite for the Chain Search engine, covering basic search, quick search, symbol search, and files-only search functionalities.
This commit is contained in:
@@ -63,6 +63,7 @@ const ParamsSchema = z.object({
|
||||
id: z.string().optional(), // Custom execution ID (e.g., IMPL-001-step1)
|
||||
noNative: z.boolean().optional(), // Force prompt concatenation instead of native resume
|
||||
category: z.enum(['user', 'internal', 'insight']).default('user'), // Execution category for tracking
|
||||
parentExecutionId: z.string().optional(), // Parent execution ID for fork/retry scenarios
|
||||
});
|
||||
|
||||
// Execution category types
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface ConversationRecord {
|
||||
turn_count: number;
|
||||
latest_status: 'success' | 'error' | 'timeout';
|
||||
turns: ConversationTurn[];
|
||||
parent_execution_id?: string; // For fork/retry scenarios
|
||||
}
|
||||
|
||||
export interface HistoryQueryOptions {
|
||||
@@ -74,6 +75,20 @@ export interface NativeSessionMapping {
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// Review record interface
|
||||
export type ReviewStatus = 'pending' | 'approved' | 'rejected' | 'changes_requested';
|
||||
|
||||
export interface ReviewRecord {
|
||||
id?: number;
|
||||
execution_id: string;
|
||||
status: ReviewStatus;
|
||||
rating?: number;
|
||||
comments?: string;
|
||||
reviewer?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI History Store using SQLite
|
||||
*/
|
||||
@@ -113,7 +128,9 @@ export class CliHistoryStore {
|
||||
total_duration_ms INTEGER DEFAULT 0,
|
||||
turn_count INTEGER DEFAULT 0,
|
||||
latest_status TEXT DEFAULT 'success',
|
||||
prompt_preview TEXT
|
||||
prompt_preview TEXT,
|
||||
parent_execution_id TEXT,
|
||||
FOREIGN KEY (parent_execution_id) REFERENCES conversations(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Turns table (individual conversation turns)
|
||||
@@ -193,6 +210,23 @@ export class CliHistoryStore {
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_insights_created ON insights(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_insights_tool ON insights(tool);
|
||||
|
||||
-- Reviews table for CLI execution reviews
|
||||
CREATE TABLE IF NOT EXISTS reviews (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
execution_id TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
rating INTEGER,
|
||||
comments TEXT,
|
||||
reviewer TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY (execution_id) REFERENCES conversations(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reviews_execution ON reviews(execution_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_reviews_status ON reviews(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_reviews_created ON reviews(created_at DESC);
|
||||
`);
|
||||
|
||||
// Migration: Add category column if not exists (for existing databases)
|
||||
@@ -207,6 +241,7 @@ export class CliHistoryStore {
|
||||
// Check if category column exists
|
||||
const tableInfo = this.db.prepare('PRAGMA table_info(conversations)').all() as Array<{ name: string }>;
|
||||
const hasCategory = tableInfo.some(col => col.name === 'category');
|
||||
const hasParentExecutionId = tableInfo.some(col => col.name === 'parent_execution_id');
|
||||
|
||||
if (!hasCategory) {
|
||||
console.log('[CLI History] Migrating database: adding category column...');
|
||||
@@ -221,6 +256,19 @@ export class CliHistoryStore {
|
||||
}
|
||||
console.log('[CLI History] Migration complete: category column added');
|
||||
}
|
||||
|
||||
if (!hasParentExecutionId) {
|
||||
console.log('[CLI History] Migrating database: adding parent_execution_id column...');
|
||||
this.db.exec(`
|
||||
ALTER TABLE conversations ADD COLUMN parent_execution_id TEXT;
|
||||
`);
|
||||
try {
|
||||
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_parent ON conversations(parent_execution_id);`);
|
||||
} catch (indexErr) {
|
||||
console.warn('[CLI History] Parent execution index creation warning:', (indexErr as Error).message);
|
||||
}
|
||||
console.log('[CLI History] Migration complete: parent_execution_id column added');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[CLI History] Migration error:', (err as Error).message);
|
||||
// Don't throw - allow the store to continue working with existing schema
|
||||
@@ -314,8 +362,8 @@ export class CliHistoryStore {
|
||||
: '';
|
||||
|
||||
const upsertConversation = this.db.prepare(`
|
||||
INSERT INTO conversations (id, created_at, updated_at, tool, model, mode, category, total_duration_ms, turn_count, latest_status, prompt_preview)
|
||||
VALUES (@id, @created_at, @updated_at, @tool, @model, @mode, @category, @total_duration_ms, @turn_count, @latest_status, @prompt_preview)
|
||||
INSERT INTO conversations (id, created_at, updated_at, tool, model, mode, category, total_duration_ms, turn_count, latest_status, prompt_preview, parent_execution_id)
|
||||
VALUES (@id, @created_at, @updated_at, @tool, @model, @mode, @category, @total_duration_ms, @turn_count, @latest_status, @prompt_preview, @parent_execution_id)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
updated_at = @updated_at,
|
||||
total_duration_ms = @total_duration_ms,
|
||||
@@ -350,7 +398,8 @@ export class CliHistoryStore {
|
||||
total_duration_ms: conversation.total_duration_ms,
|
||||
turn_count: conversation.turn_count,
|
||||
latest_status: conversation.latest_status,
|
||||
prompt_preview: promptPreview
|
||||
prompt_preview: promptPreview,
|
||||
parent_execution_id: conversation.parent_execution_id || null
|
||||
});
|
||||
|
||||
for (const turn of conversation.turns) {
|
||||
@@ -397,6 +446,7 @@ export class CliHistoryStore {
|
||||
total_duration_ms: conv.total_duration_ms,
|
||||
turn_count: conv.turn_count,
|
||||
latest_status: conv.latest_status,
|
||||
parent_execution_id: conv.parent_execution_id || undefined,
|
||||
turns: turns.map(t => ({
|
||||
turn: t.turn_number,
|
||||
timestamp: t.timestamp,
|
||||
@@ -935,6 +985,107 @@ export class CliHistoryStore {
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update a review for an execution
|
||||
*/
|
||||
saveReview(review: Omit<ReviewRecord, 'id' | 'created_at' | 'updated_at'> & { created_at?: string; updated_at?: string }): ReviewRecord {
|
||||
const now = new Date().toISOString();
|
||||
const created_at = review.created_at || now;
|
||||
const updated_at = review.updated_at || now;
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO reviews (execution_id, status, rating, comments, reviewer, created_at, updated_at)
|
||||
VALUES (@execution_id, @status, @rating, @comments, @reviewer, @created_at, @updated_at)
|
||||
ON CONFLICT(execution_id) DO UPDATE SET
|
||||
status = @status,
|
||||
rating = @rating,
|
||||
comments = @comments,
|
||||
reviewer = @reviewer,
|
||||
updated_at = @updated_at
|
||||
`);
|
||||
|
||||
const result = stmt.run({
|
||||
execution_id: review.execution_id,
|
||||
status: review.status,
|
||||
rating: review.rating ?? null,
|
||||
comments: review.comments ?? null,
|
||||
reviewer: review.reviewer ?? null,
|
||||
created_at,
|
||||
updated_at
|
||||
});
|
||||
|
||||
return {
|
||||
id: result.lastInsertRowid as number,
|
||||
execution_id: review.execution_id,
|
||||
status: review.status,
|
||||
rating: review.rating,
|
||||
comments: review.comments,
|
||||
reviewer: review.reviewer,
|
||||
created_at,
|
||||
updated_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get review for an execution
|
||||
*/
|
||||
getReview(executionId: string): ReviewRecord | null {
|
||||
const row = this.db.prepare(
|
||||
'SELECT * FROM reviews WHERE execution_id = ?'
|
||||
).get(executionId) as any;
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
execution_id: row.execution_id,
|
||||
status: row.status as ReviewStatus,
|
||||
rating: row.rating,
|
||||
comments: row.comments,
|
||||
reviewer: row.reviewer,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reviews with optional filtering
|
||||
*/
|
||||
getReviews(options: { status?: ReviewStatus; limit?: number } = {}): ReviewRecord[] {
|
||||
const { status, limit = 50 } = options;
|
||||
|
||||
let sql = 'SELECT * FROM reviews';
|
||||
const params: any = { limit };
|
||||
|
||||
if (status) {
|
||||
sql += ' WHERE status = @status';
|
||||
params.status = status;
|
||||
}
|
||||
|
||||
sql += ' ORDER BY updated_at DESC LIMIT @limit';
|
||||
|
||||
const rows = this.db.prepare(sql).all(params) as any[];
|
||||
|
||||
return rows.map(row => ({
|
||||
id: row.id,
|
||||
execution_id: row.execution_id,
|
||||
status: row.status as ReviewStatus,
|
||||
rating: row.rating,
|
||||
comments: row.comments,
|
||||
reviewer: row.reviewer,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a review
|
||||
*/
|
||||
deleteReview(executionId: string): boolean {
|
||||
const result = this.db.prepare('DELETE FROM reviews WHERE execution_id = ?').run(executionId);
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
|
||||
@@ -35,12 +35,27 @@ let bootstrapReady = false;
|
||||
|
||||
// Define Zod schema for validation
|
||||
const ParamsSchema = z.object({
|
||||
action: z.enum(['init', 'search', 'search_files', 'symbol', 'status', 'update', 'bootstrap', 'check']),
|
||||
action: z.enum([
|
||||
'init',
|
||||
'search',
|
||||
'search_files',
|
||||
'symbol',
|
||||
'status',
|
||||
'config_show',
|
||||
'config_set',
|
||||
'config_migrate',
|
||||
'clean',
|
||||
'bootstrap',
|
||||
'check',
|
||||
]),
|
||||
path: z.string().optional(),
|
||||
query: z.string().optional(),
|
||||
mode: z.enum(['text', 'semantic']).default('text'),
|
||||
file: z.string().optional(),
|
||||
files: z.array(z.string()).optional(),
|
||||
key: z.string().optional(), // For config_set action
|
||||
value: z.string().optional(), // For config_set action
|
||||
newPath: z.string().optional(), // For config_migrate action
|
||||
all: z.boolean().optional(), // For clean action
|
||||
languages: z.array(z.string()).optional(),
|
||||
limit: z.number().default(20),
|
||||
format: z.enum(['json', 'table', 'plain']).default('json'),
|
||||
@@ -75,7 +90,8 @@ interface ExecuteResult {
|
||||
files?: unknown;
|
||||
symbols?: unknown;
|
||||
status?: unknown;
|
||||
updateResult?: unknown;
|
||||
config?: unknown;
|
||||
cleanResult?: unknown;
|
||||
ready?: boolean;
|
||||
version?: string;
|
||||
}
|
||||
@@ -534,24 +550,105 @@ async function getStatus(params: Params): Promise<ExecuteResult> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update specific files in the index
|
||||
* Show configuration
|
||||
* @param params - Parameters
|
||||
* @returns Execution result
|
||||
*/
|
||||
async function updateFiles(params: Params): Promise<ExecuteResult> {
|
||||
const { files, path = '.' } = params;
|
||||
|
||||
if (!files || !Array.isArray(files) || files.length === 0) {
|
||||
return { success: false, error: 'files parameter is required and must be a non-empty array' };
|
||||
}
|
||||
|
||||
const args = ['update', ...files, '--json'];
|
||||
|
||||
const result = await executeCodexLens(args, { cwd: path });
|
||||
async function configShow(): Promise<ExecuteResult> {
|
||||
const args = ['config', 'show', '--json'];
|
||||
const result = await executeCodexLens(args);
|
||||
|
||||
if (result.success && result.output) {
|
||||
try {
|
||||
result.updateResult = JSON.parse(result.output);
|
||||
result.config = JSON.parse(result.output);
|
||||
delete result.output;
|
||||
} catch {
|
||||
// Keep raw output if JSON parse fails
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration value
|
||||
* @param params - Parameters
|
||||
* @returns Execution result
|
||||
*/
|
||||
async function configSet(params: Params): Promise<ExecuteResult> {
|
||||
const { key, value } = params;
|
||||
|
||||
if (!key) {
|
||||
return { success: false, error: 'key is required for config_set action' };
|
||||
}
|
||||
if (!value) {
|
||||
return { success: false, error: 'value is required for config_set action' };
|
||||
}
|
||||
|
||||
const args = ['config', 'set', key, value, '--json'];
|
||||
const result = await executeCodexLens(args);
|
||||
|
||||
if (result.success && result.output) {
|
||||
try {
|
||||
result.config = JSON.parse(result.output);
|
||||
delete result.output;
|
||||
} catch {
|
||||
// Keep raw output if JSON parse fails
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate indexes to new location
|
||||
* @param params - Parameters
|
||||
* @returns Execution result
|
||||
*/
|
||||
async function configMigrate(params: Params): Promise<ExecuteResult> {
|
||||
const { newPath } = params;
|
||||
|
||||
if (!newPath) {
|
||||
return { success: false, error: 'newPath is required for config_migrate action' };
|
||||
}
|
||||
|
||||
const args = ['config', 'migrate', newPath, '--json'];
|
||||
const result = await executeCodexLens(args, { timeout: 300000 }); // 5 min for migration
|
||||
|
||||
if (result.success && result.output) {
|
||||
try {
|
||||
result.config = JSON.parse(result.output);
|
||||
delete result.output;
|
||||
} catch {
|
||||
// Keep raw output if JSON parse fails
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean indexes
|
||||
* @param params - Parameters
|
||||
* @returns Execution result
|
||||
*/
|
||||
async function cleanIndexes(params: Params): Promise<ExecuteResult> {
|
||||
const { path, all } = params;
|
||||
|
||||
const args = ['clean'];
|
||||
|
||||
if (all) {
|
||||
args.push('--all');
|
||||
} else if (path) {
|
||||
args.push(path);
|
||||
}
|
||||
|
||||
args.push('--json');
|
||||
const result = await executeCodexLens(args);
|
||||
|
||||
if (result.success && result.output) {
|
||||
try {
|
||||
result.cleanResult = JSON.parse(result.output);
|
||||
delete result.output;
|
||||
} catch {
|
||||
// Keep raw output if JSON parse fails
|
||||
@@ -572,18 +669,35 @@ Usage:
|
||||
codex_lens(action="search_files", query="x") # Search, return paths only
|
||||
codex_lens(action="symbol", file="f.py") # Extract symbols
|
||||
codex_lens(action="status") # Index status
|
||||
codex_lens(action="update", files=["a.js"]) # Update specific files`,
|
||||
codex_lens(action="config_show") # Show configuration
|
||||
codex_lens(action="config_set", key="index_dir", value="/path/to/indexes") # Set config
|
||||
codex_lens(action="config_migrate", newPath="/new/path") # Migrate indexes
|
||||
codex_lens(action="clean") # Show clean status
|
||||
codex_lens(action="clean", path=".") # Clean specific project
|
||||
codex_lens(action="clean", all=true) # Clean all indexes`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['init', 'search', 'search_files', 'symbol', 'status', 'update', 'bootstrap', 'check'],
|
||||
enum: [
|
||||
'init',
|
||||
'search',
|
||||
'search_files',
|
||||
'symbol',
|
||||
'status',
|
||||
'config_show',
|
||||
'config_set',
|
||||
'config_migrate',
|
||||
'clean',
|
||||
'bootstrap',
|
||||
'check',
|
||||
],
|
||||
description: 'Action to perform',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Target path (for init, search, search_files, status, update)',
|
||||
description: 'Target path (for init, search, search_files, status, clean)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
@@ -599,10 +713,22 @@ Usage:
|
||||
type: 'string',
|
||||
description: 'File path (for symbol action)',
|
||||
},
|
||||
files: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'File paths to update (for update action)',
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Config key (for config_set action, e.g., "index_dir")',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'Config value (for config_set action)',
|
||||
},
|
||||
newPath: {
|
||||
type: 'string',
|
||||
description: 'New index path (for config_migrate action)',
|
||||
},
|
||||
all: {
|
||||
type: 'boolean',
|
||||
description: 'Clean all indexes (for clean action)',
|
||||
default: false,
|
||||
},
|
||||
languages: {
|
||||
type: 'array',
|
||||
@@ -658,8 +784,20 @@ export async function handler(params: Record<string, unknown>): Promise<ToolResu
|
||||
result = await getStatus(parsed.data);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
result = await updateFiles(parsed.data);
|
||||
case 'config_show':
|
||||
result = await configShow();
|
||||
break;
|
||||
|
||||
case 'config_set':
|
||||
result = await configSet(parsed.data);
|
||||
break;
|
||||
|
||||
case 'config_migrate':
|
||||
result = await configMigrate(parsed.data);
|
||||
break;
|
||||
|
||||
case 'clean':
|
||||
result = await cleanIndexes(parsed.data);
|
||||
break;
|
||||
|
||||
case 'bootstrap': {
|
||||
@@ -686,7 +824,7 @@ export async function handler(params: Record<string, unknown>): Promise<ToolResu
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown action: ${action}. Valid actions: init, search, search_files, symbol, status, update, bootstrap, check`
|
||||
`Unknown action: ${action}. Valid actions: init, search, search_files, symbol, status, config_show, config_set, config_migrate, clean, bootstrap, check`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
129
ccw/src/tools/notifier.ts
Normal file
129
ccw/src/tools/notifier.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Notifier Module - CLI to Server Communication
|
||||
* Provides best-effort notification to running CCW Server
|
||||
* when CLI commands modify data that should trigger UI updates
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
|
||||
// Default server configuration
|
||||
const DEFAULT_HOST = 'localhost';
|
||||
const DEFAULT_PORT = 3456;
|
||||
const NOTIFY_TIMEOUT = 2000; // 2 seconds - quick timeout for best-effort
|
||||
|
||||
export type NotifyScope = 'memory' | 'history' | 'insights' | 'all';
|
||||
|
||||
export interface NotifyPayload {
|
||||
type: 'REFRESH_REQUIRED' | 'MEMORY_UPDATED' | 'HISTORY_UPDATED' | 'INSIGHT_GENERATED';
|
||||
scope: NotifyScope;
|
||||
data?: {
|
||||
entityType?: string;
|
||||
entityId?: string | number;
|
||||
action?: string;
|
||||
executionId?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotifyResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to CCW Server (best-effort, non-blocking)
|
||||
* If server is not running or unreachable, silently fails
|
||||
*/
|
||||
export async function notifyServer(
|
||||
payload: NotifyPayload,
|
||||
options?: { host?: string; port?: number }
|
||||
): Promise<NotifyResult> {
|
||||
const host = options?.host || DEFAULT_HOST;
|
||||
const port = options?.port || DEFAULT_PORT;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const postData = JSON.stringify(payload);
|
||||
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: host,
|
||||
port: port,
|
||||
path: '/api/system/notify',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(postData),
|
||||
},
|
||||
timeout: NOTIFY_TIMEOUT,
|
||||
},
|
||||
(res) => {
|
||||
// Success if we get a 2xx response
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve({ success: true });
|
||||
} else {
|
||||
resolve({ success: false, error: `HTTP ${res.statusCode}` });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Handle errors silently - server may not be running
|
||||
req.on('error', () => {
|
||||
resolve({ success: false, error: 'Server not reachable' });
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve({ success: false, error: 'Timeout' });
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: Notify memory update
|
||||
*/
|
||||
export async function notifyMemoryUpdate(data?: {
|
||||
entityType?: string;
|
||||
entityId?: string | number;
|
||||
action?: string;
|
||||
}): Promise<NotifyResult> {
|
||||
return notifyServer({
|
||||
type: 'MEMORY_UPDATED',
|
||||
scope: 'memory',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: Notify CLI history update
|
||||
*/
|
||||
export async function notifyHistoryUpdate(executionId?: string): Promise<NotifyResult> {
|
||||
return notifyServer({
|
||||
type: 'HISTORY_UPDATED',
|
||||
scope: 'history',
|
||||
data: executionId ? { executionId } : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: Notify insight generated
|
||||
*/
|
||||
export async function notifyInsightGenerated(executionId?: string): Promise<NotifyResult> {
|
||||
return notifyServer({
|
||||
type: 'INSIGHT_GENERATED',
|
||||
scope: 'insights',
|
||||
data: executionId ? { executionId } : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: Request full refresh
|
||||
*/
|
||||
export async function notifyRefreshRequired(scope: NotifyScope = 'all'): Promise<NotifyResult> {
|
||||
return notifyServer({
|
||||
type: 'REFRESH_REQUIRED',
|
||||
scope,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user