feat: Add intelligent auto-router and enhanced integrations

- Add intelligent-router.sh hook for automatic agent routing
- Add AUTO-TRIGGER-SUMMARY.md documentation
- Add FINAL-INTEGRATION-SUMMARY.md documentation
- Complete Prometheus integration (6 commands + 4 tools)
- Complete Dexto integration (12 commands + 5 tools)
- Enhanced Ralph with access to all agents
- Fix /clawd command (removed disable-model-invocation)
- Update hooks.json to v5 with intelligent routing
- 291 total skills now available
- All 21 commands with automatic routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
admin
2026-01-28 00:27:56 +04:00
Unverified
parent 3b128ba3bd
commit b52318eeae
1724 changed files with 351216 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
// packages/analytics/src/constants.ts
// Single source for PostHog configuration.
// Embed your public key here (safe to publish). Host can stay default.
export const DEFAULT_POSTHOG_KEY = 'phc_IJHITHjBKOjDyFiVeilfdumcGniXMuLeXeiLQhYvwDW';
export const DEFAULT_POSTHOG_HOST = 'https://app.posthog.com';
/**
* Single opt-out switch for analytics.
*
* Usage:
* DEXTO_ANALYTICS_DISABLED=1 dexto ...
*
* When set to a truthy value ("1", "true", "yes"), analytics are fully disabled.
*/
export function isAnalyticsDisabled(): boolean {
const v = process.env.DEXTO_ANALYTICS_DISABLED;
return typeof v === 'string' && /^(1|true|yes)$/i.test(v);
}
/**
* Generic per-command timeout (in milliseconds) used by the analytics wrapper.
*
* This does NOT terminate the command. It emits a non-terminating timeout
* event when the duration threshold is crossed to help diagnose long runs.
*/
export const COMMAND_TIMEOUT_MS = 120000; // 2 minutes (default for quick commands)

View File

@@ -0,0 +1,175 @@
/**
* Platform source for analytics events.
* Used to distinguish which interface generated the event.
*/
export type AnalyticsSource = 'cli' | 'webui';
/**
* LLM token consumption event.
* Emitted after each LLM response with token usage data.
*
*/
export interface LLMTokensConsumedEvent {
source: AnalyticsSource;
sessionId: string;
provider?: string | undefined;
model?: string | undefined;
inputTokens?: number | undefined;
outputTokens?: number | undefined;
reasoningTokens?: number | undefined;
totalTokens?: number | undefined;
cacheReadTokens?: number | undefined;
cacheWriteTokens?: number | undefined;
/** Estimated input tokens (before LLM call, using length/4 heuristic) */
estimatedInputTokens?: number | undefined;
/** Accuracy of estimate vs actual: (estimated - actual) / actual * 100 */
estimateAccuracyPercent?: number | undefined;
}
/**
* Message sent event.
* Emitted when user sends a message to the agent.
*/
export interface MessageSentEvent {
source: AnalyticsSource;
sessionId: string;
provider: string;
model: string;
hasImage: boolean;
hasFile: boolean;
messageLength: number;
messageCount?: number | undefined;
isQueued?: boolean | undefined;
}
/**
* Tool called event.
* Emitted when a tool is invoked by the LLM.
*/
export interface ToolCalledEvent {
source: AnalyticsSource;
sessionId: string;
toolName: string;
mcpServer?: string | undefined;
}
/**
* Tool result event.
* Emitted when a tool execution completes.
*/
export interface ToolResultEvent {
source: AnalyticsSource;
sessionId: string;
toolName: string;
success: boolean;
approvalStatus?: 'approved' | 'rejected' | undefined;
}
/**
* Session created event.
*/
export interface SessionCreatedEvent {
source: AnalyticsSource;
sessionId: string;
trigger: 'first_message' | 'manual' | 'resume';
}
/**
* Session reset event (conversation cleared).
*/
export interface SessionResetEvent {
source: AnalyticsSource;
sessionId: string;
messageCount: number;
}
/**
* LLM switched event.
*/
export interface LLMSwitchedEvent {
source: AnalyticsSource;
sessionId?: string | undefined;
fromProvider: string;
fromModel: string;
toProvider: string;
toModel: string;
trigger: 'user_action' | 'config_change';
}
/**
* Session switched event.
* Emitted when user switches to a different session.
*/
export interface SessionSwitchedEvent {
source: AnalyticsSource;
fromSessionId: string | null;
toSessionId: string;
}
/**
* Agent switched event.
* Emitted when user switches to a different agent.
*/
export interface AgentSwitchedEvent {
source: AnalyticsSource;
fromAgentId: string | null;
toAgentId: string;
toAgentName?: string | undefined;
sessionId?: string | undefined;
}
/**
* MCP server connected event.
* Emitted when an MCP server connects successfully.
*/
export interface MCPServerConnectedEvent {
source: AnalyticsSource;
serverName: string;
transportType: 'stdio' | 'http' | 'sse';
toolCount?: number | undefined;
}
/**
* File attached event.
* Emitted when user attaches a file to a message.
*/
export interface FileAttachedEvent {
source: AnalyticsSource;
sessionId: string;
fileType: string;
fileSizeBytes?: number | undefined;
}
/**
* Image attached event.
* Emitted when user attaches an image to a message.
*/
export interface ImageAttachedEvent {
source: AnalyticsSource;
sessionId: string;
imageType: string;
imageSizeBytes?: number | undefined;
}
/**
* Shared analytics event map containing events supported by ALL platforms.
* CLI and WebUI extend this map with platform-specific events.
*
* IMPORTANT: If an event is tracked by both CLI and WebUI, add it here.
* Platform-specific events should be added to the respective platform's event map.
*/
export interface SharedAnalyticsEventMap {
dexto_llm_tokens_consumed: LLMTokensConsumedEvent;
dexto_message_sent: MessageSentEvent;
dexto_tool_called: ToolCalledEvent;
dexto_tool_result: ToolResultEvent;
dexto_session_created: SessionCreatedEvent;
dexto_session_reset: SessionResetEvent;
dexto_llm_switched: LLMSwitchedEvent;
dexto_session_switched: SessionSwitchedEvent;
dexto_agent_switched: AgentSwitchedEvent;
dexto_mcp_server_connected: MCPServerConnectedEvent;
dexto_image_attached: ImageAttachedEvent;
}
export type SharedAnalyticsEventName = keyof SharedAnalyticsEventMap;

