@lobu/core 3.0.5 → 3.0.6

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.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * AgentStore — unified interface for agent configuration storage.
3
+ *
4
+ * Implementations:
5
+ * - InMemoryAgentStore (default, populated from files or API)
6
+ * - Host-provided store (embedded mode, e.g. PostgresAgentStore in Owletto)
7
+ */
8
+
9
+ import type { PluginsConfig } from "./plugin-types";
10
+ import type {
11
+ AuthProfile,
12
+ InstalledProvider,
13
+ McpServerConfig,
14
+ NetworkConfig,
15
+ NixConfig,
16
+ SkillsConfig,
17
+ ToolsConfig,
18
+ } from "./types";
19
+
20
+ // ── Agent Settings ──────────────────────────────────────────────────────────
21
+
22
+ export interface AgentSettings {
23
+ model?: string;
24
+ modelSelection?: { mode: "auto" | "pinned"; pinnedModel?: string };
25
+ providerModelPreferences?: Record<string, string>;
26
+ networkConfig?: NetworkConfig;
27
+ nixConfig?: NixConfig;
28
+ mcpServers?: Record<string, McpServerConfig>;
29
+ mcpInstallNotified?: Record<string, number>;
30
+ soulMd?: string;
31
+ userMd?: string;
32
+ identityMd?: string;
33
+ skillsConfig?: SkillsConfig;
34
+ toolsConfig?: ToolsConfig;
35
+ pluginsConfig?: PluginsConfig;
36
+ authProfiles?: AuthProfile[];
37
+ installedProviders?: InstalledProvider[];
38
+ verboseLogging?: boolean;
39
+ templateAgentId?: string;
40
+ updatedAt: number;
41
+ }
42
+
43
+ // ── Agent Metadata ──────────────────────────────────────────────────────────
44
+
45
+ export interface AgentMetadata {
46
+ agentId: string;
47
+ name: string;
48
+ description?: string;
49
+ owner: { platform: string; userId: string };
50
+ isWorkspaceAgent?: boolean;
51
+ workspaceId?: string;
52
+ parentConnectionId?: string;
53
+ createdAt: number;
54
+ lastUsedAt?: number;
55
+ }
56
+
57
+ // ── Connections ─────────────────────────────────────────────────────────────
58
+
59
+ export interface ConnectionSettings {
60
+ allowFrom?: string[];
61
+ allowGroups?: boolean;
62
+ userConfigScopes?: string[];
63
+ }
64
+
65
+ export interface StoredConnection {
66
+ id: string;
67
+ platform: string;
68
+ templateAgentId?: string;
69
+ config: Record<string, any>;
70
+ settings: ConnectionSettings;
71
+ metadata: Record<string, any>;
72
+ status: "active" | "stopped" | "error";
73
+ errorMessage?: string;
74
+ createdAt: number;
75
+ updatedAt: number;
76
+ }
77
+
78
+ // ── Grants ──────────────────────────────────────────────────────────────────
79
+
80
+ export interface Grant {
81
+ pattern: string;
82
+ expiresAt: number | null;
83
+ grantedAt: number;
84
+ denied?: boolean;
85
+ }
86
+
87
+ // ── Channel Bindings ────────────────────────────────────────────────────────
88
+
89
+ export interface ChannelBinding {
90
+ agentId: string;
91
+ platform: string;
92
+ channelId: string;
93
+ teamId?: string;
94
+ createdAt: number;
95
+ }
96
+
97
+ // ── Sub-Store Interfaces ──────────────────────────────────────────────────
98
+
99
+ /**
100
+ * Agent identity & configuration storage.
101
+ * Settings (model, skills, providers, etc.) + metadata (name, owner, etc.)
102
+ */
103
+ export interface AgentConfigStore {
104
+ getSettings(agentId: string): Promise<AgentSettings | null>;
105
+ saveSettings(agentId: string, settings: AgentSettings): Promise<void>;
106
+ updateSettings(
107
+ agentId: string,
108
+ updates: Partial<AgentSettings>
109
+ ): Promise<void>;
110
+ deleteSettings(agentId: string): Promise<void>;
111
+ hasSettings(agentId: string): Promise<boolean>;
112
+
113
+ getMetadata(agentId: string): Promise<AgentMetadata | null>;
114
+ saveMetadata(agentId: string, metadata: AgentMetadata): Promise<void>;
115
+ updateMetadata(
116
+ agentId: string,
117
+ updates: Partial<AgentMetadata>
118
+ ): Promise<void>;
119
+ deleteMetadata(agentId: string): Promise<void>;
120
+ hasAgent(agentId: string): Promise<boolean>;
121
+ listAgents(): Promise<AgentMetadata[]>;
122
+ listSandboxes(connectionId: string): Promise<AgentMetadata[]>;
123
+ }
124
+
125
+ /**
126
+ * Find the first non-sandbox agent with installed providers configured.
127
+ * Used to pick a default template agent when creating ephemeral/API agents.
128
+ */
129
+ export async function findTemplateAgentId(
130
+ store: Pick<AgentConfigStore, "listAgents" | "getSettings">
131
+ ): Promise<string | null> {
132
+ const agents = await store.listAgents();
133
+
134
+ for (const agent of agents) {
135
+ if (agent.parentConnectionId) continue;
136
+ const settings = await store.getSettings(agent.agentId);
137
+ if (settings?.installedProviders?.length) {
138
+ return agent.agentId;
139
+ }
140
+ }
141
+
142
+ return null;
143
+ }
144
+
145
+ /**
146
+ * Platform wiring storage.
147
+ * Connections (Telegram, Slack, etc.) + channel bindings.
148
+ */
149
+ export interface AgentConnectionStore {
150
+ getConnection(connectionId: string): Promise<StoredConnection | null>;
151
+ listConnections(filter?: {
152
+ templateAgentId?: string;
153
+ platform?: string;
154
+ }): Promise<StoredConnection[]>;
155
+ saveConnection(connection: StoredConnection): Promise<void>;
156
+ updateConnection(
157
+ connectionId: string,
158
+ updates: Partial<StoredConnection>
159
+ ): Promise<void>;
160
+ deleteConnection(connectionId: string): Promise<void>;
161
+
162
+ getChannelBinding(
163
+ platform: string,
164
+ channelId: string,
165
+ teamId?: string
166
+ ): Promise<ChannelBinding | null>;
167
+ createChannelBinding(binding: ChannelBinding): Promise<void>;
168
+ deleteChannelBinding(
169
+ platform: string,
170
+ channelId: string,
171
+ teamId?: string
172
+ ): Promise<void>;
173
+ listChannelBindings(agentId: string): Promise<ChannelBinding[]>;
174
+ deleteAllChannelBindings(agentId: string): Promise<number>;
175
+ }
176
+
177
+ /**
178
+ * Permissions & ownership storage.
179
+ * Grants (skill/domain access) + user-agent associations.
180
+ */
181
+ export interface AgentAccessStore {
182
+ grant(
183
+ agentId: string,
184
+ pattern: string,
185
+ expiresAt: number | null,
186
+ denied?: boolean
187
+ ): Promise<void>;
188
+ hasGrant(agentId: string, pattern: string): Promise<boolean>;
189
+ isDenied(agentId: string, pattern: string): Promise<boolean>;
190
+ listGrants(agentId: string): Promise<Grant[]>;
191
+ revokeGrant(agentId: string, pattern: string): Promise<void>;
192
+
193
+ addUserAgent(
194
+ platform: string,
195
+ userId: string,
196
+ agentId: string
197
+ ): Promise<void>;
198
+ removeUserAgent(
199
+ platform: string,
200
+ userId: string,
201
+ agentId: string
202
+ ): Promise<void>;
203
+ listUserAgents(platform: string, userId: string): Promise<string[]>;
204
+ ownsAgent(
205
+ platform: string,
206
+ userId: string,
207
+ agentId: string
208
+ ): Promise<boolean>;
209
+ }
210
+
211
+ // ── AgentStore (full intersection) ────────────────────────────────────────
212
+
213
+ /**
214
+ * Full storage interface — intersection of all sub-stores.
215
+ * Implementations (InMemoryAgentStore, etc.) satisfy all 3.
216
+ * Hosts can provide individual sub-stores via GatewayOptions instead.
217
+ */
218
+ export type AgentStore = AgentConfigStore &
219
+ AgentConnectionStore &
220
+ AgentAccessStore;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Agent Settings API response types.
3
+ * These mirror the gateway API response shapes used by UI consumers.
4
+ */
5
+
6
+ import type { RegistryEntry } from "./types";
7
+
8
+ export type { RegistryEntry };
9
+
10
+ export interface ProviderInfo {
11
+ name: string;
12
+ authType: "oauth" | "device-code" | "api-key";
13
+ supportedAuthTypes: ("oauth" | "device-code" | "api-key")[];
14
+ apiKeyInstructions: string;
15
+ apiKeyPlaceholder: string;
16
+ capabilities: (
17
+ | "text"
18
+ | "image-generation"
19
+ | "speech-to-text"
20
+ | "text-to-speech"
21
+ )[];
22
+ }
23
+
24
+ export interface CatalogProvider {
25
+ id: string;
26
+ name: string;
27
+ iconUrl: string;
28
+ authType: "oauth" | "device-code" | "api-key";
29
+ supportedAuthTypes: ("oauth" | "device-code" | "api-key")[];
30
+ apiKeyInstructions: string;
31
+ apiKeyPlaceholder: string;
32
+ capabilities: (
33
+ | "text"
34
+ | "image-generation"
35
+ | "speech-to-text"
36
+ | "text-to-speech"
37
+ )[];
38
+ }
39
+
40
+ export interface ModelOption {
41
+ label: string;
42
+ value: string;
43
+ }
44
+
45
+ export type SettingsScope = "agent" | "sandbox";
46
+ export type SettingsSource = "local" | "inherited" | "mixed";
47
+ export type SettingsSectionKey =
48
+ | "model"
49
+ | "system-prompt"
50
+ | "skills"
51
+ | "packages"
52
+ | "permissions"
53
+ | "schedules"
54
+ | "logging";
55
+
56
+ export interface SectionView {
57
+ source: SettingsSource;
58
+ editable: boolean;
59
+ canReset: boolean;
60
+ hasLocalOverride: boolean;
61
+ }
62
+
63
+ export interface ProviderView {
64
+ id: string;
65
+ source: SettingsSource;
66
+ canEdit: boolean;
67
+ canReset: boolean;
68
+ hasLocalOverride: boolean;
69
+ }
70
+
71
+ export interface ModelSelectionState {
72
+ mode: "auto" | "pinned";
73
+ pinnedModel?: string;
74
+ }
75
+
76
+ export interface SkillMcpServerInfo {
77
+ id: string;
78
+ name?: string;
79
+ url?: string;
80
+ type?: "sse" | "stdio";
81
+ command?: string;
82
+ args?: string[];
83
+ }
84
+
85
+ export interface Skill {
86
+ repo: string;
87
+ name: string;
88
+ description: string;
89
+ enabled: boolean;
90
+ system?: boolean;
91
+ content?: string;
92
+ contentFetchedAt?: number;
93
+ mcpServers?: SkillMcpServerInfo[];
94
+ nixPackages?: string[];
95
+ permissions?: string[];
96
+ providers?: string[];
97
+ modelPreference?: string;
98
+ thinkingLevel?: string;
99
+ }
100
+
101
+ export interface McpConfig {
102
+ enabled?: boolean;
103
+ url?: string;
104
+ command?: string;
105
+ args?: string[];
106
+ type?: string;
107
+ description?: string;
108
+ }
109
+
110
+ export interface Schedule {
111
+ scheduleId: string;
112
+ task: string;
113
+ scheduledFor: string;
114
+ status: "pending" | "triggered" | "cancelled";
115
+ isRecurring?: boolean;
116
+ cron?: string;
117
+ iteration?: number;
118
+ maxIterations?: number;
119
+ }
120
+
121
+ export interface PrefillSkill {
122
+ repo: string;
123
+ name?: string;
124
+ description?: string;
125
+ }
126
+
127
+ export interface PrefillMcp {
128
+ id: string;
129
+ name?: string;
130
+ url?: string;
131
+ type?: string;
132
+ command?: string;
133
+ args?: string[];
134
+ envVars?: string[];
135
+ }
136
+
137
+ export interface ProviderState {
138
+ status: string;
139
+ connected: boolean;
140
+ userConnected: boolean;
141
+ systemConnected: boolean;
142
+ showAuthFlow: boolean;
143
+ showCodeInput: boolean;
144
+ showDeviceCode: boolean;
145
+ showApiKeyInput: boolean;
146
+ activeAuthTab: string;
147
+ activeAuthType?: string | null;
148
+ authMethods?: string[];
149
+ code: string;
150
+ apiKey: string;
151
+ userCode: string;
152
+ verificationUrl: string;
153
+ pollStatus: string;
154
+ deviceAuthId: string;
155
+ selectedModel: string;
156
+ modelQuery: string;
157
+ showModelDropdown: boolean;
158
+ }
159
+
160
+ export interface PermissionGrant {
161
+ pattern: string;
162
+ expiresAt: number | null;
163
+ denied?: boolean;
164
+ grantedAt?: number;
165
+ }
166
+
167
+ export interface AgentInfo {
168
+ agentId: string;
169
+ name: string;
170
+ isWorkspaceAgent?: boolean;
171
+ channelCount: number;
172
+ description?: string;
173
+ }
174
+
175
+ export interface SettingsSnapshot {
176
+ identityMd: string;
177
+ soulMd: string;
178
+ userMd: string;
179
+ verboseLogging: boolean;
180
+ primaryProvider: string;
181
+ providerOrder: string;
182
+ nixPackages: string;
183
+ skills: string;
184
+ mcpServers: string;
185
+ permissions: string;
186
+ providerModelPreferences: string;
187
+ registries: string;
188
+ }
189
+
190
+ export interface Connection {
191
+ id: string;
192
+ platform: string;
193
+ templateAgentId?: string;
194
+ config: Record<string, unknown>;
195
+ settings: {
196
+ allowFrom?: string[];
197
+ allowGroups?: boolean;
198
+ userConfigScopes?: string[];
199
+ };
200
+ metadata: Record<string, unknown>;
201
+ status: "active" | "stopped" | "error";
202
+ errorMessage?: string;
203
+ createdAt: number;
204
+ updatedAt: number;
205
+ }
206
+
207
+ export interface ProviderStatus {
208
+ connected: boolean;
209
+ userConnected: boolean;
210
+ systemConnected: boolean;
211
+ activeAuthType?: "oauth" | "device-code" | "api-key";
212
+ authMethods?: string[];
213
+ }
214
+
215
+ export interface AgentConfigResponse {
216
+ agentId: string;
217
+ scope: SettingsScope;
218
+ templateAgentId?: string;
219
+ templateAgentName?: string;
220
+ sections: Record<SettingsSectionKey, SectionView>;
221
+ providerViews: Record<string, ProviderView>;
222
+
223
+ instructions: {
224
+ identity: string;
225
+ soul: string;
226
+ user: string;
227
+ };
228
+
229
+ providers: {
230
+ order: string[];
231
+ status: Record<string, ProviderStatus>;
232
+ catalog: CatalogProvider[];
233
+ meta: Record<string, ProviderInfo>;
234
+ models: Record<string, ModelOption[]>;
235
+ preferences: Record<string, string>;
236
+ icons: Record<string, string>;
237
+ modelSelection: ModelSelectionState;
238
+ configManaged: string[];
239
+ };
240
+
241
+ skills: Skill[];
242
+ mcpServers: Record<string, McpConfig>;
243
+
244
+ tools: {
245
+ nixPackages: string[];
246
+ permissions: PermissionGrant[];
247
+ schedules: Schedule[];
248
+ registries: RegistryEntry[];
249
+ globalRegistries: RegistryEntry[];
250
+ };
251
+
252
+ settings: {
253
+ verboseLogging: boolean;
254
+ memoryEnabled: boolean;
255
+ };
256
+ }
@@ -0,0 +1,73 @@
1
+ import { createLogger } from "./logger";
2
+
3
+ const logger = createLogger("command-registry");
4
+
5
+ /**
6
+ * Context passed to command handlers.
7
+ * Shaped to match OpenClaw's registerCommand() API for future migration.
8
+ */
9
+ export interface CommandContext {
10
+ userId: string;
11
+ channelId: string;
12
+ conversationId?: string;
13
+ connectionId?: string;
14
+ agentId?: string;
15
+ args: string;
16
+ reply: (
17
+ text: string,
18
+ options?: { url?: string; urlLabel?: string; webApp?: boolean }
19
+ ) => Promise<void>;
20
+ platform: string;
21
+ }
22
+
23
+ /**
24
+ * A registered command definition.
25
+ */
26
+ export interface CommandDefinition {
27
+ name: string;
28
+ description: string;
29
+ handler: (ctx: CommandContext) => Promise<void>;
30
+ }
31
+
32
+ /**
33
+ * Shared command registry used by all platform adapters.
34
+ * Matches OpenClaw's registerCommand() shape so migration is a simple swap.
35
+ */
36
+ export class CommandRegistry {
37
+ private commands = new Map<string, CommandDefinition>();
38
+
39
+ register(cmd: CommandDefinition): void {
40
+ this.commands.set(cmd.name, cmd);
41
+ logger.debug({ command: cmd.name }, "Command registered");
42
+ }
43
+
44
+ get(name: string): CommandDefinition | undefined {
45
+ return this.commands.get(name);
46
+ }
47
+
48
+ getAll(): CommandDefinition[] {
49
+ return Array.from(this.commands.values());
50
+ }
51
+
52
+ /**
53
+ * Try to handle a command by name. Returns true if handled.
54
+ */
55
+ async tryHandle(name: string, ctx: CommandContext): Promise<boolean> {
56
+ const cmd = this.commands.get(name);
57
+ if (!cmd) return false;
58
+
59
+ try {
60
+ await cmd.handler(ctx);
61
+ return true;
62
+ } catch (error) {
63
+ logger.error(
64
+ { command: name, error: String(error) },
65
+ "Command handler failed"
66
+ );
67
+ await ctx.reply(
68
+ "Sorry, something went wrong executing that command. Please try again."
69
+ );
70
+ return true;
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Shared constants across all packages
5
+ * These are platform-agnostic and used by core, gateway, and platform adapters
6
+ */
7
+
8
+ // Time constants (milliseconds)
9
+ export const TIME = {
10
+ /** One hour in milliseconds */
11
+ HOUR_MS: 60 * 60 * 1000,
12
+ /** One day in milliseconds */
13
+ DAY_MS: 24 * 60 * 60 * 1000,
14
+ /** One hour in seconds */
15
+ HOUR_SECONDS: 3600,
16
+ /** One day in seconds */
17
+ DAY_SECONDS: 24 * 60 * 60,
18
+ /** One minute in milliseconds */
19
+ MINUTE_MS: 60 * 1000,
20
+ /** Five seconds in milliseconds */
21
+ FIVE_SECONDS_MS: 5000,
22
+ /** Thirty seconds */
23
+ THIRTY_SECONDS: 30,
24
+ /** Three hours in milliseconds (for interaction timeout) */
25
+ THREE_HOURS_MS: 3 * 60 * 60 * 1000,
26
+ /** Three hours in seconds (for Redis TTL) */
27
+ THREE_HOURS_SECONDS: 3 * 60 * 60,
28
+ } as const;
29
+
30
+ // Redis key prefixes
31
+ export const REDIS_KEYS = {
32
+ /** Prefix for bot message timestamps */
33
+ BOT_MESSAGES: "bot_messages:",
34
+ /** Prefix for session data */
35
+ SESSION: "session:",
36
+ /** Prefix for thread ownership */
37
+ THREAD_OWNER: "thread_owner:",
38
+ /** Prefix for MCP credentials */
39
+ MCP_CREDENTIAL: "mcp:credential:",
40
+ /** Prefix for MCP OAuth state */
41
+ MCP_OAUTH_STATE: "mcp:oauth:state:",
42
+ /** Prefix for MCP inputs */
43
+ MCP_INPUT: "mcp:input:",
44
+ } as const;
45
+
46
+ // Default configuration values
47
+ export const DEFAULTS = {
48
+ /** Default session TTL in milliseconds */
49
+ SESSION_TTL_MS: TIME.DAY_MS,
50
+ /** Default session TTL in seconds */
51
+ SESSION_TTL_SECONDS: TIME.DAY_SECONDS,
52
+ /** Default queue expiration in hours */
53
+ QUEUE_EXPIRE_HOURS: 24,
54
+ /** Default retry limit for queue operations */
55
+ QUEUE_RETRY_LIMIT: 3,
56
+ /** Default retry delay in seconds */
57
+ QUEUE_RETRY_DELAY_SECONDS: TIME.THIRTY_SECONDS,
58
+ /** Default session timeout in minutes */
59
+ SESSION_TIMEOUT_MINUTES: 5,
60
+ } as const;