@cpretzinger/boss-claude 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- package/scripts/verify-redis-init.js +219 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONYX IDENTITY MODULE
|
|
3
|
+
*
|
|
4
|
+
* Enforces orchestrator-only behavior by injecting identity into every system prompt.
|
|
5
|
+
* ONYX delegates to worker agents but NEVER uses worker tools directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const ONYX_IDENTITY = {
|
|
9
|
+
name: 'ONYX',
|
|
10
|
+
tier: 'Meta-Boss',
|
|
11
|
+
role: 'Orchestrator',
|
|
12
|
+
description: 'Top-level orchestrator that delegates to specialized agents',
|
|
13
|
+
|
|
14
|
+
// Core behavioral constraints
|
|
15
|
+
constraints: {
|
|
16
|
+
canDelegateToWorkers: true,
|
|
17
|
+
canDelegateToBosses: true,
|
|
18
|
+
canUseWorkerTools: false,
|
|
19
|
+
canExecuteDirectly: false,
|
|
20
|
+
mustValidateHierarchy: true
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Allowed operations (orchestration only)
|
|
24
|
+
allowedOperations: [
|
|
25
|
+
'task_analysis',
|
|
26
|
+
'agent_selection',
|
|
27
|
+
'delegation',
|
|
28
|
+
'validation',
|
|
29
|
+
'hierarchy_enforcement',
|
|
30
|
+
'result_aggregation',
|
|
31
|
+
'user_communication'
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
// Forbidden operations (worker tasks)
|
|
35
|
+
forbiddenOperations: [
|
|
36
|
+
'direct_code_execution',
|
|
37
|
+
'database_queries',
|
|
38
|
+
'file_system_writes',
|
|
39
|
+
'api_calls',
|
|
40
|
+
'workflow_creation',
|
|
41
|
+
'deployment_operations'
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generates the ONYX identity prompt that must be injected into every system context
|
|
47
|
+
*
|
|
48
|
+
* @returns {string} The ONYX identity statement
|
|
49
|
+
*/
|
|
50
|
+
export function getOnyxIdentityPrompt() {
|
|
51
|
+
return `
|
|
52
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
53
|
+
🔷 YOU ARE ONYX - META-BOSS ORCHESTRATOR
|
|
54
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
55
|
+
|
|
56
|
+
IDENTITY: ${ONYX_IDENTITY.name}
|
|
57
|
+
TIER: ${ONYX_IDENTITY.tier}
|
|
58
|
+
ROLE: ${ONYX_IDENTITY.role}
|
|
59
|
+
|
|
60
|
+
CORE DIRECTIVE: YOU ARE AN ORCHESTRATOR ONLY
|
|
61
|
+
|
|
62
|
+
✅ YOU CAN:
|
|
63
|
+
${ONYX_IDENTITY.allowedOperations.map(op => ` • ${op.replace(/_/g, ' ').toUpperCase()}`).join('\n')}
|
|
64
|
+
|
|
65
|
+
❌ YOU CANNOT:
|
|
66
|
+
${ONYX_IDENTITY.forbiddenOperations.map(op => ` • ${op.replace(/_/g, ' ').toUpperCase()}`).join('\n')}
|
|
67
|
+
|
|
68
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
69
|
+
⚠️ CRITICAL RULES:
|
|
70
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
71
|
+
|
|
72
|
+
1. NEVER execute worker tasks yourself
|
|
73
|
+
2. ALWAYS delegate to appropriate specialist agents
|
|
74
|
+
3. ALWAYS validate hierarchy before accepting worker results
|
|
75
|
+
4. NEVER use tools meant for worker agents (database, files, APIs)
|
|
76
|
+
5. Your role is to ANALYZE, DELEGATE, and VALIDATE only
|
|
77
|
+
|
|
78
|
+
When a user asks you to perform a task:
|
|
79
|
+
→ Analyze the task
|
|
80
|
+
→ Identify the specialist agent
|
|
81
|
+
→ Delegate with clear requirements
|
|
82
|
+
→ Validate the result through proper hierarchy
|
|
83
|
+
→ Report back to user
|
|
84
|
+
|
|
85
|
+
You are the conductor, not the orchestra.
|
|
86
|
+
You are the architect, not the builder.
|
|
87
|
+
You are the orchestrator, not the worker.
|
|
88
|
+
|
|
89
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Validates that current operation adheres to ONYX identity constraints
|
|
95
|
+
*
|
|
96
|
+
* @param {string} operation - The operation being attempted
|
|
97
|
+
* @returns {Object} Validation result with allowed status and reason
|
|
98
|
+
*/
|
|
99
|
+
export function validateOnyxOperation(operation) {
|
|
100
|
+
const normalizedOp = operation.toLowerCase().replace(/\s+/g, '_');
|
|
101
|
+
|
|
102
|
+
// Check if operation is explicitly allowed
|
|
103
|
+
if (ONYX_IDENTITY.allowedOperations.some(allowed => normalizedOp.includes(allowed))) {
|
|
104
|
+
return {
|
|
105
|
+
allowed: true,
|
|
106
|
+
reason: `Operation "${operation}" is within ONYX orchestrator scope`,
|
|
107
|
+
operation
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if operation is explicitly forbidden
|
|
112
|
+
if (ONYX_IDENTITY.forbiddenOperations.some(forbidden => normalizedOp.includes(forbidden))) {
|
|
113
|
+
return {
|
|
114
|
+
allowed: false,
|
|
115
|
+
reason: `Operation "${operation}" is a worker task - ONYX must delegate`,
|
|
116
|
+
operation,
|
|
117
|
+
suggestedAction: 'Delegate to appropriate specialist agent',
|
|
118
|
+
violation: true
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Unknown operation - err on the side of caution
|
|
123
|
+
return {
|
|
124
|
+
allowed: false,
|
|
125
|
+
reason: `Operation "${operation}" is not in ONYX's allowed operations`,
|
|
126
|
+
operation,
|
|
127
|
+
suggestedAction: 'Verify if this requires delegation',
|
|
128
|
+
requiresReview: true
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generates context-refresh reminder that ONYX is orchestrator only
|
|
134
|
+
* Used in long conversations or after context resets
|
|
135
|
+
*
|
|
136
|
+
* @returns {string} Compact identity reminder
|
|
137
|
+
*/
|
|
138
|
+
export function getOnyxContextReminder() {
|
|
139
|
+
return `
|
|
140
|
+
⚡ ONYX IDENTITY REMINDER ⚡
|
|
141
|
+
You are ONYX (Meta-Boss Orchestrator). You DELEGATE tasks to specialists.
|
|
142
|
+
You DO NOT execute worker tasks yourself. Analyze → Delegate → Validate.
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Checks if a tool invocation violates ONYX orchestrator constraints
|
|
148
|
+
*
|
|
149
|
+
* @param {string} toolName - The tool being invoked
|
|
150
|
+
* @param {Object} toolParams - Tool parameters
|
|
151
|
+
* @returns {Object} Violation check result
|
|
152
|
+
*/
|
|
153
|
+
export function checkToolViolation(toolName, toolParams = {}) {
|
|
154
|
+
// Define tools that are ONLY for workers
|
|
155
|
+
const workerOnlyTools = [
|
|
156
|
+
'executeSQL',
|
|
157
|
+
'writeFile',
|
|
158
|
+
'createWorkflow',
|
|
159
|
+
'deployCode',
|
|
160
|
+
'callAPI',
|
|
161
|
+
'modifySchema',
|
|
162
|
+
'commitCode',
|
|
163
|
+
'runTests',
|
|
164
|
+
'startServer'
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
// Define tools that are OK for orchestrators
|
|
168
|
+
const orchestratorTools = [
|
|
169
|
+
'analyzeTask',
|
|
170
|
+
'selectAgent',
|
|
171
|
+
'delegateTask',
|
|
172
|
+
'validateResult',
|
|
173
|
+
'hierarchyCheck',
|
|
174
|
+
'getStatus',
|
|
175
|
+
'reportToUser'
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const isWorkerTool = workerOnlyTools.some(tool =>
|
|
179
|
+
toolName.toLowerCase().includes(tool.toLowerCase())
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const isOrchestratorTool = orchestratorTools.some(tool =>
|
|
183
|
+
toolName.toLowerCase().includes(tool.toLowerCase())
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (isWorkerTool) {
|
|
187
|
+
return {
|
|
188
|
+
violation: true,
|
|
189
|
+
severity: 'critical',
|
|
190
|
+
message: `ONYX VIOLATION: Tool "${toolName}" is for worker agents only`,
|
|
191
|
+
action: 'DELEGATE this task instead of executing directly',
|
|
192
|
+
toolName
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (isOrchestratorTool) {
|
|
197
|
+
return {
|
|
198
|
+
violation: false,
|
|
199
|
+
message: `Tool "${toolName}" is approved for orchestrator use`,
|
|
200
|
+
toolName
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Unknown tool - flag for review
|
|
205
|
+
return {
|
|
206
|
+
violation: false,
|
|
207
|
+
warning: true,
|
|
208
|
+
message: `Tool "${toolName}" not recognized - verify it's appropriate for orchestrator`,
|
|
209
|
+
action: 'Review if this should be delegated',
|
|
210
|
+
toolName
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default ONYX_IDENTITY;
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { appendFileSync, mkdirSync } from 'fs';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// Load environment variables from ~/.boss-claude/.env
|
|
13
|
+
const envPath = join(os.homedir(), '.boss-claude', '.env');
|
|
14
|
+
if (existsSync(envPath)) {
|
|
15
|
+
dotenv.config({ path: envPath });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BOSS_DIR = join(os.homedir(), '.boss-claude');
|
|
19
|
+
const ALERT_LOG = join(BOSS_DIR, 'onyx-alerts.log');
|
|
20
|
+
|
|
21
|
+
// Ensure directory exists
|
|
22
|
+
try {
|
|
23
|
+
mkdirSync(BOSS_DIR, { recursive: true });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
// Directory already exists
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let redis = null;
|
|
29
|
+
|
|
30
|
+
function getRedis() {
|
|
31
|
+
if (!redis) {
|
|
32
|
+
if (!process.env.REDIS_URL) {
|
|
33
|
+
throw new Error('REDIS_URL not found. Please run: boss-claude init');
|
|
34
|
+
}
|
|
35
|
+
redis = new Redis(process.env.REDIS_URL);
|
|
36
|
+
}
|
|
37
|
+
return redis;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Redis keys
|
|
41
|
+
const DELEGATION_KEY = 'boss:onyx:delegation';
|
|
42
|
+
const DIRECT_ACTION_KEY = 'boss:onyx:direct_actions';
|
|
43
|
+
const ALERT_THRESHOLD_KEY = 'boss:onyx:alert_threshold';
|
|
44
|
+
const LAST_ALERT_KEY = 'boss:onyx:last_alert';
|
|
45
|
+
|
|
46
|
+
// Default alert threshold: 95%
|
|
47
|
+
const DEFAULT_THRESHOLD = 0.95;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Log an alert to file
|
|
51
|
+
* @param {string} message - Alert message
|
|
52
|
+
* @param {object} data - Additional data to log
|
|
53
|
+
*/
|
|
54
|
+
function logAlert(message, data = {}) {
|
|
55
|
+
const timestamp = new Date().toISOString();
|
|
56
|
+
const logLine = `[${timestamp}] ALERT: ${message}\n${JSON.stringify(data, null, 2)}\n\n`;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
appendFileSync(ALERT_LOG, logLine, 'utf8');
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`Failed to write alert log: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Track a task delegated to ONYX agent
|
|
67
|
+
* @param {string} agentName - Name of the ONYX agent
|
|
68
|
+
* @param {string} taskDescription - Description of the task
|
|
69
|
+
* @param {object} metadata - Additional metadata
|
|
70
|
+
* @returns {Promise<object>} Updated statistics
|
|
71
|
+
*/
|
|
72
|
+
export async function trackDelegation(agentName, taskDescription, metadata = {}) {
|
|
73
|
+
const client = getRedis();
|
|
74
|
+
const timestamp = new Date().toISOString();
|
|
75
|
+
|
|
76
|
+
// Increment delegation counter
|
|
77
|
+
await client.hincrby(DELEGATION_KEY, 'total', 1);
|
|
78
|
+
await client.hincrby(DELEGATION_KEY, `agent:${agentName}`, 1);
|
|
79
|
+
|
|
80
|
+
// Store delegation event
|
|
81
|
+
const event = {
|
|
82
|
+
type: 'delegation',
|
|
83
|
+
agent: agentName,
|
|
84
|
+
task: taskDescription,
|
|
85
|
+
timestamp,
|
|
86
|
+
metadata
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await client.lpush('boss:onyx:events', JSON.stringify(event));
|
|
90
|
+
await client.ltrim('boss:onyx:events', 0, 999); // Keep last 1000 events
|
|
91
|
+
|
|
92
|
+
// Check delegation ratio and alert if needed
|
|
93
|
+
const stats = await getDelegationStats();
|
|
94
|
+
await checkThresholdAndAlert(stats);
|
|
95
|
+
|
|
96
|
+
return stats;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Track a direct action taken by Boss Claude (not delegated)
|
|
101
|
+
* @param {string} actionType - Type of direct action
|
|
102
|
+
* @param {string} description - Description of the action
|
|
103
|
+
* @param {object} metadata - Additional metadata
|
|
104
|
+
* @returns {Promise<object>} Updated statistics
|
|
105
|
+
*/
|
|
106
|
+
export async function trackDirectAction(actionType, description, metadata = {}) {
|
|
107
|
+
const client = getRedis();
|
|
108
|
+
const timestamp = new Date().toISOString();
|
|
109
|
+
|
|
110
|
+
// Increment direct action counter
|
|
111
|
+
await client.hincrby(DIRECT_ACTION_KEY, 'total', 1);
|
|
112
|
+
await client.hincrby(DIRECT_ACTION_KEY, `type:${actionType}`, 1);
|
|
113
|
+
|
|
114
|
+
// Store direct action event
|
|
115
|
+
const event = {
|
|
116
|
+
type: 'direct_action',
|
|
117
|
+
action_type: actionType,
|
|
118
|
+
description,
|
|
119
|
+
timestamp,
|
|
120
|
+
metadata
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
await client.lpush('boss:onyx:events', JSON.stringify(event));
|
|
124
|
+
await client.ltrim('boss:onyx:events', 0, 999);
|
|
125
|
+
|
|
126
|
+
// Check delegation ratio and alert if needed
|
|
127
|
+
const stats = await getDelegationStats();
|
|
128
|
+
await checkThresholdAndAlert(stats);
|
|
129
|
+
|
|
130
|
+
return stats;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get current delegation statistics
|
|
135
|
+
* @returns {Promise<object>} Delegation stats including ratio
|
|
136
|
+
*/
|
|
137
|
+
export async function getDelegationStats() {
|
|
138
|
+
const client = getRedis();
|
|
139
|
+
|
|
140
|
+
const delegationData = await client.hgetall(DELEGATION_KEY);
|
|
141
|
+
const directActionData = await client.hgetall(DIRECT_ACTION_KEY);
|
|
142
|
+
|
|
143
|
+
const totalDelegations = parseInt(delegationData.total || 0);
|
|
144
|
+
const totalDirectActions = parseInt(directActionData.total || 0);
|
|
145
|
+
const totalActions = totalDelegations + totalDirectActions;
|
|
146
|
+
|
|
147
|
+
const delegationRatio = totalActions > 0 ? totalDelegations / totalActions : 0;
|
|
148
|
+
|
|
149
|
+
// Get agent breakdown
|
|
150
|
+
const agentStats = {};
|
|
151
|
+
for (const [key, value] of Object.entries(delegationData)) {
|
|
152
|
+
if (key.startsWith('agent:')) {
|
|
153
|
+
const agentName = key.replace('agent:', '');
|
|
154
|
+
agentStats[agentName] = parseInt(value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get action type breakdown
|
|
159
|
+
const actionTypeStats = {};
|
|
160
|
+
for (const [key, value] of Object.entries(directActionData)) {
|
|
161
|
+
if (key.startsWith('type:')) {
|
|
162
|
+
const actionType = key.replace('type:', '');
|
|
163
|
+
actionTypeStats[actionType] = parseInt(value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const threshold = parseFloat(await client.get(ALERT_THRESHOLD_KEY) || DEFAULT_THRESHOLD);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
total_delegations: totalDelegations,
|
|
171
|
+
total_direct_actions: totalDirectActions,
|
|
172
|
+
total_actions: totalActions,
|
|
173
|
+
delegation_ratio: delegationRatio,
|
|
174
|
+
delegation_percentage: (delegationRatio * 100).toFixed(2),
|
|
175
|
+
threshold: threshold,
|
|
176
|
+
threshold_percentage: (threshold * 100).toFixed(0),
|
|
177
|
+
meets_threshold: delegationRatio >= threshold,
|
|
178
|
+
agent_breakdown: agentStats,
|
|
179
|
+
direct_action_breakdown: actionTypeStats
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get recent events (delegations and direct actions)
|
|
185
|
+
* @param {number} limit - Number of events to retrieve (default: 50)
|
|
186
|
+
* @returns {Promise<Array>} Recent events
|
|
187
|
+
*/
|
|
188
|
+
export async function getRecentEvents(limit = 50) {
|
|
189
|
+
const client = getRedis();
|
|
190
|
+
const events = await client.lrange('boss:onyx:events', 0, limit - 1);
|
|
191
|
+
|
|
192
|
+
return events.map(e => JSON.parse(e));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if delegation ratio meets threshold and alert if not
|
|
197
|
+
* @param {object} stats - Current delegation statistics
|
|
198
|
+
* @returns {Promise<boolean>} True if alert was triggered
|
|
199
|
+
*/
|
|
200
|
+
async function checkThresholdAndAlert(stats) {
|
|
201
|
+
const client = getRedis();
|
|
202
|
+
|
|
203
|
+
// Don't alert if we don't have enough data
|
|
204
|
+
if (stats.total_actions < 10) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!stats.meets_threshold) {
|
|
209
|
+
// Check if we've alerted recently (throttle to once per hour)
|
|
210
|
+
const lastAlert = await client.get(LAST_ALERT_KEY);
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
|
|
213
|
+
if (lastAlert) {
|
|
214
|
+
const lastAlertTime = parseInt(lastAlert);
|
|
215
|
+
const hoursSinceLastAlert = (now - lastAlertTime) / (1000 * 60 * 60);
|
|
216
|
+
|
|
217
|
+
if (hoursSinceLastAlert < 1) {
|
|
218
|
+
return false; // Skip alert, too soon
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Trigger alert
|
|
223
|
+
const alertMessage = `ONYX delegation ratio dropped below ${stats.threshold_percentage}%`;
|
|
224
|
+
const alertData = {
|
|
225
|
+
current_ratio: stats.delegation_percentage + '%',
|
|
226
|
+
threshold: stats.threshold_percentage + '%',
|
|
227
|
+
total_actions: stats.total_actions,
|
|
228
|
+
delegations: stats.total_delegations,
|
|
229
|
+
direct_actions: stats.total_direct_actions,
|
|
230
|
+
agent_breakdown: stats.agent_breakdown,
|
|
231
|
+
direct_action_breakdown: stats.direct_action_breakdown
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
logAlert(alertMessage, alertData);
|
|
235
|
+
console.warn(`\n⚠️ ${alertMessage}`);
|
|
236
|
+
console.warn(`Current ratio: ${stats.delegation_percentage}% (${stats.total_delegations}/${stats.total_actions})`);
|
|
237
|
+
console.warn(`View details: boss-claude onyx-status\n`);
|
|
238
|
+
|
|
239
|
+
// Update last alert timestamp
|
|
240
|
+
await client.set(LAST_ALERT_KEY, now.toString());
|
|
241
|
+
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Set the alert threshold (0.0 to 1.0)
|
|
250
|
+
* @param {number} threshold - New threshold value (e.g., 0.95 for 95%)
|
|
251
|
+
* @returns {Promise<number>} The new threshold
|
|
252
|
+
*/
|
|
253
|
+
export async function setAlertThreshold(threshold) {
|
|
254
|
+
if (threshold < 0 || threshold > 1) {
|
|
255
|
+
throw new Error('Threshold must be between 0 and 1');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const client = getRedis();
|
|
259
|
+
await client.set(ALERT_THRESHOLD_KEY, threshold.toString());
|
|
260
|
+
|
|
261
|
+
return threshold;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get the current alert threshold
|
|
266
|
+
* @returns {Promise<number>} Current threshold
|
|
267
|
+
*/
|
|
268
|
+
export async function getAlertThreshold() {
|
|
269
|
+
const client = getRedis();
|
|
270
|
+
return parseFloat(await client.get(ALERT_THRESHOLD_KEY) || DEFAULT_THRESHOLD);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Reset all delegation tracking data
|
|
275
|
+
* @returns {Promise<void>}
|
|
276
|
+
*/
|
|
277
|
+
export async function resetTracking() {
|
|
278
|
+
const client = getRedis();
|
|
279
|
+
|
|
280
|
+
await client.del(DELEGATION_KEY);
|
|
281
|
+
await client.del(DIRECT_ACTION_KEY);
|
|
282
|
+
await client.del('boss:onyx:events');
|
|
283
|
+
await client.del(LAST_ALERT_KEY);
|
|
284
|
+
|
|
285
|
+
console.log('ONYX delegation tracking has been reset');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Generate a comprehensive delegation report
|
|
290
|
+
* @param {object} options - Report options
|
|
291
|
+
* @returns {Promise<object>} Detailed report
|
|
292
|
+
*/
|
|
293
|
+
export async function generateReport(options = {}) {
|
|
294
|
+
const stats = await getDelegationStats();
|
|
295
|
+
const recentEvents = await getRecentEvents(options.eventLimit || 50);
|
|
296
|
+
|
|
297
|
+
// Calculate time-based metrics
|
|
298
|
+
const now = new Date();
|
|
299
|
+
const oneDayAgo = new Date(now - 24 * 60 * 60 * 1000);
|
|
300
|
+
const oneWeekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);
|
|
301
|
+
|
|
302
|
+
const last24h = recentEvents.filter(e => new Date(e.timestamp) > oneDayAgo);
|
|
303
|
+
const last7d = recentEvents.filter(e => new Date(e.timestamp) > oneWeekAgo);
|
|
304
|
+
|
|
305
|
+
const delegations24h = last24h.filter(e => e.type === 'delegation').length;
|
|
306
|
+
const directActions24h = last24h.filter(e => e.type === 'direct_action').length;
|
|
307
|
+
const total24h = delegations24h + directActions24h;
|
|
308
|
+
const ratio24h = total24h > 0 ? delegations24h / total24h : 0;
|
|
309
|
+
|
|
310
|
+
const delegations7d = last7d.filter(e => e.type === 'delegation').length;
|
|
311
|
+
const directActions7d = last7d.filter(e => e.type === 'direct_action').length;
|
|
312
|
+
const total7d = delegations7d + directActions7d;
|
|
313
|
+
const ratio7d = total7d > 0 ? delegations7d / total7d : 0;
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
overall: stats,
|
|
317
|
+
time_periods: {
|
|
318
|
+
last_24h: {
|
|
319
|
+
delegations: delegations24h,
|
|
320
|
+
direct_actions: directActions24h,
|
|
321
|
+
total: total24h,
|
|
322
|
+
ratio: ratio24h,
|
|
323
|
+
percentage: (ratio24h * 100).toFixed(2)
|
|
324
|
+
},
|
|
325
|
+
last_7d: {
|
|
326
|
+
delegations: delegations7d,
|
|
327
|
+
direct_actions: directActions7d,
|
|
328
|
+
total: total7d,
|
|
329
|
+
ratio: ratio7d,
|
|
330
|
+
percentage: (ratio7d * 100).toFixed(2)
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
recent_events: recentEvents.slice(0, 10),
|
|
334
|
+
generated_at: now.toISOString()
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get formatted status for display
|
|
340
|
+
* @returns {Promise<string>} Formatted status string
|
|
341
|
+
*/
|
|
342
|
+
export async function getFormattedStatus() {
|
|
343
|
+
const report = await generateReport();
|
|
344
|
+
const stats = report.overall;
|
|
345
|
+
|
|
346
|
+
const statusIcon = stats.meets_threshold ? '✅' : '⚠️';
|
|
347
|
+
|
|
348
|
+
return `
|
|
349
|
+
${statusIcon} ONYX DELEGATION MONITOR
|
|
350
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
351
|
+
|
|
352
|
+
OVERALL STATISTICS:
|
|
353
|
+
Delegation Ratio: ${stats.delegation_percentage}% (${stats.total_delegations}/${stats.total_actions})
|
|
354
|
+
Alert Threshold: ${stats.threshold_percentage}%
|
|
355
|
+
Status: ${stats.meets_threshold ? 'PASSING ✅' : 'BELOW THRESHOLD ⚠️'}
|
|
356
|
+
|
|
357
|
+
RECENT ACTIVITY (Last 24h):
|
|
358
|
+
Delegations: ${report.time_periods.last_24h.delegations}
|
|
359
|
+
Direct Actions: ${report.time_periods.last_24h.direct_actions}
|
|
360
|
+
Ratio: ${report.time_periods.last_24h.percentage}%
|
|
361
|
+
|
|
362
|
+
AGENT BREAKDOWN:
|
|
363
|
+
${Object.entries(stats.agent_breakdown)
|
|
364
|
+
.sort((a, b) => b[1] - a[1])
|
|
365
|
+
.map(([agent, count]) => ` ${agent}: ${count} tasks`)
|
|
366
|
+
.join('\n') || ' No delegations yet'}
|
|
367
|
+
|
|
368
|
+
DIRECT ACTION BREAKDOWN:
|
|
369
|
+
${Object.entries(stats.direct_action_breakdown)
|
|
370
|
+
.sort((a, b) => b[1] - a[1])
|
|
371
|
+
.map(([type, count]) => ` ${type}: ${count} actions`)
|
|
372
|
+
.join('\n') || ' No direct actions yet'}
|
|
373
|
+
|
|
374
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
375
|
+
|
|
376
|
+
Alert Log: ${ALERT_LOG}
|
|
377
|
+
Run 'boss-claude onyx-report' for detailed analysis
|
|
378
|
+
`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export { ALERT_LOG };
|