@lakitu/sdk 0.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.
Files changed (111) hide show
  1. package/README.md +166 -0
  2. package/convex/_generated/api.d.ts +45 -0
  3. package/convex/_generated/api.js +23 -0
  4. package/convex/_generated/dataModel.d.ts +58 -0
  5. package/convex/_generated/server.d.ts +143 -0
  6. package/convex/_generated/server.js +93 -0
  7. package/convex/cloud/CLAUDE.md +238 -0
  8. package/convex/cloud/_generated/api.ts +84 -0
  9. package/convex/cloud/_generated/component.ts +861 -0
  10. package/convex/cloud/_generated/dataModel.ts +60 -0
  11. package/convex/cloud/_generated/server.ts +156 -0
  12. package/convex/cloud/convex.config.ts +16 -0
  13. package/convex/cloud/index.ts +29 -0
  14. package/convex/cloud/intentSchema/generate.ts +447 -0
  15. package/convex/cloud/intentSchema/index.ts +16 -0
  16. package/convex/cloud/intentSchema/types.ts +418 -0
  17. package/convex/cloud/ksaPolicy.ts +554 -0
  18. package/convex/cloud/mail.ts +92 -0
  19. package/convex/cloud/schema.ts +322 -0
  20. package/convex/cloud/utils/kanbanContext.ts +229 -0
  21. package/convex/cloud/workflows/agentBoard.ts +451 -0
  22. package/convex/cloud/workflows/agentPrompt.ts +272 -0
  23. package/convex/cloud/workflows/agentThread.ts +374 -0
  24. package/convex/cloud/workflows/compileSandbox.ts +146 -0
  25. package/convex/cloud/workflows/crudBoard.ts +217 -0
  26. package/convex/cloud/workflows/crudKSAs.ts +262 -0
  27. package/convex/cloud/workflows/crudLorobeads.ts +371 -0
  28. package/convex/cloud/workflows/crudSkills.ts +205 -0
  29. package/convex/cloud/workflows/crudThreads.ts +708 -0
  30. package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
  31. package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
  32. package/convex/sandbox/README.md +90 -0
  33. package/convex/sandbox/_generated/api.d.ts +2934 -0
  34. package/convex/sandbox/_generated/api.js +23 -0
  35. package/convex/sandbox/_generated/dataModel.d.ts +60 -0
  36. package/convex/sandbox/_generated/server.d.ts +143 -0
  37. package/convex/sandbox/_generated/server.js +93 -0
  38. package/convex/sandbox/actions/bash.ts +130 -0
  39. package/convex/sandbox/actions/browser.ts +282 -0
  40. package/convex/sandbox/actions/file.ts +336 -0
  41. package/convex/sandbox/actions/lsp.ts +325 -0
  42. package/convex/sandbox/actions/pdf.ts +119 -0
  43. package/convex/sandbox/agent/codeExecLoop.ts +535 -0
  44. package/convex/sandbox/agent/decisions.ts +284 -0
  45. package/convex/sandbox/agent/index.ts +515 -0
  46. package/convex/sandbox/agent/subagents.ts +651 -0
  47. package/convex/sandbox/brandResearch/index.ts +417 -0
  48. package/convex/sandbox/context/index.ts +7 -0
  49. package/convex/sandbox/context/session.ts +402 -0
  50. package/convex/sandbox/convex.config.ts +17 -0
  51. package/convex/sandbox/index.ts +51 -0
  52. package/convex/sandbox/nodeActions/codeExec.ts +130 -0
  53. package/convex/sandbox/planning/beads.ts +187 -0
  54. package/convex/sandbox/planning/index.ts +8 -0
  55. package/convex/sandbox/planning/sync.ts +194 -0
  56. package/convex/sandbox/prompts/codeExec.ts +852 -0
  57. package/convex/sandbox/prompts/modes.ts +231 -0
  58. package/convex/sandbox/prompts/system.ts +142 -0
  59. package/convex/sandbox/schema.ts +510 -0
  60. package/convex/sandbox/state/artifacts.ts +99 -0
  61. package/convex/sandbox/state/checkpoints.ts +341 -0
  62. package/convex/sandbox/state/files.ts +383 -0
  63. package/convex/sandbox/state/index.ts +10 -0
  64. package/convex/sandbox/state/verification.actions.ts +268 -0
  65. package/convex/sandbox/state/verification.ts +101 -0
  66. package/convex/sandbox/tsconfig.json +25 -0
  67. package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
  68. package/dist/cli/commands/build.d.ts +19 -0
  69. package/dist/cli/commands/build.d.ts.map +1 -0
  70. package/dist/cli/commands/build.js +223 -0
  71. package/dist/cli/commands/init.d.ts +16 -0
  72. package/dist/cli/commands/init.d.ts.map +1 -0
  73. package/dist/cli/commands/init.js +148 -0
  74. package/dist/cli/commands/publish.d.ts +12 -0
  75. package/dist/cli/commands/publish.d.ts.map +1 -0
  76. package/dist/cli/commands/publish.js +33 -0
  77. package/dist/cli/index.d.ts +14 -0
  78. package/dist/cli/index.d.ts.map +1 -0
  79. package/dist/cli/index.js +40 -0
  80. package/dist/sdk/builders.d.ts +104 -0
  81. package/dist/sdk/builders.d.ts.map +1 -0
  82. package/dist/sdk/builders.js +214 -0
  83. package/dist/sdk/index.d.ts +29 -0
  84. package/dist/sdk/index.d.ts.map +1 -0
  85. package/dist/sdk/index.js +38 -0
  86. package/dist/sdk/types.d.ts +107 -0
  87. package/dist/sdk/types.d.ts.map +1 -0
  88. package/dist/sdk/types.js +6 -0
  89. package/ksa/README.md +263 -0
  90. package/ksa/_generated/REFERENCE.md +2954 -0
  91. package/ksa/_generated/registry.ts +257 -0
  92. package/ksa/_shared/configReader.ts +302 -0
  93. package/ksa/_shared/configSchemas.ts +649 -0
  94. package/ksa/_shared/gateway.ts +175 -0
  95. package/ksa/_shared/ksaBehaviors.ts +411 -0
  96. package/ksa/_shared/ksaProxy.ts +248 -0
  97. package/ksa/_shared/localDb.ts +302 -0
  98. package/ksa/index.ts +134 -0
  99. package/package.json +93 -0
  100. package/runtime/browser/agent-browser.ts +330 -0
  101. package/runtime/entrypoint.ts +194 -0
  102. package/runtime/lsp/manager.ts +366 -0
  103. package/runtime/pdf/pdf-generator.ts +50 -0
  104. package/runtime/pdf/renderer.ts +357 -0
  105. package/runtime/pdf/schema.ts +97 -0
  106. package/runtime/services/file-watcher.ts +191 -0
  107. package/template/build.ts +307 -0
  108. package/template/e2b/Dockerfile +69 -0
  109. package/template/e2b/e2b.toml +13 -0
  110. package/template/e2b/prebuild.sh +68 -0
  111. package/template/e2b/start.sh +14 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Shared Gateway Module
