@lobu/core 3.0.13 → 3.0.16
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/package.json +3 -3
- package/src/__tests__/encryption.test.ts +0 -103
- package/src/__tests__/fixtures/factories.ts +0 -76
- package/src/__tests__/fixtures/index.ts +0 -9
- package/src/__tests__/fixtures/mock-fetch.ts +0 -32
- package/src/__tests__/fixtures/mock-queue.ts +0 -50
- package/src/__tests__/fixtures/mock-redis.ts +0 -300
- package/src/__tests__/retry.test.ts +0 -134
- package/src/__tests__/sanitize.test.ts +0 -158
- package/src/agent-policy.ts +0 -207
- package/src/agent-store.ts +0 -220
- package/src/api-types.ts +0 -256
- package/src/command-registry.ts +0 -73
- package/src/constants.ts +0 -60
- package/src/errors.ts +0 -220
- package/src/index.ts +0 -131
- package/src/integration-types.ts +0 -26
- package/src/logger.ts +0 -248
- package/src/modules.ts +0 -184
- package/src/otel.ts +0 -307
- package/src/plugin-types.ts +0 -46
- package/src/provider-config-types.ts +0 -54
- package/src/redis/base-store.ts +0 -200
- package/src/sentry.ts +0 -56
- package/src/trace.ts +0 -32
- package/src/types.ts +0 -440
- package/src/utils/encryption.ts +0 -78
- package/src/utils/env.ts +0 -50
- package/src/utils/json.ts +0 -37
- package/src/utils/lock.ts +0 -75
- package/src/utils/mcp-tool-instructions.ts +0 -5
- package/src/utils/retry.ts +0 -91
- package/src/utils/sanitize.ts +0 -127
- package/src/worker/auth.ts +0 -100
- package/src/worker/transport.ts +0 -107
- package/tsconfig.json +0 -20
package/src/utils/retry.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "../logger";
|
|
2
|
-
|
|
3
|
-
const logger = createLogger("retry");
|
|
4
|
-
|
|
5
|
-
export interface RetryOptions {
|
|
6
|
-
maxRetries?: number;
|
|
7
|
-
baseDelay?: number;
|
|
8
|
-
strategy?: "exponential" | "linear";
|
|
9
|
-
jitter?: boolean;
|
|
10
|
-
onRetry?: (attempt: number, error: Error) => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Retry a function with configurable backoff strategy
|
|
15
|
-
*
|
|
16
|
-
* @param fn - The async function to retry
|
|
17
|
-
* @param options - Retry configuration
|
|
18
|
-
* @returns The result of the function
|
|
19
|
-
* @throws The last error if all retries fail
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```typescript
|
|
23
|
-
* // Exponential backoff (default)
|
|
24
|
-
* const result = await retryWithBackoff(
|
|
25
|
-
* () => fetch('https://api.example.com'),
|
|
26
|
-
* { maxRetries: 3, baseDelay: 1000 }
|
|
27
|
-
* );
|
|
28
|
-
*
|
|
29
|
-
* // Linear backoff with jitter
|
|
30
|
-
* const result = await retryWithBackoff(
|
|
31
|
-
* () => createDeployment(),
|
|
32
|
-
* {
|
|
33
|
-
* maxRetries: 3,
|
|
34
|
-
* strategy: 'linear',
|
|
35
|
-
* jitter: true,
|
|
36
|
-
* baseDelay: 2000,
|
|
37
|
-
* onRetry: (attempt, error) => {
|
|
38
|
-
* logger.warn(`Attempt ${attempt} failed: ${error.message}`);
|
|
39
|
-
* }
|
|
40
|
-
* }
|
|
41
|
-
* );
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export async function retryWithBackoff<T>(
|
|
45
|
-
fn: () => Promise<T>,
|
|
46
|
-
options: RetryOptions = {}
|
|
47
|
-
): Promise<T> {
|
|
48
|
-
const {
|
|
49
|
-
maxRetries = 3,
|
|
50
|
-
baseDelay = 1000,
|
|
51
|
-
strategy = "exponential",
|
|
52
|
-
jitter = false,
|
|
53
|
-
onRetry,
|
|
54
|
-
} = options;
|
|
55
|
-
|
|
56
|
-
let lastError: Error | undefined;
|
|
57
|
-
|
|
58
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
59
|
-
try {
|
|
60
|
-
return await fn();
|
|
61
|
-
} catch (error) {
|
|
62
|
-
lastError = error as Error;
|
|
63
|
-
|
|
64
|
-
if (attempt < maxRetries) {
|
|
65
|
-
// Calculate delay based on strategy
|
|
66
|
-
const delay =
|
|
67
|
-
strategy === "exponential"
|
|
68
|
-
? baseDelay * 2 ** attempt
|
|
69
|
-
: baseDelay * (attempt + 1);
|
|
70
|
-
|
|
71
|
-
// Add jitter if requested (0-1000ms random)
|
|
72
|
-
const jitterMs = jitter ? Math.random() * 1000 : 0;
|
|
73
|
-
const finalDelay = delay + jitterMs;
|
|
74
|
-
|
|
75
|
-
// Notify caller of retry
|
|
76
|
-
if (onRetry) {
|
|
77
|
-
onRetry(attempt + 1, lastError);
|
|
78
|
-
} else {
|
|
79
|
-
logger.warn(
|
|
80
|
-
`Retry attempt ${attempt + 1}/${maxRetries} after ${Math.round(finalDelay)}ms`,
|
|
81
|
-
{ error: lastError.message }
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
await new Promise((resolve) => setTimeout(resolve, finalDelay));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
throw lastError;
|
|
91
|
-
}
|
package/src/utils/sanitize.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sanitize filename to prevent path traversal attacks
|
|
3
|
-
* Removes directory separators and dangerous characters
|
|
4
|
-
*
|
|
5
|
-
* @param filename - The filename to sanitize
|
|
6
|
-
* @param maxLength - Maximum filename length (default: 255)
|
|
7
|
-
* @returns Safe filename
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* sanitizeFilename("../../etc/passwd") // "etc_passwd"
|
|
12
|
-
* sanitizeFilename("file<>|name.txt") // "file___name.txt"
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export function sanitizeFilename(
|
|
16
|
-
filename: string,
|
|
17
|
-
maxLength: number = 255
|
|
18
|
-
): string {
|
|
19
|
-
// Remove any directory path components
|
|
20
|
-
const basename = filename.replace(/^.*[\\/]/, "");
|
|
21
|
-
|
|
22
|
-
// Remove null bytes and other dangerous characters
|
|
23
|
-
const sanitized = basename.replace(/[^\w\s.-]/g, "_");
|
|
24
|
-
|
|
25
|
-
// Prevent hidden files and parent directory references
|
|
26
|
-
const safe = sanitized.replace(/^\.+/, "").replace(/\.{2,}/g, ".");
|
|
27
|
-
|
|
28
|
-
// Ensure filename is not empty after sanitization
|
|
29
|
-
if (!safe || safe.length === 0) {
|
|
30
|
-
return "unnamed_file";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Limit filename length
|
|
34
|
-
return safe.length > maxLength ? safe.substring(0, maxLength) : safe;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Sanitize conversation ID for filesystem usage
|
|
39
|
-
* Removes any characters that aren't safe for directory names
|
|
40
|
-
*
|
|
41
|
-
* @param conversationId - The conversation ID to sanitize
|
|
42
|
-
* @returns Safe conversation ID
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```typescript
|
|
46
|
-
* sanitizeConversationId("1756766056.836119") // "1756766056.836119"
|
|
47
|
-
* sanitizeConversationId("thread/123/../456") // "thread_123___456"
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
export function sanitizeConversationId(conversationId: string): string {
|
|
51
|
-
return conversationId.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Sanitize sensitive data from objects before logging
|
|
56
|
-
* Redacts API keys, tokens, and other credentials
|
|
57
|
-
*
|
|
58
|
-
* @param obj - Object to sanitize
|
|
59
|
-
* @param sensitiveKeys - Additional sensitive key names to redact
|
|
60
|
-
* @returns Sanitized object safe for logging
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```typescript
|
|
64
|
-
* const config = {
|
|
65
|
-
* apiKey: "secret-key-123",
|
|
66
|
-
* timeout: 5000,
|
|
67
|
-
* env: { TOKEN: "bearer-xyz" }
|
|
68
|
-
* };
|
|
69
|
-
*
|
|
70
|
-
* sanitizeForLogging(config)
|
|
71
|
-
* // {
|
|
72
|
-
* // apiKey: "[REDACTED:14]",
|
|
73
|
-
* // timeout: 5000,
|
|
74
|
-
* // env: { TOKEN: "[REDACTED:10]" }
|
|
75
|
-
* // }
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
export function sanitizeForLogging(
|
|
79
|
-
obj: any,
|
|
80
|
-
additionalSensitiveKeys: string[] = []
|
|
81
|
-
): any {
|
|
82
|
-
if (!obj || typeof obj !== "object") {
|
|
83
|
-
return obj;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const defaultSensitiveKeys = [
|
|
87
|
-
"anthropic_api_key",
|
|
88
|
-
"api_key",
|
|
89
|
-
"apiKey",
|
|
90
|
-
"token",
|
|
91
|
-
"password",
|
|
92
|
-
"secret",
|
|
93
|
-
"authorization",
|
|
94
|
-
"bearer",
|
|
95
|
-
"credentials",
|
|
96
|
-
"privateKey",
|
|
97
|
-
"private_key",
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
const sensitiveKeys = [...defaultSensitiveKeys, ...additionalSensitiveKeys];
|
|
101
|
-
|
|
102
|
-
const sanitized = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
103
|
-
|
|
104
|
-
for (const key in sanitized) {
|
|
105
|
-
const lowerKey = key.toLowerCase();
|
|
106
|
-
const isSensitive = sensitiveKeys.some((k) => lowerKey.includes(k));
|
|
107
|
-
|
|
108
|
-
if (isSensitive && typeof sanitized[key] === "string") {
|
|
109
|
-
// Redact but show length for debugging
|
|
110
|
-
sanitized[key] = `[REDACTED:${sanitized[key].length}]`;
|
|
111
|
-
} else if (key === "env" && typeof sanitized[key] === "object") {
|
|
112
|
-
// Recursively sanitize env object
|
|
113
|
-
sanitized[key] = sanitizeForLogging(
|
|
114
|
-
sanitized[key],
|
|
115
|
-
additionalSensitiveKeys
|
|
116
|
-
);
|
|
117
|
-
} else if (typeof sanitized[key] === "object") {
|
|
118
|
-
// Recursively sanitize nested objects
|
|
119
|
-
sanitized[key] = sanitizeForLogging(
|
|
120
|
-
sanitized[key],
|
|
121
|
-
additionalSensitiveKeys
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return sanitized;
|
|
127
|
-
}
|
package/src/worker/auth.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "../logger";
|
|
2
|
-
import { decrypt, encrypt } from "../utils/encryption";
|
|
3
|
-
|
|
4
|
-
const logger = createLogger("worker-auth");
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Worker authentication using encrypted conversation ID
|
|
8
|
-
* Token format: encrypted(userId:conversationId:deploymentName:timestamp)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export interface WorkerTokenData {
|
|
12
|
-
userId: string;
|
|
13
|
-
conversationId: string;
|
|
14
|
-
channelId: string;
|
|
15
|
-
teamId?: string; // Optional - not all platforms have teams
|
|
16
|
-
agentId?: string; // Space ID for multi-tenant isolation
|
|
17
|
-
connectionId?: string;
|
|
18
|
-
deploymentName: string;
|
|
19
|
-
timestamp: number;
|
|
20
|
-
platform?: string;
|
|
21
|
-
sessionKey?: string;
|
|
22
|
-
traceId?: string; // Trace ID for end-to-end observability
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Generate a worker authentication token by encrypting thread metadata
|
|
27
|
-
*/
|
|
28
|
-
export function generateWorkerToken(
|
|
29
|
-
userId: string,
|
|
30
|
-
conversationId: string,
|
|
31
|
-
deploymentName: string,
|
|
32
|
-
options: {
|
|
33
|
-
channelId: string;
|
|
34
|
-
teamId?: string;
|
|
35
|
-
agentId?: string;
|
|
36
|
-
connectionId?: string;
|
|
37
|
-
platform?: string;
|
|
38
|
-
sessionKey?: string;
|
|
39
|
-
traceId?: string; // Trace ID for end-to-end observability
|
|
40
|
-
}
|
|
41
|
-
): string {
|
|
42
|
-
// Validate required fields
|
|
43
|
-
if (!options.channelId) {
|
|
44
|
-
throw new Error("channelId is required for worker token generation");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const timestamp = Date.now();
|
|
48
|
-
const payload: WorkerTokenData = {
|
|
49
|
-
userId,
|
|
50
|
-
conversationId,
|
|
51
|
-
channelId: options.channelId,
|
|
52
|
-
teamId: options.teamId, // Can be undefined - that's ok
|
|
53
|
-
agentId: options.agentId, // Space ID for multi-tenant credential lookup
|
|
54
|
-
connectionId: options.connectionId,
|
|
55
|
-
deploymentName,
|
|
56
|
-
timestamp,
|
|
57
|
-
platform: options.platform,
|
|
58
|
-
sessionKey: options.sessionKey,
|
|
59
|
-
traceId: options.traceId, // Trace ID for observability
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Encrypt the payload
|
|
63
|
-
const encrypted = encrypt(JSON.stringify(payload));
|
|
64
|
-
return encrypted;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Verify and decrypt a worker authentication token
|
|
69
|
-
*/
|
|
70
|
-
export function verifyWorkerToken(token: string): WorkerTokenData | null {
|
|
71
|
-
try {
|
|
72
|
-
// Decrypt the token
|
|
73
|
-
const decrypted = decrypt(token);
|
|
74
|
-
const data = JSON.parse(decrypted) as WorkerTokenData;
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
!data.conversationId ||
|
|
78
|
-
!data.userId ||
|
|
79
|
-
!data.deploymentName ||
|
|
80
|
-
!data.timestamp
|
|
81
|
-
) {
|
|
82
|
-
logger.error("Worker token rejected: missing required fields");
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check token expiration (default 24h)
|
|
87
|
-
const parsedTtl = parseInt(process.env.WORKER_TOKEN_TTL_MS ?? "", 10);
|
|
88
|
-
const ttl =
|
|
89
|
-
!Number.isNaN(parsedTtl) && parsedTtl > 0 ? parsedTtl : 86400000;
|
|
90
|
-
if (Date.now() - data.timestamp > ttl) {
|
|
91
|
-
logger.error("Worker token rejected: expired");
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return data;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
logger.error("Error verifying token:", error);
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
package/src/worker/transport.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker Transport Interface
|
|
3
|
-
* Defines how workers communicate with the gateway (platform-agnostic)
|
|
4
|
-
*
|
|
5
|
-
* This abstraction allows different transport implementations:
|
|
6
|
-
* - HTTP (current implementation)
|
|
7
|
-
* - WebSocket (for real-time bidirectional communication)
|
|
8
|
-
* - gRPC (for high-performance scenarios)
|
|
9
|
-
* - Message Queue (for asynchronous processing)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Transport interface for worker-to-gateway communication
|
|
14
|
-
*/
|
|
15
|
-
export interface WorkerTransport {
|
|
16
|
-
/**
|
|
17
|
-
* Set the job ID for this worker session
|
|
18
|
-
* Used to correlate responses with the originating request
|
|
19
|
-
*/
|
|
20
|
-
setJobId(jobId: string): void;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Set module-specific data to be included in responses
|
|
24
|
-
* Allows modules to attach metadata to worker responses
|
|
25
|
-
*/
|
|
26
|
-
setModuleData(moduleData: Record<string, unknown>): void;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Send a streaming delta to the gateway
|
|
30
|
-
*
|
|
31
|
-
* @param delta - The content delta to send
|
|
32
|
-
* @param isFullReplacement - If true, replaces entire content; if false, appends
|
|
33
|
-
* @param isFinal - If true, indicates this is the final delta
|
|
34
|
-
*/
|
|
35
|
-
sendStreamDelta(
|
|
36
|
-
delta: string,
|
|
37
|
-
isFullReplacement?: boolean,
|
|
38
|
-
isFinal?: boolean
|
|
39
|
-
): Promise<void>;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Signal that the worker has completed processing
|
|
43
|
-
* Optionally includes a final delta
|
|
44
|
-
*
|
|
45
|
-
* @param finalDelta - Optional final content delta
|
|
46
|
-
*/
|
|
47
|
-
signalDone(finalDelta?: string): Promise<void>;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Signal successful completion without additional content
|
|
51
|
-
*/
|
|
52
|
-
signalCompletion(): Promise<void>;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Signal that an error occurred during processing
|
|
56
|
-
*
|
|
57
|
-
* @param error - The error that occurred
|
|
58
|
-
*/
|
|
59
|
-
signalError(error: Error, errorCode?: string): Promise<void>;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Send a status update to the gateway
|
|
63
|
-
* Used for long-running operations to show progress
|
|
64
|
-
*
|
|
65
|
-
* @param elapsedSeconds - Time elapsed since operation started
|
|
66
|
-
* @param state - Current state description (e.g., "processing", "waiting for API")
|
|
67
|
-
*/
|
|
68
|
-
sendStatusUpdate(elapsedSeconds: number, state: string): Promise<void>;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Configuration for creating a worker transport
|
|
73
|
-
*/
|
|
74
|
-
export interface WorkerTransportConfig {
|
|
75
|
-
/** Gateway URL for sending responses */
|
|
76
|
-
gatewayUrl: string;
|
|
77
|
-
|
|
78
|
-
/** Authentication token for worker */
|
|
79
|
-
workerToken: string;
|
|
80
|
-
|
|
81
|
-
/** User ID who initiated the request */
|
|
82
|
-
userId: string;
|
|
83
|
-
|
|
84
|
-
/** Channel/conversation ID */
|
|
85
|
-
channelId: string;
|
|
86
|
-
|
|
87
|
-
/** Conversation ID for organizing messages */
|
|
88
|
-
conversationId: string;
|
|
89
|
-
|
|
90
|
-
/** Original message timestamp/ID */
|
|
91
|
-
originalMessageTs: string;
|
|
92
|
-
|
|
93
|
-
/** Bot's response message timestamp/ID (if exists) */
|
|
94
|
-
botResponseTs?: string;
|
|
95
|
-
|
|
96
|
-
/** Team/workspace ID (required for all platforms) */
|
|
97
|
-
teamId: string;
|
|
98
|
-
|
|
99
|
-
/** Platform identifier (slack, whatsapp, api, etc.) */
|
|
100
|
-
platform?: string;
|
|
101
|
-
|
|
102
|
-
/** Platform-specific metadata needed for response routing */
|
|
103
|
-
platformMetadata?: Record<string, unknown>;
|
|
104
|
-
|
|
105
|
-
/** IDs of messages already processed (for deduplication) */
|
|
106
|
-
processedMessageIds?: string[];
|
|
107
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"outDir": "dist",
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"declarationMap": true,
|
|
7
|
-
"sourceMap": true,
|
|
8
|
-
"noEmit": false,
|
|
9
|
-
"moduleResolution": "node",
|
|
10
|
-
"module": "CommonJS",
|
|
11
|
-
"target": "ES2020",
|
|
12
|
-
"strict": true,
|
|
13
|
-
"skipLibCheck": true,
|
|
14
|
-
"esModuleInterop": true,
|
|
15
|
-
"allowSyntheticDefaultImports": true,
|
|
16
|
-
"composite": true
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*"],
|
|
19
|
-
"exclude": ["node_modules", "dist"]
|
|
20
|
-
}
|