@okrlinkhub/agent-bridge 0.2.0 → 2.0.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 (69) hide show
  1. package/README.md +96 -127
  2. package/dist/cli/init.d.ts +3 -0
  3. package/dist/cli/init.d.ts.map +1 -0
  4. package/dist/cli/init.js +100 -0
  5. package/dist/cli/init.js.map +1 -0
  6. package/dist/client/index.d.ts +50 -262
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +127 -536
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/component/_generated/api.d.ts +4 -8
  11. package/dist/component/_generated/api.d.ts.map +1 -1
  12. package/dist/component/_generated/api.js.map +1 -1
  13. package/dist/component/_generated/component.d.ts +58 -257
  14. package/dist/component/_generated/component.d.ts.map +1 -1
  15. package/dist/component/agentBridgeUtils.d.ts +8 -0
  16. package/dist/component/agentBridgeUtils.d.ts.map +1 -0
  17. package/dist/component/agentBridgeUtils.js +33 -0
  18. package/dist/component/agentBridgeUtils.js.map +1 -0
  19. package/dist/component/agents.d.ts +27 -0
  20. package/dist/component/agents.d.ts.map +1 -0
  21. package/dist/component/agents.js +94 -0
  22. package/dist/component/agents.js.map +1 -0
  23. package/dist/component/gateway.d.ts +25 -55
  24. package/dist/component/gateway.d.ts.map +1 -1
  25. package/dist/component/gateway.js +120 -205
  26. package/dist/component/gateway.js.map +1 -1
  27. package/dist/component/permissions.d.ts +30 -84
  28. package/dist/component/permissions.d.ts.map +1 -1
  29. package/dist/component/permissions.js +80 -203
  30. package/dist/component/permissions.js.map +1 -1
  31. package/dist/component/schema.d.ts +55 -223
  32. package/dist/component/schema.d.ts.map +1 -1
  33. package/dist/component/schema.js +30 -126
  34. package/dist/component/schema.js.map +1 -1
  35. package/dist/react/index.d.ts +2 -2
  36. package/dist/react/index.d.ts.map +1 -1
  37. package/dist/react/index.js +2 -3
  38. package/dist/react/index.js.map +1 -1
  39. package/package.json +3 -2
  40. package/src/cli/init.ts +116 -0
  41. package/src/client/index.ts +224 -792
  42. package/src/component/_generated/api.ts +4 -8
  43. package/src/component/_generated/component.ts +55 -312
  44. package/src/component/agentBridgeUtils.ts +52 -0
  45. package/src/component/agents.ts +106 -0
  46. package/src/component/gateway.ts +142 -252
  47. package/src/component/permissions.ts +89 -259
  48. package/src/component/schema.ts +32 -146
  49. package/src/react/index.ts +5 -6
  50. package/dist/component/channels.d.ts +0 -83
  51. package/dist/component/channels.d.ts.map +0 -1
  52. package/dist/component/channels.js +0 -288
  53. package/dist/component/channels.js.map +0 -1
  54. package/dist/component/circuitBreaker.d.ts +0 -73
  55. package/dist/component/circuitBreaker.d.ts.map +0 -1
  56. package/dist/component/circuitBreaker.js +0 -216
  57. package/dist/component/circuitBreaker.js.map +0 -1
  58. package/dist/component/provisioning.d.ts +0 -87
  59. package/dist/component/provisioning.d.ts.map +0 -1
  60. package/dist/component/provisioning.js +0 -343
  61. package/dist/component/provisioning.js.map +0 -1
  62. package/dist/component/registry.d.ts +0 -46
  63. package/dist/component/registry.d.ts.map +0 -1
  64. package/dist/component/registry.js +0 -121
  65. package/dist/component/registry.js.map +0 -1
  66. package/src/component/channels.ts +0 -374
  67. package/src/component/circuitBreaker.ts +0 -250
  68. package/src/component/provisioning.ts +0 -402
  69. package/src/component/registry.ts +0 -152