3
+ *
4
+ * Common utilities for calling the cloud gateway from KSAs.
5
+ * All KSAs should use this module instead of implementing their own fetch logic.
6
+ *
7
+ * Optimizations:
8
+ * - callGateway: Standard blocking call (await response)
9
+ * - callGatewayBatch: Multiple calls in single HTTP request (parallel execution)
10
+ * - fireAndForget: Non-blocking call (don't wait for response)
11
+ */
12
+
13
+ import { readFileSync, existsSync } from "fs";
14
+
15
+ // Load env vars from /home/user/.env if present (set by pool claim)
16
+ function loadEnvFile(): Record<string, string> {
17
+ const envPath = "/home/user/.env";
18
+ if (!existsSync(envPath)) return {};
19
+
20
+ try {
21
+ const content = readFileSync(envPath, "utf-8");
22
+ const envVars: Record<string, string> = {};
23
+ for (const line of content.split("\n")) {
24
+ const match = line.match(/^export\s+(\w+)="([^"]*)"/);
25
+ if (match) {
26
+ envVars[match[1]] = match[2];
27
+ }
28
+ }
29
+ return envVars;
30
+ } catch {
31
+ return {};
32
+ }
33
+ }
34
+
35
+ const envFile = loadEnvFile();
36
+
37
+ // Gateway config from environment (set by sandbox runtime)
38
+ // Check both process.env and .env file (for pooled sandboxes)
39
+ const GATEWAY_URL = process.env.GATEWAY_URL || envFile.GATEWAY_URL || "http://localhost:3210";
40
+ const JWT = process.env.SANDBOX_JWT || envFile.SANDBOX_JWT || "";
41
+
42
+ // Export THREAD_ID, CARD_ID, and WORKSPACE_ID for other KSAs to use
43
+ export const THREAD_ID = process.env.THREAD_ID || envFile.THREAD_ID;
44
+ export const CARD_ID = process.env.CARD_ID || envFile.CARD_ID;
45
+ export const WORKSPACE_ID = process.env.WORKSPACE_ID || envFile.WORKSPACE_ID;
46
+
47
+ /**
48
+ * Call the cloud gateway to invoke a Convex service.
49
+ *
50
+ * @param path - Service path (e.g., 'services.Valyu.internal.search')
51
+ * @param args - Arguments to pass to the service
52
+ * @returns Service response data
53
+ *
54
+ * @example
55
+ * const data = await callGateway('services.SendGrid.internal.send', {
56
+ * to: 'user@example.com',
57
+ * subject: 'Hello',
58
+ * text: 'World'
59
+ * });
60
+ */
61
+ export async function callGateway<T = unknown>(
62
+ path: string,
63
+ args: Record<string, unknown>,
64
+ type?: "query" | "mutation" | "action"
65
+ ): Promise<T> {
66
+ const response = await fetch(`${GATEWAY_URL}/agent/call`, {
67
+ method: "POST",
68
+ headers: {
69
+ "Content-Type": "application/json",
70
+ Authorization: `Bearer ${JWT}`,
71
+ },
72
+ body: JSON.stringify({ path, args, type }),
73
+ });
74
+
75
+ if (!response.ok) {
76
+ const text = await response.text();
77
+ throw new Error(`Gateway error (${response.status}): ${text}`);
78
+ }
79
+
80
+ const result = await response.json();
81
+ if (!result.ok) {
82
+ throw new Error(`Service error: ${result.error || JSON.stringify(result)}`);
83
+ }
84
+
85
+ return result.data as T;
86
+ }
87
+
88
+ interface BatchCall {
89
+ path: string;
90
+ args?: Record<string, unknown>;
91
+ type?: "query" | "mutation" | "action";
92
+ }
93
+
94
+ interface BatchResult<T = unknown> {
95
+ ok: boolean;
96
+ data?: T;
97
+ error?: string;
98
+ }
99
+
100
+ /**
101
+ * Execute multiple gateway calls in a single HTTP request.
102
+ * All calls execute in parallel on the server.
103
+ *
104
+ * @param calls - Array of calls to execute
105
+ * @returns Array of results (same order as calls)
106
+ *
107
+ * @example
108
+ * const [users, posts] = await callGatewayBatch([
109
+ * { path: 'services.Users.internal.list', args: { limit: 10 } },
110
+ * { path: 'services.Posts.internal.recent', args: {} }
111
+ * ]);
112
+ */
113
+ export async function callGatewayBatch<T extends unknown[] = unknown[]>(
114
+ calls: BatchCall[]
115
+ ): Promise<BatchResult<T[number]>[]> {
116
+ const response = await fetch(`${GATEWAY_URL}/agent/batch`, {
117
+ method: "POST",
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ Authorization: `Bearer ${JWT}`,
121
+ },
122
+ body: JSON.stringify({ calls }),
123
+ });
124
+
125
+ if (!response.ok) {
126
+ const text = await response.text();
127
+ throw new Error(`Gateway batch error (${response.status}): ${text}`);
128
+ }
129
+
130
+ const result = await response.json();
131
+ if (!result.ok) {
132
+ throw new Error(`Batch error: ${result.error || JSON.stringify(result)}`);
133
+ }
134
+
135
+ return result.results;
136
+ }
137
+
138
+ /**
139
+ * Fire-and-forget gateway call - doesn't wait for response.
140
+ * Use for non-critical operations like logging, analytics, beads updates.
141
+ *
142
+ * @param path - Service path
143
+ * @param args - Arguments to pass
144
+ *
145
+ * @example
146
+ * // Log something without blocking
147
+ * fireAndForget('agent.workflows.sandboxConvex.appendLogs', { sessionId, logs });
148
+ */
149
+ export function fireAndForget(
150
+ path: string,
151
+ args: Record<string, unknown>,
152
+ type?: "query" | "mutation" | "action"
153
+ ): void {
154
+ fetch(`${GATEWAY_URL}/agent/call`, {
155
+ method: "POST",
156
+ headers: {
157
+ "Content-Type": "application/json",
158
+ Authorization: `Bearer ${JWT}`,
159
+ },
160
+ body: JSON.stringify({ path, args, type }),
161
+ }).catch(() => {
162
+ // Intentionally ignore errors - fire and forget
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Get the gateway configuration.
168
+ * Useful for debugging or checking connectivity.
169
+ */
170
+ export function getGatewayConfig() {
171
+ return {
172
+ url: GATEWAY_URL,
173
+ hasJwt: !!JWT,
174
+ };
175
+ }
@@ -0,0 +1,411 @@
1
+ /**
2
+ * KSA Behaviors - Per-KSA Hooks for Local DB Integration
3
+ *
4
+ * Defines before/after hooks that the proxy layer calls automatically.
5
+ * These hooks enable file tracking, caching, session persistence, etc.
6
+ * without requiring changes to individual KSA implementations.
7
+ *
8
+ * Hook Types:
9
+ * - before: Called before function execution, can return cached result
10
+ * - after: Called after function execution, for tracking/caching
11
+ * - transform: Transform result before returning (optional)
12
+ */
13
+
14
+ import { localDb, getSessionId, getThreadId, cacheKey, simpleHash } from "./localDb";
15
+ import type { FrameworkConfig } from "./configSchemas";
16
+
17
+ // ============================================================================
18
+ // Types
19
+ // ============================================================================
20
+
21
+ export interface BeforeHookResult {
22
+ /** If true, skip function execution and return cachedResult */
23
+ skipExecution?: boolean;
24
+ /** Cached result to return if skipExecution is true */
25
+ cachedResult?: unknown;
26
+ }
27
+
28
+ export interface HookContext {
29
+ ksaName: string;
30
+ funcName: string;
31
+ args: unknown[];
32
+ config: FrameworkConfig;
33
+ startTime: number;
34
+ }
35
+
36
+ export type BeforeHook = (
37
+ ctx: HookContext
38
+ ) => Promise<BeforeHookResult | void> | BeforeHookResult | void;
39
+
40
+ export type AfterHook = (
41
+ ctx: HookContext,
42
+ result: unknown,
43
+ error?: Error
44
+ ) => Promise<void> | void;
45
+
46
+ export interface KSABehavior {
47
+ /** Hooks for specific functions */
48
+ functions?: Record<string, {
49
+ before?: BeforeHook;
50
+ after?: AfterHook;
51
+ }>;
52
+ /** Default hooks for all functions in this KSA */
53
+ default?: {
54
+ before?: BeforeHook;
55
+ after?: AfterHook;
56
+ };
57
+ }
58
+
59
+ // ============================================================================
60
+ // Cache Helpers
61
+ // ============================================================================
62
+
63
+ /**
64
+ * Check cache for a function result.
65
+ */
66
+ async function checkCache(
67
+ ctx: HookContext
68
+ ): Promise<BeforeHookResult | void> {
69
+ if (!ctx.config.cacheResults) return;
70
+
71
+ const key = cacheKey(ctx.ksaName, ctx.funcName, ctx.args);
72
+ const taskHash = simpleHash(key);
73
+
74
+ try {
75
+ const cached = await localDb.query<{
76
+ context: { result: unknown; timestamp: number };
77
+ } | null>("context/session.getCachedContext", {
78
+ sessionId: getSessionId(),
79
+ taskHash,
80
+ });
81
+
82
+ if (cached?.context) {
83
+ const age = Date.now() - cached.context.timestamp;
84
+ if (age < ctx.config.cacheTTLMs) {
85
+ return {
86
+ skipExecution: true,
87
+ cachedResult: cached.context.result,
88
+ };
89
+ }
90
+ }
91
+ } catch {
92
+ // Cache miss or error - continue with execution
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Store result in cache.
98
+ */
99
+ function storeInCache(ctx: HookContext, result: unknown): void {
100
+ if (!ctx.config.cacheResults) return;
101
+
102
+ const key = cacheKey(ctx.ksaName, ctx.funcName, ctx.args);
103
+ const taskHash = simpleHash(key);
104
+
105
+ localDb.fire("context/session.cacheContext", {
106
+ sessionId: getSessionId(),
107
+ taskHash,
108
+ context: {
109
+ relevantFiles: [],
110
+ toolsNeeded: [ctx.ksaName],
111
+ tokenBudget: 0,
112
+ // Store our custom fields
113
+ result,
114
+ timestamp: Date.now(),
115
+ ksaName: ctx.ksaName,
116
+ funcName: ctx.funcName,
117
+ },
118
+ ttlMs: ctx.config.cacheTTLMs,
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Log a KSA function call for tracking.
124
+ */
125
+ function logCall(
126
+ ctx: HookContext,
127
+ result: unknown,
128
+ error?: Error
129
+ ): void {
130
+ if (!ctx.config.trackCalls) return;
131
+
132
+ const threadId = getThreadId();
133
+ if (!threadId) return;
134
+
135
+ localDb.fire("agentDecisions.create", {
136
+ threadId,
137
+ task: `${ctx.ksaName}.${ctx.funcName}`,
138
+ decisionType: "tool_selection",
139
+ selectedTools: [ctx.ksaName],
140
+ reasoning: `Called ${ctx.funcName} with ${ctx.args.length} args`,
141
+ expectedOutcome: error ? "failure" : "success",
142
+ metadata: {
143
+ ksaName: ctx.ksaName,
144
+ funcName: ctx.funcName,
145
+ durationMs: Date.now() - ctx.startTime,
146
+ success: !error,
147
+ error: error?.message,
148
+ },
149
+ timestamp: Date.now(),
150
+ });
151
+ }
152
+
153
+ // ============================================================================
154
+ // File KSA Behaviors
155
+ // ============================================================================
156
+
157
+ const fileBehaviors: KSABehavior = {
158
+ functions: {
159
+ read: {
160
+ after: (ctx, result) => {
161
+ if (!ctx.config.trackFileState) return;
162
+
163
+ const [path] = ctx.args as [string];
164
+ const content = result as string;
165
+
166
+ localDb.fire("state/files.trackFileAccess", {
167
+ path,
168
+ operation: "read",
169
+ size: content?.length,
170
+ contentHash: content ? simpleHash(content) : undefined,
171
+ threadId: getThreadId(),
172
+ });
173
+ },
174
+ },
175
+ write: {
176
+ after: (ctx) => {
177
+ if (!ctx.config.trackFileState) return;
178
+
179
+ const [path, content] = ctx.args as [string, string];
180
+
181
+ localDb.fire("state/files.trackFileAccess", {
182
+ path,
183
+ operation: "write",
184
+ size: content?.length,
185
+ contentHash: content ? simpleHash(content) : undefined,
186
+ threadId: getThreadId(),
187
+ });
188
+ },
189
+ },
190
+ edit: {
191
+ after: (ctx) => {
192
+ if (!ctx.config.trackFileState) return;
193
+
194
+ const [path, oldText, newText] = ctx.args as [string, string, string];
195
+
196
+ // Generate a simple diff representation
197
+ const diff = `- ${oldText.slice(0, 100)}...\n+ ${newText.slice(0, 100)}...`;
198
+
199
+ localDb.fire("state/files.recordEdit", {
200
+ path,
201
+ oldContent: oldText,
202
+ newContent: newText,
203
+ diff,
204
+ verified: false,
205
+ threadId: getThreadId(),
206
+ });
207
+ },
208
+ },
209
+ glob: {
210
+ // Cache glob results for 1 minute
211
+ before: checkCache,
212
+ after: (ctx, result) => {
213
+ storeInCache(ctx, result);
214
+ },
215
+ },
216
+ grep: {
217
+ // Cache grep results for 1 minute
218
+ before: checkCache,
219
+ after: (ctx, result) => {
220
+ storeInCache(ctx, result);
221
+ },
222
+ },
223
+ },
224
+ };
225
+
226
+ // ============================================================================
227
+ // Web KSA Behaviors
228
+ // ============================================================================
229
+
230
+ const webBehaviors: KSABehavior = {
231
+ functions: {
232
+ search: {
233
+ before: checkCache,
234
+ after: (ctx, result) => {
235
+ storeInCache(ctx, result);
236
+ },
237
+ },
238
+ scrape: {
239
+ before: checkCache,
240
+ after: (ctx, result) => {
241
+ storeInCache(ctx, result);
242
+ },
243
+ },
244
+ news: {
245
+ before: checkCache,
246
+ after: (ctx, result) => {
247
+ storeInCache(ctx, result);
248
+ },
249
+ },
250
+ },
251
+ };
252
+
253
+ // ============================================================================
254
+ // Social KSA Behaviors
255
+ // ============================================================================
256
+
257
+ const socialBehaviors: KSABehavior = {
258
+ default: {
259
+ // Cache all social API results
260
+ before: checkCache,
261
+ after: (ctx, result) => {
262
+ storeInCache(ctx, result);
263
+ },
264
+ },
265
+ };
266
+
267
+ // ============================================================================
268
+ // Companies KSA Behaviors
269
+ // ============================================================================
270
+
271
+ const companiesBehaviors: KSABehavior = {
272
+ default: {
273
+ // Cache all company enrichment results
274
+ before: checkCache,
275
+ after: (ctx, result) => {
276
+ storeInCache(ctx, result);
277
+ },
278
+ },
279
+ };
280
+
281
+ // ============================================================================
282
+ // News KSA Behaviors
283
+ // ============================================================================
284
+
285
+ const newsBehaviors: KSABehavior = {
286
+ default: {
287
+ before: checkCache,
288
+ after: (ctx, result) => {
289
+ storeInCache(ctx, result);
290
+ },
291
+ },
292
+ };
293
+
294
+ // ============================================================================
295
+ // Beads KSA Behaviors
296
+ // ============================================================================
297
+
298
+ const beadsBehaviors: KSABehavior = {
299
+ // Beads already uses local DB directly, no additional behaviors needed
300
+ // But we track calls for observability
301
+ default: {
302
+ after: (ctx, result, error) => {
303
+ logCall(ctx, result, error);
304
+ },
305
+ },
306
+ };
307
+
308
+ // ============================================================================
309
+ // Artifacts KSA Behaviors
310
+ // ============================================================================
311
+
312
+ const artifactsBehaviors: KSABehavior = {
313
+ functions: {
314
+ listArtifacts: {
315
+ before: checkCache,
316
+ after: (ctx, result) => {
317
+ storeInCache(ctx, result);
318
+ },
319
+ },
320
+ },
321
+ };
322
+
323
+ // ============================================================================
324
+ // Browser KSA Behaviors
325
+ // ============================================================================
326
+
327
+ const browserBehaviors: KSABehavior = {
328
+ functions: {
329
+ screenshot: {
330
+ // Don't cache screenshots
331
+ after: (ctx, _result, error) => {
332
+ logCall(ctx, "[screenshot]", error);
333
+ },
334
+ },
335
+ },
336
+ default: {
337
+ // Log all browser actions
338
+ after: (ctx, result, error) => {
339
+ logCall(ctx, result, error);
340
+ },
341
+ },
342
+ };
343
+
344
+ // ============================================================================
345
+ // Default Behaviors (for any KSA without specific behaviors)
346
+ // ============================================================================
347
+
348
+ const defaultBehaviors: KSABehavior = {
349
+ default: {
350
+ before: checkCache,
351
+ after: (ctx, result, error) => {
352
+ if (!error) {
353
+ storeInCache(ctx, result);
354
+ }
355
+ logCall(ctx, result, error);
356
+ },
357
+ },
358
+ };
359
+
360
+ // ============================================================================
361
+ // Registry
362
+ // ============================================================================
363
+
364
+ /**
365
+ * KSA behavior registry.
366
+ * Maps KSA names to their behavior definitions.
367
+ */
368
+ export const KSA_BEHAVIORS: Record<string, KSABehavior> = {
369
+ file: fileBehaviors,
370
+ web: webBehaviors,
371
+ social: socialBehaviors,
372
+ companies: companiesBehaviors,
373
+ news: newsBehaviors,
374
+ beads: beadsBehaviors,
375
+ artifacts: artifactsBehaviors,
376
+ browser: browserBehaviors,
377
+ };
378
+
379
+ /**
380
+ * Get behavior for a specific KSA function.
381
+ * Falls back to default behaviors if no specific behavior is defined.
382
+ */
383
+ export function getBehavior(
384
+ ksaName: string,
385
+ funcName: string
386
+ ): { before?: BeforeHook; after?: AfterHook } {
387
+ const ksaBehavior = KSA_BEHAVIORS[ksaName];
388
+
389
+ if (ksaBehavior) {
390
+ // Check for function-specific behavior
391
+ const funcBehavior = ksaBehavior.functions?.[funcName];
392
+ if (funcBehavior) {
393
+ return funcBehavior;
394
+ }
395
+
396
+ // Fall back to KSA default behavior
397
+ if (ksaBehavior.default) {
398
+ return ksaBehavior.default;
399
+ }
400
+ }
401
+
402
+ // Fall back to global default behavior
403
+ return defaultBehaviors.default || {};
404
+ }
405
+
406
+ /**
407
+ * Check if a KSA has any custom behaviors defined.
408
+ */
409
+ export function hasCustomBehaviors(ksaName: string): boolean {
410
+ return ksaName in KSA_BEHAVIORS;
411
+ }