opc-agent 0.9.0 ā 1.1.0
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/CHANGELOG.md +7 -0
- package/README.md +145 -144
- package/dist/channels/discord.d.ts +44 -0
- package/dist/channels/discord.js +189 -0
- package/dist/channels/feishu.d.ts +47 -0
- package/dist/channels/feishu.js +221 -0
- package/dist/channels/web.js +118 -39
- package/dist/cli.js +109 -1
- package/dist/core/errors.d.ts +68 -0
- package/dist/core/errors.js +149 -0
- package/dist/core/security.d.ts +48 -0
- package/dist/core/security.js +146 -0
- package/dist/core/watch.d.ts +73 -0
- package/dist/core/watch.js +106 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +32 -1
- package/dist/plugins/index.d.ts +24 -3
- package/dist/plugins/index.js +109 -4
- package/dist/schema/oad.d.ts +54 -0
- package/dist/schema/oad.js +6 -1
- package/package.json +1 -1
- package/src/channels/discord.ts +192 -0
- package/src/channels/feishu.ts +236 -0
- package/src/channels/web.ts +118 -39
- package/src/cli.ts +108 -1
- package/src/core/errors.ts +148 -0
- package/src/core/security.ts +171 -0
- package/src/core/watch.ts +178 -0
- package/src/index.ts +15 -0
- package/src/plugins/index.ts +128 -7
- package/src/schema/oad.ts +6 -0
- package/tests/errors.test.ts +83 -0
- package/tests/security.test.ts +60 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OPC Agent Error Hierarchy - v1.0.0
|
|
4
|
+
* Custom error classes with user-friendly messages and recovery hints.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TimeoutError = exports.SecurityError = exports.RateLimitError = exports.PluginError = exports.ChannelError = exports.ConfigError = exports.ValidationError = exports.ProviderError = exports.OPCError = void 0;
|
|
8
|
+
exports.wrapError = wrapError;
|
|
9
|
+
exports.formatErrorForUser = formatErrorForUser;
|
|
10
|
+
class OPCError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
hint;
|
|
13
|
+
context;
|
|
14
|
+
timestamp = Date.now();
|
|
15
|
+
constructor(message, opts) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'OPCError';
|
|
18
|
+
this.code = opts?.code ?? 'OPC_UNKNOWN';
|
|
19
|
+
this.hint = opts?.hint;
|
|
20
|
+
this.context = opts?.context;
|
|
21
|
+
if (opts?.cause)
|
|
22
|
+
this.cause = opts.cause;
|
|
23
|
+
}
|
|
24
|
+
toJSON() {
|
|
25
|
+
return { name: this.name, code: this.code, message: this.message, hint: this.hint, context: this.context, timestamp: this.timestamp };
|
|
26
|
+
}
|
|
27
|
+
toUserMessage() {
|
|
28
|
+
return this.hint ? `${this.message}\nš” ${this.hint}` : this.message;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.OPCError = OPCError;
|
|
32
|
+
class ProviderError extends OPCError {
|
|
33
|
+
provider;
|
|
34
|
+
statusCode;
|
|
35
|
+
constructor(provider, message, opts) {
|
|
36
|
+
super(message, {
|
|
37
|
+
code: 'OPC_PROVIDER_ERROR',
|
|
38
|
+
hint: opts?.hint ?? `Check your API key and network connection for ${provider}.`,
|
|
39
|
+
context: { provider, statusCode: opts?.statusCode },
|
|
40
|
+
cause: opts?.cause,
|
|
41
|
+
});
|
|
42
|
+
this.name = 'ProviderError';
|
|
43
|
+
this.provider = provider;
|
|
44
|
+
this.statusCode = opts?.statusCode;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.ProviderError = ProviderError;
|
|
48
|
+
class ValidationError extends OPCError {
|
|
49
|
+
field;
|
|
50
|
+
errors;
|
|
51
|
+
constructor(message, errors = [], field) {
|
|
52
|
+
super(message, {
|
|
53
|
+
code: 'OPC_VALIDATION_ERROR',
|
|
54
|
+
hint: 'Check your OAD configuration file for missing or invalid fields.',
|
|
55
|
+
context: { field, errors },
|
|
56
|
+
});
|
|
57
|
+
this.name = 'ValidationError';
|
|
58
|
+
this.field = field;
|
|
59
|
+
this.errors = errors;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.ValidationError = ValidationError;
|
|
63
|
+
class ConfigError extends OPCError {
|
|
64
|
+
constructor(message, hint) {
|
|
65
|
+
super(message, { code: 'OPC_CONFIG_ERROR', hint: hint ?? 'Check your oad.yaml and .env files.' });
|
|
66
|
+
this.name = 'ConfigError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.ConfigError = ConfigError;
|
|
70
|
+
class ChannelError extends OPCError {
|
|
71
|
+
channelType;
|
|
72
|
+
constructor(channelType, message, opts) {
|
|
73
|
+
super(message, {
|
|
74
|
+
code: 'OPC_CHANNEL_ERROR',
|
|
75
|
+
hint: opts?.hint ?? `Check configuration for the ${channelType} channel.`,
|
|
76
|
+
context: { channelType },
|
|
77
|
+
cause: opts?.cause,
|
|
78
|
+
});
|
|
79
|
+
this.name = 'ChannelError';
|
|
80
|
+
this.channelType = channelType;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.ChannelError = ChannelError;
|
|
84
|
+
class PluginError extends OPCError {
|
|
85
|
+
pluginName;
|
|
86
|
+
constructor(pluginName, message, opts) {
|
|
87
|
+
super(message, {
|
|
88
|
+
code: 'OPC_PLUGIN_ERROR',
|
|
89
|
+
hint: opts?.hint ?? `Check plugin "${pluginName}" configuration.`,
|
|
90
|
+
context: { pluginName },
|
|
91
|
+
cause: opts?.cause,
|
|
92
|
+
});
|
|
93
|
+
this.name = 'PluginError';
|
|
94
|
+
this.pluginName = pluginName;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.PluginError = PluginError;
|
|
98
|
+
class RateLimitError extends OPCError {
|
|
99
|
+
retryAfterMs;
|
|
100
|
+
constructor(message, retryAfterMs) {
|
|
101
|
+
super(message ?? 'Rate limit exceeded. Please slow down.', {
|
|
102
|
+
code: 'OPC_RATE_LIMIT',
|
|
103
|
+
hint: retryAfterMs ? `Try again in ${Math.ceil(retryAfterMs / 1000)} seconds.` : 'Please wait before sending more messages.',
|
|
104
|
+
context: { retryAfterMs },
|
|
105
|
+
});
|
|
106
|
+
this.name = 'RateLimitError';
|
|
107
|
+
this.retryAfterMs = retryAfterMs;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.RateLimitError = RateLimitError;
|
|
111
|
+
class SecurityError extends OPCError {
|
|
112
|
+
constructor(message, hint) {
|
|
113
|
+
super(message, { code: 'OPC_SECURITY_ERROR', hint: hint ?? 'This request was blocked for security reasons.' });
|
|
114
|
+
this.name = 'SecurityError';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.SecurityError = SecurityError;
|
|
118
|
+
class TimeoutError extends OPCError {
|
|
119
|
+
constructor(operation, timeoutMs) {
|
|
120
|
+
super(`Operation "${operation}" timed out after ${timeoutMs}ms`, {
|
|
121
|
+
code: 'OPC_TIMEOUT',
|
|
122
|
+
hint: 'The operation took too long. Try again or increase the timeout.',
|
|
123
|
+
context: { operation, timeoutMs },
|
|
124
|
+
});
|
|
125
|
+
this.name = 'TimeoutError';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.TimeoutError = TimeoutError;
|
|
129
|
+
/**
|
|
130
|
+
* Wrap an unknown thrown value into an OPCError.
|
|
131
|
+
*/
|
|
132
|
+
function wrapError(err, fallbackMessage = 'An unexpected error occurred') {
|
|
133
|
+
if (err instanceof OPCError)
|
|
134
|
+
return err;
|
|
135
|
+
if (err instanceof Error)
|
|
136
|
+
return new OPCError(err.message, { cause: err });
|
|
137
|
+
return new OPCError(typeof err === 'string' ? err : fallbackMessage);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Format error for user display (no stack traces).
|
|
141
|
+
*/
|
|
142
|
+
function formatErrorForUser(err) {
|
|
143
|
+
if (err instanceof OPCError)
|
|
144
|
+
return err.toUserMessage();
|
|
145
|
+
if (err instanceof Error)
|
|
146
|
+
return err.message;
|
|
147
|
+
return String(err);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Hardening Module - v1.0.0
|
|
3
|
+
* Input sanitization, CORS, security headers, API key rotation.
|
|
4
|
+
*/
|
|
5
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
6
|
+
export declare function sanitizeInput(input: string): string;
|
|
7
|
+
export declare function detectInjection(input: string): {
|
|
8
|
+
safe: boolean;
|
|
9
|
+
threats: string[];
|
|
10
|
+
};
|
|
11
|
+
export interface SecurityHeadersConfig {
|
|
12
|
+
contentSecurityPolicy?: string;
|
|
13
|
+
enableHSTS?: boolean;
|
|
14
|
+
frameDeny?: boolean;
|
|
15
|
+
xssProtection?: boolean;
|
|
16
|
+
noSniff?: boolean;
|
|
17
|
+
referrerPolicy?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function securityHeaders(config?: SecurityHeadersConfig): (_req: Request, res: Response, next: NextFunction) => void;
|
|
20
|
+
export interface CORSConfig {
|
|
21
|
+
origins?: string[];
|
|
22
|
+
methods?: string[];
|
|
23
|
+
allowHeaders?: string[];
|
|
24
|
+
credentials?: boolean;
|
|
25
|
+
maxAge?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function corsMiddleware(config?: CORSConfig): (req: Request, res: Response, next: NextFunction) => void;
|
|
28
|
+
export interface APIKeyEntry {
|
|
29
|
+
key: string;
|
|
30
|
+
label?: string;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
expiresAt?: number;
|
|
33
|
+
active: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare class APIKeyManager {
|
|
36
|
+
private keys;
|
|
37
|
+
addKey(key: string, opts?: {
|
|
38
|
+
label?: string;
|
|
39
|
+
expiresAt?: number;
|
|
40
|
+
}): void;
|
|
41
|
+
revokeKey(key: string): boolean;
|
|
42
|
+
isValid(key: string): boolean;
|
|
43
|
+
rotateKey(oldKey: string, newKey: string): boolean;
|
|
44
|
+
listActive(): APIKeyEntry[];
|
|
45
|
+
cleanup(): number;
|
|
46
|
+
}
|
|
47
|
+
export declare function inputValidation(): (req: Request, res: Response, next: NextFunction) => void;
|
|
48
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security Hardening Module - v1.0.0
|
|
4
|
+
* Input sanitization, CORS, security headers, API key rotation.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.APIKeyManager = void 0;
|
|
8
|
+
exports.sanitizeInput = sanitizeInput;
|
|
9
|
+
exports.detectInjection = detectInjection;
|
|
10
|
+
exports.securityHeaders = securityHeaders;
|
|
11
|
+
exports.corsMiddleware = corsMiddleware;
|
|
12
|
+
exports.inputValidation = inputValidation;
|
|
13
|
+
// āā Input Sanitization āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
14
|
+
const XSS_PATTERNS = [
|
|
15
|
+
/<script\b[^>]*>[\s\S]*?<\/script>/gi,
|
|
16
|
+
/javascript:/gi,
|
|
17
|
+
/on\w+\s*=/gi,
|
|
18
|
+
/<iframe\b/gi,
|
|
19
|
+
/<object\b/gi,
|
|
20
|
+
/<embed\b/gi,
|
|
21
|
+
/<form\b/gi,
|
|
22
|
+
];
|
|
23
|
+
const SQL_PATTERNS = [
|
|
24
|
+
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|EXEC)\b.*\b(FROM|INTO|TABLE|SET|WHERE|ALL)\b)/gi,
|
|
25
|
+
/(--|;)\s*(DROP|ALTER|DELETE)/gi,
|
|
26
|
+
];
|
|
27
|
+
function sanitizeInput(input) {
|
|
28
|
+
let clean = input;
|
|
29
|
+
for (const pattern of XSS_PATTERNS) {
|
|
30
|
+
clean = clean.replace(pattern, '');
|
|
31
|
+
}
|
|
32
|
+
// Encode dangerous HTML entities
|
|
33
|
+
clean = clean.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
34
|
+
return clean;
|
|
35
|
+
}
|
|
36
|
+
function detectInjection(input) {
|
|
37
|
+
const threats = [];
|
|
38
|
+
for (const pattern of XSS_PATTERNS) {
|
|
39
|
+
if (pattern.test(input))
|
|
40
|
+
threats.push('xss');
|
|
41
|
+
pattern.lastIndex = 0;
|
|
42
|
+
}
|
|
43
|
+
for (const pattern of SQL_PATTERNS) {
|
|
44
|
+
if (pattern.test(input))
|
|
45
|
+
threats.push('sql_injection');
|
|
46
|
+
pattern.lastIndex = 0;
|
|
47
|
+
}
|
|
48
|
+
return { safe: threats.length === 0, threats: [...new Set(threats)] };
|
|
49
|
+
}
|
|
50
|
+
function securityHeaders(config) {
|
|
51
|
+
const csp = config?.contentSecurityPolicy ?? "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'";
|
|
52
|
+
return (_req, res, next) => {
|
|
53
|
+
res.setHeader('Content-Security-Policy', csp);
|
|
54
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
55
|
+
res.setHeader('X-Frame-Options', config?.frameDeny !== false ? 'DENY' : 'SAMEORIGIN');
|
|
56
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
57
|
+
res.setHeader('Referrer-Policy', config?.referrerPolicy ?? 'strict-origin-when-cross-origin');
|
|
58
|
+
if (config?.enableHSTS !== false) {
|
|
59
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
60
|
+
}
|
|
61
|
+
res.removeHeader('X-Powered-By');
|
|
62
|
+
next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function corsMiddleware(config) {
|
|
66
|
+
const origins = config?.origins ?? ['*'];
|
|
67
|
+
const methods = config?.methods ?? ['GET', 'POST', 'OPTIONS'];
|
|
68
|
+
const headers = config?.allowHeaders ?? ['Content-Type', 'Authorization'];
|
|
69
|
+
return (req, res, next) => {
|
|
70
|
+
const origin = req.headers.origin ?? '';
|
|
71
|
+
if (origins.includes('*') || origins.includes(origin)) {
|
|
72
|
+
res.setHeader('Access-Control-Allow-Origin', origins.includes('*') ? '*' : origin);
|
|
73
|
+
}
|
|
74
|
+
res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
|
|
75
|
+
res.setHeader('Access-Control-Allow-Headers', headers.join(', '));
|
|
76
|
+
if (config?.credentials)
|
|
77
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
78
|
+
if (config?.maxAge)
|
|
79
|
+
res.setHeader('Access-Control-Max-Age', String(config.maxAge));
|
|
80
|
+
if (req.method === 'OPTIONS') {
|
|
81
|
+
res.status(204).end();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
next();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
class APIKeyManager {
|
|
88
|
+
keys = [];
|
|
89
|
+
addKey(key, opts) {
|
|
90
|
+
this.keys.push({ key, label: opts?.label, createdAt: Date.now(), expiresAt: opts?.expiresAt, active: true });
|
|
91
|
+
}
|
|
92
|
+
revokeKey(key) {
|
|
93
|
+
const entry = this.keys.find(k => k.key === key);
|
|
94
|
+
if (entry) {
|
|
95
|
+
entry.active = false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
isValid(key) {
|
|
101
|
+
const entry = this.keys.find(k => k.key === key);
|
|
102
|
+
if (!entry || !entry.active)
|
|
103
|
+
return false;
|
|
104
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
105
|
+
entry.active = false;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
rotateKey(oldKey, newKey) {
|
|
111
|
+
const entry = this.keys.find(k => k.key === oldKey && k.active);
|
|
112
|
+
if (!entry)
|
|
113
|
+
return false;
|
|
114
|
+
entry.active = false;
|
|
115
|
+
this.addKey(newKey, { label: entry.label });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
listActive() {
|
|
119
|
+
return this.keys.filter(k => k.active && (!k.expiresAt || Date.now() <= k.expiresAt));
|
|
120
|
+
}
|
|
121
|
+
cleanup() {
|
|
122
|
+
const before = this.keys.length;
|
|
123
|
+
this.keys = this.keys.filter(k => k.active && (!k.expiresAt || Date.now() <= k.expiresAt));
|
|
124
|
+
return before - this.keys.length;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.APIKeyManager = APIKeyManager;
|
|
128
|
+
// āā Input Validation Middleware āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
129
|
+
function inputValidation() {
|
|
130
|
+
return (req, res, next) => {
|
|
131
|
+
if (req.body?.message && typeof req.body.message === 'string') {
|
|
132
|
+
const check = detectInjection(req.body.message);
|
|
133
|
+
if (!check.safe) {
|
|
134
|
+
res.status(400).json({ error: 'Input contains potentially unsafe content', threats: check.threats });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Limit message size
|
|
138
|
+
if (req.body.message.length > 100_000) {
|
|
139
|
+
res.status(413).json({ error: 'Message too large (max 100KB)' });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
next();
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
/**
|
|
3
|
+
* ProcessWatcher ā Background process output monitoring with pattern matching.
|
|
4
|
+
*
|
|
5
|
+
* Inspired by Hermes Agent's watch_patterns feature.
|
|
6
|
+
* Set patterns to watch for in background process output and get callbacks
|
|
7
|
+
* when they match ā no polling needed.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const watcher = new ProcessWatcher();
|
|
11
|
+
* watcher.watch(childProcess.stdout, {
|
|
12
|
+
* patterns: [
|
|
13
|
+
* { regex: /listening on port (\d+)/, label: 'server-ready' },
|
|
14
|
+
* { regex: /error|Error|ERROR/, label: 'error-detected' },
|
|
15
|
+
* { regex: /build completed/, label: 'build-done', once: true },
|
|
16
|
+
* ],
|
|
17
|
+
* onMatch: (match) => console.log(`[${match.label}] ${match.line}`),
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
export interface WatchPattern {
|
|
21
|
+
/** Regex to match against each line of output */
|
|
22
|
+
regex: RegExp;
|
|
23
|
+
/** Human-readable label for this pattern */
|
|
24
|
+
label: string;
|
|
25
|
+
/** If true, auto-remove after first match */
|
|
26
|
+
once?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface WatchMatch {
|
|
29
|
+
/** Pattern label */
|
|
30
|
+
label: string;
|
|
31
|
+
/** The full line that matched */
|
|
32
|
+
line: string;
|
|
33
|
+
/** Regex match groups */
|
|
34
|
+
groups: string[];
|
|
35
|
+
/** Timestamp of match */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
/** Stream source */
|
|
38
|
+
stream: 'stdout' | 'stderr';
|
|
39
|
+
}
|
|
40
|
+
export interface WatchOptions {
|
|
41
|
+
/** Patterns to match */
|
|
42
|
+
patterns: WatchPattern[];
|
|
43
|
+
/** Callback on match */
|
|
44
|
+
onMatch: (match: WatchMatch) => void;
|
|
45
|
+
/** Optional: max matches to keep in history (default: 100) */
|
|
46
|
+
maxHistory?: number;
|
|
47
|
+
}
|
|
48
|
+
export declare class ProcessWatcher extends EventEmitter {
|
|
49
|
+
private watchers;
|
|
50
|
+
private watcherIdCounter;
|
|
51
|
+
/**
|
|
52
|
+
* Start watching a readable stream for patterns.
|
|
53
|
+
* Returns a watcher ID that can be used to stop watching.
|
|
54
|
+
*/
|
|
55
|
+
watch(stream: NodeJS.ReadableStream, options: WatchOptions, streamName?: 'stdout' | 'stderr'): string;
|
|
56
|
+
/**
|
|
57
|
+
* Watch both stdout and stderr of a ChildProcess.
|
|
58
|
+
*/
|
|
59
|
+
watchProcess(proc: {
|
|
60
|
+
stdout?: NodeJS.ReadableStream | null;
|
|
61
|
+
stderr?: NodeJS.ReadableStream | null;
|
|
62
|
+
}, options: WatchOptions): string[];
|
|
63
|
+
/** Stop a specific watcher */
|
|
64
|
+
unwatch(id: string): void;
|
|
65
|
+
/** Get match history for a watcher */
|
|
66
|
+
getHistory(id: string): WatchMatch[];
|
|
67
|
+
/** Add a pattern to an existing watcher */
|
|
68
|
+
addPattern(id: string, pattern: WatchPattern): void;
|
|
69
|
+
/** Remove a pattern by label from a watcher */
|
|
70
|
+
removePattern(id: string, label: string): void;
|
|
71
|
+
private matchLine;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProcessWatcher = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
class ProcessWatcher extends events_1.EventEmitter {
|
|
6
|
+
watchers = new Map();
|
|
7
|
+
watcherIdCounter = 0;
|
|
8
|
+
/**
|
|
9
|
+
* Start watching a readable stream for patterns.
|
|
10
|
+
* Returns a watcher ID that can be used to stop watching.
|
|
11
|
+
*/
|
|
12
|
+
watch(stream, options, streamName = 'stdout') {
|
|
13
|
+
const id = `watcher_${++this.watcherIdCounter}`;
|
|
14
|
+
const state = {
|
|
15
|
+
patterns: [...options.patterns],
|
|
16
|
+
onMatch: options.onMatch,
|
|
17
|
+
history: [],
|
|
18
|
+
maxHistory: options.maxHistory ?? 100,
|
|
19
|
+
};
|
|
20
|
+
this.watchers.set(id, state);
|
|
21
|
+
let buffer = '';
|
|
22
|
+
const onData = (chunk) => {
|
|
23
|
+
buffer += chunk.toString();
|
|
24
|
+
const lines = buffer.split('\n');
|
|
25
|
+
buffer = lines.pop() ?? ''; // Keep incomplete line in buffer
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
this.matchLine(id, line, streamName);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
stream.on('data', onData);
|
|
31
|
+
stream.on('end', () => {
|
|
32
|
+
// Process remaining buffer
|
|
33
|
+
if (buffer)
|
|
34
|
+
this.matchLine(id, buffer, streamName);
|
|
35
|
+
this.watchers.delete(id);
|
|
36
|
+
this.emit('watcher:end', id);
|
|
37
|
+
});
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Watch both stdout and stderr of a ChildProcess.
|
|
42
|
+
*/
|
|
43
|
+
watchProcess(proc, options) {
|
|
44
|
+
const ids = [];
|
|
45
|
+
if (proc.stdout)
|
|
46
|
+
ids.push(this.watch(proc.stdout, options, 'stdout'));
|
|
47
|
+
if (proc.stderr)
|
|
48
|
+
ids.push(this.watch(proc.stderr, options, 'stderr'));
|
|
49
|
+
return ids;
|
|
50
|
+
}
|
|
51
|
+
/** Stop a specific watcher */
|
|
52
|
+
unwatch(id) {
|
|
53
|
+
this.watchers.delete(id);
|
|
54
|
+
}
|
|
55
|
+
/** Get match history for a watcher */
|
|
56
|
+
getHistory(id) {
|
|
57
|
+
return this.watchers.get(id)?.history ?? [];
|
|
58
|
+
}
|
|
59
|
+
/** Add a pattern to an existing watcher */
|
|
60
|
+
addPattern(id, pattern) {
|
|
61
|
+
const state = this.watchers.get(id);
|
|
62
|
+
if (state)
|
|
63
|
+
state.patterns.push(pattern);
|
|
64
|
+
}
|
|
65
|
+
/** Remove a pattern by label from a watcher */
|
|
66
|
+
removePattern(id, label) {
|
|
67
|
+
const state = this.watchers.get(id);
|
|
68
|
+
if (state) {
|
|
69
|
+
state.patterns = state.patterns.filter(p => p.label !== label);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
matchLine(watcherId, line, stream) {
|
|
73
|
+
const state = this.watchers.get(watcherId);
|
|
74
|
+
if (!state)
|
|
75
|
+
return;
|
|
76
|
+
const toRemove = [];
|
|
77
|
+
for (let i = 0; i < state.patterns.length; i++) {
|
|
78
|
+
const pattern = state.patterns[i];
|
|
79
|
+
const m = line.match(pattern.regex);
|
|
80
|
+
if (m) {
|
|
81
|
+
const match = {
|
|
82
|
+
label: pattern.label,
|
|
83
|
+
line,
|
|
84
|
+
groups: m.slice(1),
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
stream,
|
|
87
|
+
};
|
|
88
|
+
state.history.push(match);
|
|
89
|
+
if (state.history.length > state.maxHistory) {
|
|
90
|
+
state.history.shift();
|
|
91
|
+
}
|
|
92
|
+
state.onMatch(match);
|
|
93
|
+
this.emit('match', match);
|
|
94
|
+
if (pattern.once) {
|
|
95
|
+
toRemove.push(i);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Remove once-patterns in reverse order
|
|
100
|
+
for (let i = toRemove.length - 1; i >= 0; i--) {
|
|
101
|
+
state.patterns.splice(toRemove[i], 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.ProcessWatcher = ProcessWatcher;
|
|
106
|
+
//# sourceMappingURL=watch.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -80,4 +80,15 @@ export type { CacheConfig, CacheEntry } from './core/cache';
|
|
|
80
80
|
export { getSupportedLocales } from './i18n';
|
|
81
81
|
export { createDataAnalystConfig } from './templates/data-analyst';
|
|
82
82
|
export { createTeacherConfig } from './templates/teacher';
|
|
83
|
+
export { OPCError, ProviderError, ValidationError, ConfigError, ChannelError, PluginError, RateLimitError, SecurityError, TimeoutError, wrapError, formatErrorForUser } from './core/errors';
|
|
84
|
+
export { sanitizeInput, detectInjection, securityHeaders, corsMiddleware, APIKeyManager, inputValidation } from './core/security';
|
|
85
|
+
export type { SecurityHeadersConfig, CORSConfig, APIKeyEntry } from './core/security';
|
|
86
|
+
export { createLoggingPlugin, createAnalyticsPlugin, createRateLimitPlugin } from './plugins';
|
|
87
|
+
export type { PluginManifest } from './plugins';
|
|
88
|
+
export { FeishuChannel } from './channels/feishu';
|
|
89
|
+
export type { FeishuChannelConfig } from './channels/feishu';
|
|
90
|
+
export { DiscordChannel } from './channels/discord';
|
|
91
|
+
export type { DiscordChannelConfig } from './channels/discord';
|
|
92
|
+
export { ProcessWatcher } from './core/watch';
|
|
93
|
+
export type { WatchPattern, WatchMatch, WatchOptions } from './core/watch';
|
|
83
94
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.EmailChannel = exports.compose = exports.AgentPipeline = exports.Orchestrator = exports.getActiveSessions = exports.createAuthMiddleware = exports.installAgent = exports.publishAgent = exports.deployToHermes = exports.KnowledgeBase = exports.addMessages = exports.detectLocale = exports.getLocale = exports.setLocale = exports.t = exports.LazyLoader = exports.RequestBatcher = exports.ConnectionPool = exports.VersionManager = exports.WebhookChannel = exports.VoiceChannel = exports.HITLManager = exports.AgentRegistry = exports.WorkflowEngine = exports.Analytics = exports.Sandbox = exports.PluginManager = exports.createMCPTool = exports.MCPToolRegistry = exports.Room = exports.SUPPORTED_PROVIDERS = exports.createProvider = exports.MRGConfigReader = exports.ValueTracker = exports.TrustManager = exports.DeepBrainMemoryStore = exports.InMemoryStore = exports.SkillRegistry = exports.BaseSkill = exports.WebSocketChannel = exports.TelegramChannel = exports.WebChannel = exports.BaseChannel = exports.OADSchema = exports.validateOAD = exports.loadOAD = exports.Logger = exports.truncateOutput = exports.AgentRuntime = exports.BaseAgent = void 0;
|
|
4
|
-
exports.createTeacherConfig = exports.createDataAnalystConfig = exports.getSupportedLocales = exports.LLMCache = exports.RateLimiter = exports.AnalyticsEngine = exports.formatReport = exports.loadTestCases = exports.runTests = exports.DocumentSkill = exports.SchedulerSkill = exports.WebhookTriggerSkill = exports.HttpSkill = exports.TextAnalysisTool = exports.JsonTransformTool = exports.DateTimeTool = exports.CalculatorTool = exports.WeChatChannel = exports.SlackChannel = void 0;
|
|
4
|
+
exports.ProcessWatcher = exports.DiscordChannel = exports.FeishuChannel = exports.createRateLimitPlugin = exports.createAnalyticsPlugin = exports.createLoggingPlugin = exports.inputValidation = exports.APIKeyManager = exports.corsMiddleware = exports.securityHeaders = exports.detectInjection = exports.sanitizeInput = exports.formatErrorForUser = exports.wrapError = exports.TimeoutError = exports.SecurityError = exports.RateLimitError = exports.PluginError = exports.ChannelError = exports.ConfigError = exports.ValidationError = exports.ProviderError = exports.OPCError = exports.createTeacherConfig = exports.createDataAnalystConfig = exports.getSupportedLocales = exports.LLMCache = exports.RateLimiter = exports.AnalyticsEngine = exports.formatReport = exports.loadTestCases = exports.runTests = exports.DocumentSkill = exports.SchedulerSkill = exports.WebhookTriggerSkill = exports.HttpSkill = exports.TextAnalysisTool = exports.JsonTransformTool = exports.DateTimeTool = exports.CalculatorTool = exports.WeChatChannel = exports.SlackChannel = void 0;
|
|
5
5
|
// OPC Agent ā Open Agent Framework
|
|
6
6
|
var agent_1 = require("./core/agent");
|
|
7
7
|
Object.defineProperty(exports, "BaseAgent", { enumerable: true, get: function () { return agent_1.BaseAgent; } });
|
|
@@ -132,4 +132,35 @@ var data_analyst_1 = require("./templates/data-analyst");
|
|
|
132
132
|
Object.defineProperty(exports, "createDataAnalystConfig", { enumerable: true, get: function () { return data_analyst_1.createDataAnalystConfig; } });
|
|
133
133
|
var teacher_1 = require("./templates/teacher");
|
|
134
134
|
Object.defineProperty(exports, "createTeacherConfig", { enumerable: true, get: function () { return teacher_1.createTeacherConfig; } });
|
|
135
|
+
// v1.0.0 modules
|
|
136
|
+
var errors_1 = require("./core/errors");
|
|
137
|
+
Object.defineProperty(exports, "OPCError", { enumerable: true, get: function () { return errors_1.OPCError; } });
|
|
138
|
+
Object.defineProperty(exports, "ProviderError", { enumerable: true, get: function () { return errors_1.ProviderError; } });
|
|
139
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_1.ValidationError; } });
|
|
140
|
+
Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return errors_1.ConfigError; } });
|
|
141
|
+
Object.defineProperty(exports, "ChannelError", { enumerable: true, get: function () { return errors_1.ChannelError; } });
|
|
142
|
+
Object.defineProperty(exports, "PluginError", { enumerable: true, get: function () { return errors_1.PluginError; } });
|
|
143
|
+
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } });
|
|
144
|
+
Object.defineProperty(exports, "SecurityError", { enumerable: true, get: function () { return errors_1.SecurityError; } });
|
|
145
|
+
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return errors_1.TimeoutError; } });
|
|
146
|
+
Object.defineProperty(exports, "wrapError", { enumerable: true, get: function () { return errors_1.wrapError; } });
|
|
147
|
+
Object.defineProperty(exports, "formatErrorForUser", { enumerable: true, get: function () { return errors_1.formatErrorForUser; } });
|
|
148
|
+
var security_1 = require("./core/security");
|
|
149
|
+
Object.defineProperty(exports, "sanitizeInput", { enumerable: true, get: function () { return security_1.sanitizeInput; } });
|
|
150
|
+
Object.defineProperty(exports, "detectInjection", { enumerable: true, get: function () { return security_1.detectInjection; } });
|
|
151
|
+
Object.defineProperty(exports, "securityHeaders", { enumerable: true, get: function () { return security_1.securityHeaders; } });
|
|
152
|
+
Object.defineProperty(exports, "corsMiddleware", { enumerable: true, get: function () { return security_1.corsMiddleware; } });
|
|
153
|
+
Object.defineProperty(exports, "APIKeyManager", { enumerable: true, get: function () { return security_1.APIKeyManager; } });
|
|
154
|
+
Object.defineProperty(exports, "inputValidation", { enumerable: true, get: function () { return security_1.inputValidation; } });
|
|
155
|
+
var plugins_2 = require("./plugins");
|
|
156
|
+
Object.defineProperty(exports, "createLoggingPlugin", { enumerable: true, get: function () { return plugins_2.createLoggingPlugin; } });
|
|
157
|
+
Object.defineProperty(exports, "createAnalyticsPlugin", { enumerable: true, get: function () { return plugins_2.createAnalyticsPlugin; } });
|
|
158
|
+
Object.defineProperty(exports, "createRateLimitPlugin", { enumerable: true, get: function () { return plugins_2.createRateLimitPlugin; } });
|
|
159
|
+
// v1.1.0 modules
|
|
160
|
+
var feishu_1 = require("./channels/feishu");
|
|
161
|
+
Object.defineProperty(exports, "FeishuChannel", { enumerable: true, get: function () { return feishu_1.FeishuChannel; } });
|
|
162
|
+
var discord_1 = require("./channels/discord");
|
|
163
|
+
Object.defineProperty(exports, "DiscordChannel", { enumerable: true, get: function () { return discord_1.DiscordChannel; } });
|
|
164
|
+
var watch_1 = require("./core/watch");
|
|
165
|
+
Object.defineProperty(exports, "ProcessWatcher", { enumerable: true, get: function () { return watch_1.ProcessWatcher; } });
|
|
135
166
|
//# sourceMappingURL=index.js.map
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type { ISkill, IChannel } from '../core/types';
|
|
1
|
+
import type { ISkill, IChannel, Message } from '../core/types';
|
|
2
2
|
import type { MCPTool } from '../tools/mcp';
|
|
3
3
|
/**
|
|
4
|
-
* Plugin lifecycle hooks.
|
|
4
|
+
* Plugin lifecycle hooks - v1.0.0
|
|
5
5
|
*/
|
|
6
6
|
export interface PluginHooks {
|
|
7
|
+
onInit?: () => Promise<void>;
|
|
8
|
+
onMessage?: (message: Message) => Promise<Message | void>;
|
|
9
|
+
onResponse?: (message: Message, response: Message) => Promise<Message | void>;
|
|
10
|
+
onError?: (error: Error, context?: Record<string, unknown>) => Promise<void>;
|
|
11
|
+
onShutdown?: () => Promise<void>;
|
|
7
12
|
beforeInit?: () => Promise<void>;
|
|
8
13
|
afterInit?: () => Promise<void>;
|
|
9
14
|
beforeMessage?: (message: {
|
|
@@ -17,7 +22,14 @@ export interface PluginHooks {
|
|
|
17
22
|
beforeShutdown?: () => Promise<void>;
|
|
18
23
|
}
|
|
19
24
|
/**
|
|
20
|
-
* Plugin
|
|
25
|
+
* Plugin manifest in OAD: plugins: [{ name, config }]
|
|
26
|
+
*/
|
|
27
|
+
export interface PluginManifest {
|
|
28
|
+
name: string;
|
|
29
|
+
config?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Plugin interface - extend agent with skills, tools, channels, and lifecycle hooks.
|
|
21
33
|
*/
|
|
22
34
|
export interface IPlugin {
|
|
23
35
|
name: string;
|
|
@@ -30,6 +42,7 @@ export interface IPlugin {
|
|
|
30
42
|
}
|
|
31
43
|
export declare class PluginManager {
|
|
32
44
|
private plugins;
|
|
45
|
+
private logger;
|
|
33
46
|
register(plugin: IPlugin): void;
|
|
34
47
|
unregister(name: string): void;
|
|
35
48
|
get(name: string): IPlugin | undefined;
|
|
@@ -40,8 +53,16 @@ export declare class PluginManager {
|
|
|
40
53
|
}[];
|
|
41
54
|
has(name: string): boolean;
|
|
42
55
|
runHook(hookName: keyof PluginHooks, ...args: unknown[]): Promise<void>;
|
|
56
|
+
runOnInit(): Promise<void>;
|
|
57
|
+
runOnMessage(message: Message): Promise<Message>;
|
|
58
|
+
runOnResponse(message: Message, response: Message): Promise<Message>;
|
|
59
|
+
runOnError(error: Error, context?: Record<string, unknown>): Promise<void>;
|
|
60
|
+
runOnShutdown(): Promise<void>;
|
|
43
61
|
getAllSkills(): ISkill[];
|
|
44
62
|
getAllTools(): MCPTool[];
|
|
45
63
|
getAllChannels(): IChannel[];
|
|
46
64
|
}
|
|
65
|
+
export declare function createLoggingPlugin(): IPlugin;
|
|
66
|
+
export declare function createAnalyticsPlugin(): IPlugin;
|
|
67
|
+
export declare function createRateLimitPlugin(maxPerMinute?: number): IPlugin;
|
|
47
68
|
//# sourceMappingURL=index.d.ts.map
|