@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/src/types.ts DELETED
@@ -1,440 +0,0 @@
1
- // ============================================================================
2
- // Provider Catalog Types
3
- // ============================================================================
4
-
5
- /**
6
- * Represents a provider installed for a specific agent.
7
- * Stored in AgentSettings.installedProviders as an ordered array (index 0 = primary).
8
- */
9
- export interface InstalledProvider {
10
- providerId: string; // "claude", "chatgpt", "gemini", "z-ai"
11
- installedAt: number;
12
- config?: {
13
- baseUrl?: string; // override upstream (e.g. z.ai proxy)
14
- [key: string]: unknown;
15
- };
16
- }
17
-
18
- /**
19
- * CLI backend configuration for pi-agent integration.
20
- * Providers can ship CLI tools that pi-agent invokes as backends.
21
- */
22
- export interface CliBackendConfig {
23
- name: string; // "claude-code", "codex"
24
- command: string; // "/usr/local/bin/claude"
25
- args?: string[];
26
- env?: Record<string, string>;
27
- modelArg?: string; // "--model"
28
- sessionArg?: string; // "--session"
29
- }
30
-
31
- // ============================================================================
32
- // Auth Profile Types
33
- // ============================================================================
34
-
35
- /**
36
- * Unified authentication profile for any model provider.
37
- * Stored in AgentSettings.authProfiles as an ordered array (index 0 = primary).
38
- */
39
- export interface AuthProfile {
40
- id: string; // UUID
41
- provider: string; // "anthropic", "openai-codex", "gemini", "nvidia"
42
- model: string; // Full model ref: "openai-codex/gpt-5.2-codex"
43
- credential: string; // API key or OAuth access token
44
- label: string; // "user@gmail.com", "sk-ant-...1234"
45
- authType: "oauth" | "device-code" | "api-key";
46
- metadata?: {
47
- email?: string;
48
- expiresAt?: number;
49
- refreshToken?: string;
50
- accountId?: string;
51
- };
52
- createdAt: number;
53
- }
54
-
55
- export interface SessionContext {
56
- // Core identifiers
57
- platform: string; // Platform identifier (e.g., "slack", "discord", "teams")
58
- channelId: string;
59
- userId: string;
60
- messageId: string; // Required - always needed for tracking
61
-
62
- // Optional context
63
- conversationId?: string;
64
- teamId?: string; // Platform workspace/team identifier
65
- userDisplayName?: string; // For logging/display purposes
66
- workingDirectory?: string;
67
- customInstructions?: string;
68
- conversationHistory?: ConversationMessage[];
69
- }
70
-
71
- export interface ConversationMessage {
72
- role: "system" | "user" | "assistant";
73
- content: string;
74
- timestamp: number;
75
- }
76
-
77
- // ============================================================================
78
- // Conversation History Types
79
- // ============================================================================
80
-
81
- // ============================================================================
82
- // Skills Configuration Types
83
- // ============================================================================
84
-
85
- /**
86
- * Per-skill thinking budget level.
87
- * Controls how much reasoning the model applies when executing a skill.
88
- */
89
- export type ThinkingLevel = "off" | "low" | "medium" | "high";
90
-
91
- /**
92
- * MCP server declared by a skill manifest.
93
- */
94
- export interface McpOAuthConfig {
95
- /** Authorization endpoint (user verification page for device-code flow). */
96
- authUrl?: string;
97
- /** Token endpoint. Falls back to `{origin}/oauth/token`. */
98
- tokenUrl?: string;
99
- /** Pre-registered client ID. When provided, dynamic client registration is skipped. */
100
- clientId?: string;
101
- /** Client secret for confidential clients. */
102
- clientSecret?: string;
103
- /** OAuth scopes. Falls back to `mcp:read mcp:write profile:read`. */
104
- scopes?: string[];
105
- /** Device authorization endpoint. Falls back to `{origin}/oauth/device_authorization`. */
106
- deviceAuthorizationUrl?: string;
107
- /** Dynamic client registration endpoint. Falls back to `{origin}/oauth/register`. */
108
- registrationUrl?: string;
109
- /** RFC 8707 resource indicator included in token requests. */
110
- resource?: string;
111
- }
112
-
113
- export interface SkillMcpServer {
114
- id: string;
115
- name?: string;
116
- url?: string;
117
- type?: "sse" | "stdio";
118
- command?: string;
119
- args?: string[];
120
- oauth?: McpOAuthConfig;
121
- inputs?: Array<{ id: string; label?: string; type?: string }>;
122
- headers?: Record<string, string>;
123
- }
124
-
125
- /**
126
- * Individual skill configuration.
127
- * Skills are SKILL.md files from GitHub repos that provide instructions to Claude.
128
- */
129
- export interface SkillConfig {
130
- /** Skill repository in owner/repo format (e.g., "anthropics/skills/pdf") */
131
- repo: string;
132
- /** Skill name derived from SKILL.md frontmatter or folder name */
133
- name: string;
134
- /** Optional description from SKILL.md frontmatter */
135
- description?: string;
136
- /** Short always-inlined instruction block for critical rules */
137
- instructions?: string;
138
- /** Whether this skill is currently enabled */
139
- enabled: boolean;
140
- /** True for system-defined skills (from system-skills.json). Cannot be removed by users. */
141
- system?: boolean;
142
- /** Cached SKILL.md content (fetched from GitHub) */
143
- content?: string;
144
- /** When the content was last fetched (timestamp ms) */
145
- contentFetchedAt?: number;
146
- /** MCP servers declared by the skill */
147
- mcpServers?: SkillMcpServer[];
148
- /** System packages declared by the skill (nix) */
149
- nixPackages?: string[];
150
- /** Network domains the skill needs access to (legacy flat list) */
151
- permissions?: string[];
152
- /** Network access policy declared by the skill */
153
- networkConfig?: { allowedDomains?: string[]; deniedDomains?: string[] };
154
- /** Tool permission policy declared by the skill */
155
- toolPermissions?: { allow?: string[]; deny?: string[] };
156
- /** AI providers the skill requires */
157
- providers?: string[];
158
- /** Preferred model for this skill (e.g., "anthropic/claude-opus-4") */
159
- modelPreference?: string;
160
- /** Thinking level budget for this skill */
161
- thinkingLevel?: ThinkingLevel;
162
- }
163
-
164
- /**
165
- * Skills configuration for agent settings.
166
- * Contains list of configured skills that can be enabled/disabled.
167
- */
168
- export interface SkillsConfig {
169
- /** List of configured skills */
170
- skills: SkillConfig[];
171
- }
172
-
173
- /**
174
- * Platform-agnostic history message format.
175
- * Used to pass conversation history to workers.
176
- */
177
- export interface HistoryMessage {
178
- role: "user" | "assistant";
179
- content: string;
180
- timestamp: number;
181
- /** Display name of the message sender */
182
- userName?: string;
183
- /** Platform-specific message ID for deduplication */
184
- messageId?: string;
185
- }
186
-
187
- /**
188
- * Network configuration for worker sandbox isolation.
189
- * Controls which domains the worker can access via HTTP proxy.
190
- *
191
- * Filtering rules:
192
- * - deniedDomains are checked first (take precedence)
193
- * - allowedDomains are checked second
194
- * - If neither matches, request is denied
195
- *
196
- * Domain pattern format:
197
- * - "example.com" - exact match
198
- * - ".example.com" or "*.example.com" - matches subdomains
199
- */
200
- export interface NetworkConfig {
201
- /** Domains the worker is allowed to access. Empty array = no network access. */
202
- allowedDomains?: string[];
203
- /** Domains explicitly blocked (takes precedence over allowedDomains). */
204
- deniedDomains?: string[];
205
- }
206
-
207
- /**
208
- * Nix environment configuration for agent workspace.
209
- * Allows agents to run with specific Nix packages or flakes.
210
- *
211
- * Resolution priority:
212
- * 1. API-provided flakeUrl (highest)
213
- * 2. API-provided packages
214
- * 3. flake.nix in git repo
215
- * 4. shell.nix in git repo
216
- * 5. .nix-packages file in git repo
217
- */
218
- export interface NixConfig {
219
- /** Nix flake URL (e.g., "github:user/repo#devShell") */
220
- flakeUrl?: string;
221
- /** Nixpkgs packages to install (e.g., ["python311", "ffmpeg"]) */
222
- packages?: string[];
223
- }
224
-
225
- // ============================================================================
226
- // Tools Configuration Types
227
- // ============================================================================
228
-
229
- /**
230
- * Tool permission configuration for agent settings.
231
- * Follows Claude Code's permission patterns for consistency.
232
- *
233
- * Pattern formats (Claude Code compatible):
234
- * - "Read" - exact tool match
235
- * - "Bash(git:*)" - Bash with command filter (only git commands)
236
- * - "Bash(npm:*)" - Bash with npm commands only
237
- * - "mcp__servername__*" - all tools from an MCP server
238
- * - "*" - wildcard (all tools)
239
- *
240
- * Filtering rules:
241
- * - deniedTools are checked first (take precedence)
242
- * - allowedTools are checked second
243
- * - If strictMode=true, only allowedTools are permitted
244
- * - If strictMode=false, defaults + allowedTools are permitted
245
- */
246
- export interface ToolsConfig {
247
- /**
248
- * Tools to auto-allow (in addition to defaults unless strictMode=true).
249
- * Supports patterns like "Bash(git:*)" or "mcp__github__*".
250
- */
251
- allowedTools?: string[];
252
-
253
- /**
254
- * Tools to always deny (takes precedence over allowedTools).
255
- * Use to block specific tools even if they're in defaults.
256
- */
257
- deniedTools?: string[];
258
-
259
- /**
260
- * If true, ONLY allowedTools are permitted (ignores defaults).
261
- * If false (default), allowedTools are ADDED to default permissions.
262
- */
263
- strictMode?: boolean;
264
- }
265
-
266
- /**
267
- * MCP server configuration for per-agent MCP servers.
268
- * Supports both HTTP/SSE and stdio MCP servers.
269
- */
270
- export interface McpServerConfig {
271
- /** For HTTP/SSE MCPs: upstream URL */
272
- url?: string;
273
- /** Server type: "sse" for HTTP MCPs, "stdio" for command-based */
274
- type?: "sse" | "stdio";
275
- /** For stdio MCPs: command to execute */
276
- command?: string;
277
- /** For stdio MCPs: command arguments */
278
- args?: string[];
279
- /** For stdio MCPs: environment variables */
280
- env?: Record<string, string>;
281
- /** Additional headers for HTTP MCPs */
282
- headers?: Record<string, string>;
283
- /** Optional description for the MCP */
284
- description?: string;
285
- }
286
-
287
- /**
288
- * Per-agent MCP configuration.
289
- * These MCPs are ADDED to global MCPs (not replacing).
290
- */
291
- export interface AgentMcpConfig {
292
- /** Additional MCP servers for this agent */
293
- mcpServers: Record<string, McpServerConfig>;
294
- }
295
-
296
- export interface MemoryFlushOptions {
297
- enabled?: boolean;
298
- softThresholdTokens?: number;
299
- systemPrompt?: string;
300
- prompt?: string;
301
- }
302
-
303
- export interface AgentCompactionOptions {
304
- memoryFlush?: MemoryFlushOptions;
305
- }
306
-
307
- /**
308
- * Platform-agnostic execution hints passed through gateway → worker.
309
- * Flexible types (string | string[]) and index signature allow forward
310
- * compatibility for different agent implementations.
311
- */
312
- export interface AgentOptions {
313
- runtime?: string;
314
- model?: string;
315
- maxTokens?: number;
316
- temperature?: number;
317
- allowedTools?: string | string[];
318
- disallowedTools?: string | string[];
319
- timeoutMinutes?: number | string;
320
- compaction?: AgentCompactionOptions;
321
- // Additional settings passed through from gateway (can be nested objects)
322
- networkConfig?: Record<string, unknown>;
323
- envVars?: Record<string, string>;
324
- [key: string]: unknown;
325
- }
326
-
327
- /**
328
- * Platform-agnostic log level type
329
- * Maps to common logging levels used across different platforms
330
- */
331
- export type LogLevel = "debug" | "info" | "warn" | "error";
332
-
333
- // ============================================================================
334
- // Instruction Provider Types
335
- // ============================================================================
336
-
337
- /**
338
- * Context information passed to instruction providers
339
- */
340
- export interface InstructionContext {
341
- userId: string;
342
- agentId: string;
343
- sessionKey: string;
344
- workingDirectory: string;
345
- availableProjects?: string[];
346
- userPrompt?: string;
347
- }
348
-
349
- /**
350
- * Interface for components that contribute custom instructions
351
- */
352
- export interface InstructionProvider {
353
- /** Unique identifier for this provider */
354
- name: string;
355
-
356
- /** Priority for ordering (lower = earlier in output) */
357
- priority: number;
358
-
359
- /**
360
- * Generate instruction text for this provider
361
- * @param context - Context information for instruction generation
362
- * @returns Instruction text or empty string if none
363
- */
364
- getInstructions(context: InstructionContext): Promise<string> | string;
365
- }
366
-
367
- // ============================================================================
368
- // Thread Response Types
369
- // ============================================================================
370
-
371
- /**
372
- * Shared payload contract for worker → platform thread responses.
373
- * Ensures gateway consumers and workers stay type-aligned.
374
- */
375
- export interface ThreadResponsePayload {
376
- messageId: string;
377
- channelId: string;
378
- conversationId: string;
379
- userId: string;
380
- teamId: string;
381
- platform?: string; // Platform identifier (slack, whatsapp, api, etc.) for routing
382
- content?: string; // Used only for ephemeral messages (OAuth/auth flows)
383
- delta?: string;
384
- isFullReplacement?: boolean;
385
- processedMessageIds?: string[];
386
- error?: string;
387
- errorCode?: string;
388
- timestamp: number;
389
- originalMessageId?: string;
390
- moduleData?: Record<string, unknown>;
391
- botResponseId?: string;
392
- ephemeral?: boolean; // If true, message should be sent as ephemeral (only visible to user)
393
- platformMetadata?: Record<string, unknown>;
394
- statusUpdate?: {
395
- elapsedSeconds: number;
396
- state: string; // e.g., "is running" or "is scheduling"
397
- };
398
-
399
- // Exec-specific response fields (for jobType === "exec")
400
- execId?: string; // Exec job ID for response routing
401
- execStream?: "stdout" | "stderr"; // Which stream this delta is from
402
- execExitCode?: number; // Process exit code (sent on completion)
403
- }
404
-
405
- // ============================================================================
406
- // User Interaction Types
407
- // ============================================================================
408
-
409
- /**
410
- * Suggested prompt for user
411
- */
412
- export interface SuggestedPrompt {
413
- title: string; // Short label shown as chip
414
- message: string; // Full message sent when clicked
415
- }
416
-
417
- /**
418
- * Skill registry entry (global or per-agent).
419
- */
420
- export interface RegistryEntry {
421
- id: string;
422
- type: string;
423
- apiUrl: string;
424
- }
425
-
426
- /**
427
- * Non-blocking suggestions - agent continues immediately
428
- * Used for optional next steps
429
- */
430
- export interface UserSuggestion {
431
- id: string;
432
- userId: string;
433
- conversationId: string;
434
- channelId: string;
435
- teamId?: string;
436
-
437
- blocking: false; // Always false - distinguishes from interactions
438
-
439
- prompts: SuggestedPrompt[];
440
- }
@@ -1,78 +0,0 @@
1
- import * as crypto from "node:crypto";
2
- import { createLogger } from "../logger";
3
-
4
- const IV_LENGTH = 12; // 96-bit nonce for AES-GCM
5
- const logger = createLogger("encryption");
6
-
7
- /**
8
- * Get encryption key from environment with validation
9
- *
10
- * IMPORTANT: The ENCRYPTION_KEY must be exactly 32 bytes (256 bits) for AES-256.
11
- * Generate a secure key using: `openssl rand -base64 32` or `openssl rand -hex 32`
12
- */
13
- function getEncryptionKey(): Buffer {
14
- const key = process.env.ENCRYPTION_KEY || "";
15
- if (!key) {
16
- throw new Error(
17
- "ENCRYPTION_KEY environment variable is required for secure operation"
18
- );
19
- }
20
-
21
- // Try to decode as base64 first (most common format)
22
- let keyBuffer: Buffer;
23
- try {
24
- keyBuffer = Buffer.from(key, "base64");
25
- if (keyBuffer.length === 32) {
26
- return keyBuffer;
27
- }
28
- } catch (err) {
29
- logger.debug("ENCRYPTION_KEY is not valid base64, trying hex format", err);
30
- }
31
-
32
- // Try as hex (must be exactly 64 hex characters for 32 bytes)
33
- if (/^[0-9a-fA-F]{64}$/.test(key)) {
34
- keyBuffer = Buffer.from(key, "hex");
35
- if (keyBuffer.length === 32) {
36
- return keyBuffer;
37
- }
38
- }
39
-
40
- throw new Error(
41
- "ENCRYPTION_KEY must be a base64 or hex encoded 32-byte key. " +
42
- "Generate a valid key with: openssl rand -base64 32"
43
- );
44
- }
45
-
46
- /**
47
- * Encrypt a string using AES-256-GCM
48
- */
49
- export function encrypt(text: string): string {
50
- const encryptionKey = getEncryptionKey();
51
- const iv = crypto.randomBytes(IV_LENGTH);
52
- const cipher = crypto.createCipheriv("aes-256-gcm", encryptionKey, iv);
53
- const encrypted = Buffer.concat([
54
- cipher.update(text, "utf8"),
55
- cipher.final(),
56
- ]);
57
- const tag = cipher.getAuthTag();
58
- return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`;
59
- }
60
-
61
- /**
62
- * Decrypt a string encrypted with AES-256-GCM
63
- */
64
- export function decrypt(text: string): string {
65
- const encryptionKey = getEncryptionKey();
66
- const parts = text.split(":");
67
- if (parts.length !== 3) throw new Error("Invalid encrypted format");
68
- const iv = Buffer.from(parts[0]!, "hex");
69
- const tag = Buffer.from(parts[1]!, "hex");
70
- const encryptedText = Buffer.from(parts[2]!, "hex");
71
- const decipher = crypto.createDecipheriv("aes-256-gcm", encryptionKey, iv);
72
- decipher.setAuthTag(tag);
73
- const decrypted = Buffer.concat([
74
- decipher.update(encryptedText),
75
- decipher.final(),
76
- ]);
77
- return decrypted.toString("utf8");
78
- }
package/src/utils/env.ts DELETED
@@ -1,50 +0,0 @@
1
- import { ConfigError } from "../errors";
2
-
3
- /**
4
- * Get required environment variable
5
- * Throws ConfigError if not set
6
- */
7
- export function getRequiredEnv(name: string): string {
8
- const value = process.env[name];
9
- if (!value) {
10
- throw new ConfigError(`Missing required environment variable: ${name}`);
11
- }
12
- return value;
13
- }
14
-
15
- /**
16
- * Get optional environment variable with default
17
- */
18
- export function getOptionalEnv(name: string, defaultValue: string): string {
19
- return process.env[name] || defaultValue;
20
- }
21
-
22
- /**
23
- * Get optional number environment variable with default
24
- * Throws ConfigError if value is not a valid number
25
- */
26
- export function getOptionalNumber(name: string, defaultValue: number): number {
27
- const value = process.env[name];
28
- if (!value) return defaultValue;
29
- const parsed = parseInt(value, 10);
30
- if (Number.isNaN(parsed)) {
31
- throw new ConfigError(
32
- `Invalid number for ${name}: ${value} (expected integer)`
33
- );
34
- }
35
- return parsed;
36
- }
37
-
38
- /**
39
- * Get optional boolean environment variable with default
40
- * Accepts "true", "1", "yes" as truthy values
41
- */
42
- export function getOptionalBoolean(
43
- name: string,
44
- defaultValue: boolean
45
- ): boolean {
46
- const value = process.env[name];
47
- if (!value) return defaultValue;
48
- const lower = value.toLowerCase();
49
- return lower === "true" || lower === "1" || lower === "yes";
50
- }
package/src/utils/json.ts DELETED
@@ -1,37 +0,0 @@
1
- import { createLogger } from "../logger";
2
-
3
- const logger = createLogger("json-utils");
4
-
5
- /**
6
- * Safely parse JSON string
7
- * Returns null on parse failure instead of throwing
8
- */
9
- export function safeJsonParse<T = unknown>(
10
- data: string,
11
- fallback: T | null = null
12
- ): T | null {
13
- try {
14
- return JSON.parse(data) as T;
15
- } catch (error) {
16
- logger.debug("JSON parse failed", {
17
- error: error instanceof Error ? error.message : String(error),
18
- dataPreview: data.substring(0, 100),
19
- });
20
- return fallback;
21
- }
22
- }
23
-
24
- /**
25
- * Safely stringify value to JSON
26
- * Returns null on stringify failure instead of throwing
27
- */
28
- export function safeJsonStringify(value: unknown): string | null {
29
- try {
30
- return JSON.stringify(value);
31
- } catch (error) {
32
- logger.error("JSON stringify failed", {
33
- error: error instanceof Error ? error.message : String(error),
34
- });
35
- return null;
36
- }
37
- }
package/src/utils/lock.ts DELETED
@@ -1,75 +0,0 @@
1
- /**
2
- * Async lock for serializing concurrent operations
3
- * Prevents race conditions in async code by ensuring only one operation runs at a time
4
- *
5
- * @example
6
- * ```typescript
7
- * class StreamSession {
8
- * private streamLock = new AsyncLock();
9
- *
10
- * async appendDelta(delta: string) {
11
- * return this.streamLock.acquire(() => this.appendDeltaUnsafe(delta));
12
- * }
13
- *
14
- * private async appendDeltaUnsafe(delta: string) {
15
- * // Critical section - only one execution at a time
16
- * }
17
- * }
18
- * ```
19
- */
20
- export class AsyncLock {
21
- private lock: Promise<void> = Promise.resolve();
22
- private lockContext: string;
23
-
24
- constructor(context: string = "unknown") {
25
- this.lockContext = context;
26
- }
27
-
28
- /**
29
- * Acquire lock and execute function exclusively
30
- *
31
- * @param fn - The async function to execute with exclusive access
32
- * @param timeoutMs - Maximum time to wait for lock acquisition (default: 30s)
33
- * @returns The result of the function
34
- * @throws Error if lock acquisition times out
35
- */
36
- async acquire<T>(
37
- fn: () => Promise<T>,
38
- timeoutMs: number = 30000
39
- ): Promise<T> {
40
- const currentLock = this.lock;
41
- let releaseLock: (() => void) | undefined;
42
-
43
- // Create new lock that will be released when fn completes
44
- this.lock = new Promise<void>((resolve) => {
45
- releaseLock = resolve;
46
- });
47
-
48
- let timeoutId: ReturnType<typeof setTimeout> | undefined;
49
-
50
- try {
51
- // Wait for previous operation with timeout to prevent deadlock
52
- const lockTimeout = new Promise<never>((_, reject) => {
53
- timeoutId = setTimeout(() => {
54
- reject(
55
- new Error(
56
- `Lock acquisition timeout after ${timeoutMs}ms - possible deadlock in ${this.lockContext}`
57
- )
58
- );
59
- }, timeoutMs);
60
- });
61
-
62
- await Promise.race([currentLock, lockTimeout]);
63
-
64
- // Execute function with exclusive access
65
- return await fn();
66
- } finally {
67
- // Clear the timeout to prevent leak
68
- if (timeoutId !== undefined) {
69
- clearTimeout(timeoutId);
70
- }
71
- // Always release lock, even on error
72
- releaseLock?.();
73
- }
74
- }
75
- }
@@ -1,5 +0,0 @@
1
- export interface McpToolDef {
2
- name: string;
3
- description?: string;
4
- inputSchema?: Record<string, unknown>;
5
- }