View File

@@ -0,0 +1,12 @@
// packages/analytics/src/index.ts
// Shared analytics utilities for Dexto CLI and WebUI
export {
DEFAULT_POSTHOG_KEY,
DEFAULT_POSTHOG_HOST,
COMMAND_TIMEOUT_MS,
isAnalyticsDisabled,
} from './constants.js';
export { loadState, saveState } from './state.js';
export type { AnalyticsState } from './state.js';
export * from './events.js';

View File

@@ -0,0 +1,90 @@
// packages/analytics/src/state.ts
import { promises as fs } from 'fs';
import * as path from 'path';
import os from 'os';
import { randomUUID, createHash } from 'crypto';
import { createRequire } from 'module';
import { getDextoGlobalPath } from '@dexto/core';
/**
* Shape of the persisted analytics state written to
* ~/.dexto/telemetry/state.json.
*
* - distinctId: Anonymous ID (UUID) for grouping events by machine.
* - createdAt: ISO timestamp when the state was first created.
* - commandRunCounts: Local counters per command for coarse diagnostics.
*/
export interface AnalyticsState {
distinctId: string;
createdAt: string; // ISO string
commandRunCounts?: Record<string, number>;
}
const STATE_DIR = getDextoGlobalPath('telemetry');
const STATE_FILE = path.join(STATE_DIR, 'state.json');
/**
* Load the persisted analytics state, creating a new file if missing.
* Returns a valid state object with defaults populated.
*/
export async function loadState(): Promise<AnalyticsState> {
try {
const content = await fs.readFile(STATE_FILE, 'utf8');
const parsed = JSON.parse(content) as Partial<AnalyticsState>;
// Validate minimal shape
if (!parsed.distinctId) throw new Error('invalid state');
return {
distinctId: parsed.distinctId,
createdAt: parsed.createdAt || new Date().toISOString(),
commandRunCounts: parsed.commandRunCounts ?? {},
};
} catch {
await fs.mkdir(STATE_DIR, { recursive: true });
const state: AnalyticsState = {
distinctId: computeDistinctId(),
createdAt: new Date().toISOString(),
commandRunCounts: {},
};
await saveState(state);
return state;
}
}
/**
* Persist the analytics state to ~/.dexto/telemetry/state.json.
*/
export async function saveState(state: AnalyticsState): Promise<void> {
await fs.mkdir(STATE_DIR, { recursive: true });
await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
}
/**
* Compute a stable, privacysafe machine identifier so identity
* survives ~/.dexto deletion by default.
*
* Strategy:
* - Prefer node-machine-id (hashed), which abstracts platform differences.
* - Fallback to a salted/hashed hostname.
* - As a last resort, generate a random UUID.
*/
function computeDistinctId(): string {
try {
// node-machine-id is CommonJS; require lazily to avoid import-time failures
const requireCJS = createRequire(import.meta.url);
const { machineIdSync } = requireCJS('node-machine-id') as {
machineIdSync: (original?: boolean) => string;
};
// machineIdSync(true) returns a hashed, stable identifier
const id = machineIdSync(true);
if (typeof id === 'string' && id.length > 0) return `DEXTO-${id}`;
} catch {
// fall through to hostname hash
}
// Fallback: hash hostname to avoid exposing raw value
const hostname = os.hostname() || 'unknown-host';
const digest = createHash('sha256').update(hostname).digest('hex');
if (digest) return `DEXTO-${digest.slice(0, 32)}`;
// Last resort
return `DEXTO-${randomUUID()}`;
}