@@ -1,402 +0,0 @@
1
- import { v } from "convex/values";
2
- import { mutation, query, internalMutation } from "./_generated/server.js";
3
-
4
- // --- Token hashing utility (SHA-256, no external deps) ---
5
-
6
- async function hashToken(token: string): Promise<string> {
7
- const encoder = new TextEncoder();
8
- const data = encoder.encode(token);
9
- const hash = await crypto.subtle.digest("SHA-256", data);
10
- return Array.from(new Uint8Array(hash))
11
- .map((b) => b.toString(16).padStart(2, "0"))
12
- .join("");
13
- }
14
-
15
- // --- Public mutations ---
16
-
17
- /**
18
- * Generate a provisioning token (APT) for an employee.
19
- * Called by IT admin. Returns the plaintext token (store it securely!).
20
- */
21
- export const generateProvisioningToken = mutation({
22
- args: {
23
- employeeEmail: v.string(),
24
- department: v.string(),
25
- maxApps: v.optional(v.number()),
26
- expiresInDays: v.optional(v.number()),
27
- createdBy: v.string(),
28
- },
29
- returns: v.object({
30
- token: v.string(),
31
- expiresAt: v.number(),
32
- }),
33
- handler: async (ctx, args) => {
34
- const token = "apt_live_" + crypto.randomUUID().replace(/-/g, "");
35
- const tokenHash = await hashToken(token);
36
- const expiresAt =
37
- Date.now() + (args.expiresInDays ?? 7) * 24 * 60 * 60 * 1000;
38
-
39
- await ctx.db.insert("provisioningTokens", {
40
- tokenHash,
41
- employeeEmail: args.employeeEmail,
42
- department: args.department,
43
- maxApps: args.maxApps ?? 5,
44
- usedCount: 0,
45
- expiresAt,
46
- isActive: true,
47
- createdBy: args.createdBy,
48
- });
49
-
50
- return { token, expiresAt };
51
- },
52
- });
53
-
54
- /**
55
- * Provision an agent on a specific app.
56
- * Called by the employee (or their OpenClaw agent on bootstrap).
57
- * Validates the APT, creates/finds the agent, creates an app instance.
58
- */
59
- export const provisionAgent = mutation({
60
- args: {
61
- provisioningToken: v.string(),
62
- appName: v.string(),
63
- },
64
- returns: v.object({
65
- agentId: v.string(),
66
- instanceToken: v.string(),
67
- expiresAt: v.number(),
68
- appName: v.string(),
69
- message: v.string(),
70
- }),
71
- handler: async (ctx, args) => {
72
- const tokenHash = await hashToken(args.provisioningToken);
73
-
74
- // Find and validate the provisioning token
75
- const tokenRecord = await ctx.db
76
- .query("provisioningTokens")
77
- .withIndex("by_token_hash", (q) => q.eq("tokenHash", tokenHash))
78
- .unique();
79
-
80
- if (!tokenRecord || !tokenRecord.isActive) {
81
- throw new Error("Invalid provisioning token");
82
- }
83
- if (tokenRecord.expiresAt < Date.now()) {
84
- throw new Error("Provisioning token has expired");
85
- }
86
- if (tokenRecord.usedCount >= tokenRecord.maxApps) {
87
- throw new Error("Maximum number of apps reached for this token");
88
- }
89
-
90
- // Find or create the agent
91
- let agent = await ctx.db
92
- .query("registeredAgents")
93
- .withIndex("by_email", (q) =>
94
- q.eq("employeeEmail", tokenRecord.employeeEmail),
95
- )
96
- .unique();
97
-
98
- let agentId: string;
99
-
100
- if (!agent) {
101
- // First provisioning: create global agent record
102
- agentId = crypto.randomUUID();
103
- await ctx.db.insert("registeredAgents", {
104
- agentId,
105
- employeeEmail: tokenRecord.employeeEmail,
106
- department: tokenRecord.department,
107
- firstRegisteredAt: Date.now(),
108
- lastSeenAt: Date.now(),
109
- isActive: true,
110
- });
111
- } else {
112
- if (!agent.isActive) {
113
- throw new Error("Agent has been revoked");
114
- }
115
- agentId = agent.agentId;
116
- // Update last seen
117
- await ctx.db.patch(agent._id, { lastSeenAt: Date.now() });
118
- }
119
-
120
- // Check if instance already exists for this app
121
- const existing = await ctx.db
122
- .query("agentAppInstances")
123
- .withIndex("by_agent_and_app", (q) =>
124
- q.eq("agentId", agentId).eq("appName", args.appName),
125
- )
126
- .unique();
127
-
128
- if (existing && existing.expiresAt > Date.now()) {
129
- // Return existing valid instance (but we can't return the token since we only store the hash)
130
- // Generate a new token and update the hash
131
- const instanceToken = crypto.randomUUID();
132
- const instanceTokenHash = await hashToken(instanceToken);
133
- const expiresAt = Date.now() + 24 * 60 * 60 * 1000;
134
-
135
- await ctx.db.patch(existing._id, {
136
- instanceTokenHash,
137
- expiresAt,
138
- lastActivityAt: Date.now(),
139
- });
140
-
141
- return {
142
- agentId,
143
- instanceToken,
144
- expiresAt,
145
- appName: args.appName,
146
- message: "Instance refreshed with new credentials",
147
- };
148
- }
149
-
150
- if (existing) {
151
- // Expired instance: delete and recreate
152
- await ctx.db.delete(existing._id);
153
- }
154
-
155
- // Create new instance
156
- const instanceToken = crypto.randomUUID();
157
- const instanceTokenHash = await hashToken(instanceToken);
158
- const expiresAt = Date.now() + 24 * 60 * 60 * 1000; // 24h
159
-
160
- await ctx.db.insert("agentAppInstances", {
161
- agentId,
162
- appName: args.appName,
163
- instanceTokenHash,
164
- registeredAt: Date.now(),
165
- expiresAt,
166
- lastActivityAt: Date.now(),
167
- monthlyRequests: 0,
168
- });
169
-
170
- // Increment token usage
171
- await ctx.db.patch(tokenRecord._id, {
172
- usedCount: tokenRecord.usedCount + 1,
173
- });
174
-
175
- // Setup default permissions from app-specific config
176
- const config = await ctx.db
177
- .query("appConfigs")
178
- .withIndex("by_app_name", (q) => q.eq("appName", args.appName))
179
- .unique();
180
- if (config && config.defaultPermissions.length > 0) {
181
- for (const perm of config.defaultPermissions) {
182
- await ctx.db.insert("functionPermissions", {
183
- agentId,
184
- appName: args.appName,
185
- functionPattern: perm.pattern,
186
- permission: perm.permission,
187
- rateLimitConfig: perm.rateLimitConfig,
188
- createdAt: Date.now(),
189
- createdBy: "system",
190
- });
191
- }
192
- }
193
-
194
- return {
195
- agentId,
196
- instanceToken,
197
- expiresAt,
198
- appName: args.appName,
199
- message:
200
- "Provisioning successful. Configure your agent with these credentials.",
201
- };
202
- },
203
- });
204
-
205
- /**
206
- * Refresh an expired instance token.
207
- * Called by the agent when its token expires.
208
- */
209
- export const refreshInstanceToken = mutation({
210
- args: {
211
- agentId: v.string(),
212
- appName: v.string(),
213
- currentTokenHash: v.string(), // Hash of the current (expired) token for verification
214
- },
215
- returns: v.object({
216
- instanceToken: v.string(),
217
- expiresAt: v.number(),
218
- }),
219
- handler: async (ctx, args) => {
220
- // Verify the agent exists and is active
221
- const agent = await ctx.db
222
- .query("registeredAgents")
223
- .withIndex("by_agent_id", (q) => q.eq("agentId", args.agentId))
224
- .unique();
225
-
226
- if (!agent || !agent.isActive) {
227
- throw new Error("Agent not found or has been revoked");
228
- }
229
-
230
- // Find the instance
231
- const instance = await ctx.db
232
- .query("agentAppInstances")
233
- .withIndex("by_agent_and_app", (q) =>
234
- q.eq("agentId", args.agentId).eq("appName", args.appName),
235
- )
236
- .unique();
237
-
238
- if (!instance) {
239
- throw new Error("No instance found for this agent and app");
240
- }
241
-
242
- // Verify the current token hash matches
243
- if (instance.instanceTokenHash !== args.currentTokenHash) {
244
- throw new Error("Token hash mismatch");
245
- }
246
-
247
- // Generate new token
248
- const instanceToken = crypto.randomUUID();
249
- const instanceTokenHash = await hashToken(instanceToken);
250
- const expiresAt = Date.now() + 24 * 60 * 60 * 1000;
251
-
252
- await ctx.db.patch(instance._id, {
253
- instanceTokenHash,
254
- expiresAt,
255
- lastActivityAt: Date.now(),
256
- });
257
-
258
- return { instanceToken, expiresAt };
259
- },
260
- });
261
-
262
- /**
263
- * Revoke an agent globally (IT admin operation).
264
- * All instances become invalid immediately.
265
- */
266
- export const revokeAgent = mutation({
267
- args: {
268
- agentId: v.string(),
269
- revokedBy: v.string(),
270
- },
271
- returns: v.boolean(),
272
- handler: async (ctx, args) => {
273
- const agent = await ctx.db
274
- .query("registeredAgents")
275
- .withIndex("by_agent_id", (q) => q.eq("agentId", args.agentId))
276
- .unique();
277
-
278
- if (!agent) return false;
279
-
280
- await ctx.db.patch(agent._id, {
281
- isActive: false,
282
- revokedAt: Date.now(),
283
- revokedBy: args.revokedBy,
284
- });
285
-
286
- return true;
287
- },
288
- });
289
-
290
- /**
291
- * Revoke a specific app instance for an agent.
292
- */
293
- export const revokeAppInstance = mutation({
294
- args: {
295
- agentId: v.string(),
296
- appName: v.string(),
297
- },
298
- returns: v.boolean(),
299
- handler: async (ctx, args) => {
300
- const instance = await ctx.db
301
- .query("agentAppInstances")
302
- .withIndex("by_agent_and_app", (q) =>
303
- q.eq("agentId", args.agentId).eq("appName", args.appName),
304
- )
305
- .unique();
306
-
307
- if (!instance) return false;
308
-
309
- await ctx.db.delete(instance._id);
310
- return true;
311
- },
312
- });
313
-
314
- /**
315
- * List all registered agents (admin query).
316
- */
317
- export const listAgents = query({
318
- args: {
319
- activeOnly: v.optional(v.boolean()),
320
- },
321
- returns: v.array(
322
- v.object({
323
- agentId: v.string(),
324
- employeeEmail: v.string(),
325
- department: v.string(),
326
- firstRegisteredAt: v.number(),
327
- lastSeenAt: v.number(),
328
- isActive: v.boolean(),
329
- revokedAt: v.optional(v.number()),
330
- revokedBy: v.optional(v.string()),
331
- }),
332
- ),
333
- handler: async (ctx, args) => {
334
- const agents = await ctx.db.query("registeredAgents").collect();
335
-
336
- const filtered =
337
- args.activeOnly !== false
338
- ? agents.filter((a) => a.isActive)
339
- : agents;
340
-
341
- return filtered.map((a) => ({
342
- agentId: a.agentId,
343
- employeeEmail: a.employeeEmail,
344
- department: a.department,
345
- firstRegisteredAt: a.firstRegisteredAt,
346
- lastSeenAt: a.lastSeenAt,
347
- isActive: a.isActive,
348
- revokedAt: a.revokedAt,
349
- revokedBy: a.revokedBy,
350
- }));
351
- },
352
- });
353
-
354
- /**
355
- * Configure app defaults (per-app) for provisioning.
356
- * This is a mutation called via the client class.
357
- */
358
- export const configure = mutation({
359
- args: {
360
- appName: v.string(),
361
- defaultPermissions: v.array(
362
- v.object({
363
- pattern: v.string(),
364
- permission: v.union(
365
- v.literal("allow"),
366
- v.literal("deny"),
367
- v.literal("rate_limited"),
368
- ),
369
- rateLimitConfig: v.optional(
370
- v.object({
371
- requestsPerHour: v.number(),
372
- tokenBudget: v.number(),
373
- }),
374
- ),
375
- }),
376
- ),
377
- },
378
- returns: v.null(),
379
- handler: async (ctx, args) => {
380
- // Upsert: replace existing config for this app
381
- const existing = await ctx.db
382
- .query("appConfigs")
383
- .withIndex("by_app_name", (q) => q.eq("appName", args.appName))
384
- .unique();
385
-
386
- if (existing) {
387
- await ctx.db.patch(existing._id, {
388
- appName: args.appName,
389
- defaultPermissions: args.defaultPermissions,
390
- configuredAt: Date.now(),
391
- });
392
- } else {
393
- await ctx.db.insert("appConfigs", {
394
- appName: args.appName,
395
- defaultPermissions: args.defaultPermissions,
396
- configuredAt: Date.now(),
397
- });
398
- }
399
-
400
- return null;
401
- },
402
- });
@@ -1,152 +0,0 @@
1
- import { v } from "convex/values";
2
- import { mutation, query } from "./_generated/server.js";
3
-
4
- /**
5
- * Register a function handle that agents can call.
6
- * The host app creates a function handle via createFunctionHandle()
7
- * and registers it here with a human-readable alias.
8
- *
9
- * If a function with the same appName + functionName already exists, it is updated.
10
- */
11
- export const register = mutation({
12
- args: {
13
- appName: v.string(),
14
- functionName: v.string(),
15
- functionHandle: v.string(),
16
- functionType: v.union(
17
- v.literal("query"),
18
- v.literal("mutation"),
19
- v.literal("action"),
20
- ),
21
- description: v.optional(v.string()),
22
- },
23
- returns: v.string(),
24
- handler: async (ctx, args) => {
25
- // Check if already registered (upsert)
26
- const existing = await ctx.db
27
- .query("functionRegistry")
28
- .withIndex("by_app_and_function", (q) =>
29
- q.eq("appName", args.appName).eq("functionName", args.functionName),
30
- )
31
- .unique();
32
-
33
- if (existing) {
34
- await ctx.db.patch(existing._id, {
35
- functionHandle: args.functionHandle,
36
- functionType: args.functionType,
37
- description: args.description,
38
- registeredAt: Date.now(),
39
- });
40
- return existing._id;
41
- }
42
-
43
- const id = await ctx.db.insert("functionRegistry", {
44
- appName: args.appName,
45
- functionName: args.functionName,
46
- functionHandle: args.functionHandle,
47
- functionType: args.functionType,
48
- description: args.description,
49
- registeredAt: Date.now(),
50
- });
51
- return id;
52
- },
53
- });
54
-
55
- /**
56
- * Get a specific function handle by appName + functionName.
57
- * Returns null if not found.
58
- */
59
- export const getHandle = query({
60
- args: {
61
- appName: v.string(),
62
- functionName: v.string(),
63
- },
64
- returns: v.union(
65
- v.null(),
66
- v.object({
67
- functionHandle: v.string(),
68
- functionType: v.union(
69
- v.literal("query"),
70
- v.literal("mutation"),
71
- v.literal("action"),
72
- ),
73
- functionName: v.string(),
74
- description: v.optional(v.string()),
75
- }),
76
- ),
77
- handler: async (ctx, args) => {
78
- const entry = await ctx.db
79
- .query("functionRegistry")
80
- .withIndex("by_app_and_function", (q) =>
81
- q.eq("appName", args.appName).eq("functionName", args.functionName),
82
- )
83
- .unique();
84
-
85
- if (!entry) return null;
86
-
87
- return {
88
- functionHandle: entry.functionHandle,
89
- functionType: entry.functionType,
90
- functionName: entry.functionName,
91
- description: entry.description,
92
- };
93
- },
94
- });
95
-
96
- /**
97
- * List all registered functions for a given app.
98
- */
99
- export const listFunctions = query({
100
- args: {
101
- appName: v.string(),
102
- },
103
- returns: v.array(
104
- v.object({
105
- functionName: v.string(),
106
- functionType: v.union(
107
- v.literal("query"),
108
- v.literal("mutation"),
109
- v.literal("action"),
110
- ),
111
- description: v.optional(v.string()),
112
- registeredAt: v.number(),
113
- }),
114
- ),
115
- handler: async (ctx, args) => {
116
- const entries = await ctx.db
117
- .query("functionRegistry")
118
- .withIndex("by_app_and_function", (q) => q.eq("appName", args.appName))
119
- .collect();
120
-
121
- return entries.map((entry) => ({
122
- functionName: entry.functionName,
123
- functionType: entry.functionType,
124
- description: entry.description,
125
- registeredAt: entry.registeredAt,
126
- }));
127
- },
128
- });
129
-
130
- /**
131
- * Unregister a function handle.
132
- */
133
- export const unregister = mutation({
134
- args: {
135
- appName: v.string(),
136
- functionName: v.string(),
137
- },
138
- returns: v.boolean(),
139
- handler: async (ctx, args) => {
140
- const existing = await ctx.db
141
- .query("functionRegistry")
142
- .withIndex("by_app_and_function", (q) =>
143
- q.eq("appName", args.appName).eq("functionName", args.functionName),
144
- )
145
- .unique();
146
-
147
- if (!existing) return false;
148
-
149
- await ctx.db.delete(existing._id);
150
- return true;
151
- },
152
- });