@agentuity/runtime 0.0.100 → 0.0.102

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 (68) hide show
  1. package/AGENTS.md +34 -212
  2. package/dist/_metadata.d.ts +107 -0
  3. package/dist/_metadata.d.ts.map +1 -0
  4. package/dist/_metadata.js +179 -0
  5. package/dist/_metadata.js.map +1 -0
  6. package/dist/_process-protection.d.ts.map +1 -1
  7. package/dist/_process-protection.js +4 -0
  8. package/dist/_process-protection.js.map +1 -1
  9. package/dist/_services.d.ts.map +1 -1
  10. package/dist/_services.js +18 -17
  11. package/dist/_services.js.map +1 -1
  12. package/dist/_standalone.d.ts.map +1 -1
  13. package/dist/_standalone.js +17 -0
  14. package/dist/_standalone.js.map +1 -1
  15. package/dist/agent.d.ts.map +1 -1
  16. package/dist/agent.js +53 -12
  17. package/dist/agent.js.map +1 -1
  18. package/dist/app.d.ts +61 -10
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js.map +1 -1
  21. package/dist/devmode.d.ts.map +1 -1
  22. package/dist/devmode.js +13 -5
  23. package/dist/devmode.js.map +1 -1
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +5 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/middleware.d.ts +61 -5
  29. package/dist/middleware.d.ts.map +1 -1
  30. package/dist/middleware.js +192 -25
  31. package/dist/middleware.js.map +1 -1
  32. package/dist/services/evalrun/http.d.ts.map +1 -1
  33. package/dist/services/evalrun/http.js +14 -4
  34. package/dist/services/evalrun/http.js.map +1 -1
  35. package/dist/services/local/vector.d.ts +5 -1
  36. package/dist/services/local/vector.d.ts.map +1 -1
  37. package/dist/services/local/vector.js +112 -0
  38. package/dist/services/local/vector.js.map +1 -1
  39. package/dist/services/session/http.d.ts.map +1 -1
  40. package/dist/services/session/http.js +7 -0
  41. package/dist/services/session/http.js.map +1 -1
  42. package/dist/services/session/local.d.ts +2 -2
  43. package/dist/services/session/local.d.ts.map +1 -1
  44. package/dist/services/session/local.js +5 -4
  45. package/dist/services/session/local.js.map +1 -1
  46. package/dist/session.d.ts +30 -4
  47. package/dist/session.d.ts.map +1 -1
  48. package/dist/session.js +90 -13
  49. package/dist/session.js.map +1 -1
  50. package/dist/workbench.d.ts.map +1 -1
  51. package/dist/workbench.js +13 -20
  52. package/dist/workbench.js.map +1 -1
  53. package/package.json +5 -5
  54. package/src/_metadata.ts +307 -0
  55. package/src/_process-protection.ts +6 -0
  56. package/src/_services.ts +23 -21
  57. package/src/_standalone.ts +22 -0
  58. package/src/agent.ts +63 -12
  59. package/src/app.ts +65 -9
  60. package/src/devmode.ts +16 -5
  61. package/src/index.ts +12 -2
  62. package/src/middleware.ts +221 -29
  63. package/src/services/evalrun/http.ts +15 -4
  64. package/src/services/local/vector.ts +160 -0
  65. package/src/services/session/http.ts +11 -0
  66. package/src/services/session/local.ts +9 -4
  67. package/src/session.ts +142 -13
  68. package/src/workbench.ts +13 -26
@@ -8,6 +8,7 @@ import {
8
8
  type Logger,
9
9
  StructuredError,
10
10
  } from '@agentuity/core';
11
+ import { internal } from '../../logger/internal';
11
12
 
12
13
  const SessionResponseError = StructuredError('SessionResponseError');
13
14
 
@@ -29,6 +30,7 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
29
30
  * @param event SessionStartEvent
30
31
  */
31
32
  async start(event: SessionStartEvent): Promise<void> {
33
+ internal.info('[session-http] sending start event: %s', event.id);
32
34
  this.logger.debug('Sending session start event: %s', event.id);
33
35
  const resp = await this.apiClient.post(
34
36
  '/session/2025-03-17',
@@ -37,9 +39,11 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
37
39
  SessionStartEventDelayedSchema
38
40
  );
39
41
  if (resp.success) {
42
+ internal.info('[session-http] start event sent successfully: %s', event.id);
40
43
  this.logger.debug('Session start event sent successfully: %s', event.id);
41
44
  return;
42
45
  }
46
+ internal.info('[session-http] start event failed: %s - %s', event.id, resp.message);
43
47
  throw new SessionResponseError({ message: resp.message });
44
48
  }
45
49
 
@@ -49,6 +53,11 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
49
53
  * @param event SessionCompleteEvent
50
54
  */
51
55
  async complete(event: SessionCompleteEvent): Promise<void> {
56
+ internal.info(
57
+ '[session-http] sending complete event: %s, userData: %s',
58
+ event.id,
59
+ event.userData ? `${event.userData.length} bytes` : 'none'
60
+ );
52
61
  this.logger.debug('Sending session complete event: %s', event.id);
53
62
  const resp = await this.apiClient.put(
54
63
  '/session/2025-03-17',
@@ -57,9 +66,11 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
57
66
  SessionCompleteEventDelayedSchema
58
67
  );
59
68
  if (resp.success) {
69
+ internal.info('[session-http] complete event sent successfully: %s', event.id);
60
70
  this.logger.debug('Session complete event sent successfully: %s', event.id);
61
71
  return;
62
72
  }
73
+ internal.info('[session-http] complete event failed: %s - %s', event.id, resp.message);
63
74
  throw new SessionResponseError({ message: resp.message });
64
75
  }
65
76
  }
@@ -3,6 +3,7 @@ import {
3
3
  type SessionStartEvent,
4
4
  type SessionCompleteEvent,
5
5
  } from '@agentuity/core';
6
+ import { internal } from '../../logger/internal';
6
7
 
7
8
  /**
8
9
  * An implementation of the SessionEventProvider which is no-op
@@ -13,8 +14,8 @@ export class LocalSessionEventProvider implements SessionEventProvider {
13
14
  *
14
15
  * @param event SessionStartEvent
15
16
  */
16
- async start(_event: SessionStartEvent): Promise<void> {
17
- // no op
17
+ async start(event: SessionStartEvent): Promise<void> {
18
+ internal.info('[session-local] start event (no-op): %s', event.id);
18
19
  }
19
20
 
20
21
  /**
@@ -22,7 +23,11 @@ export class LocalSessionEventProvider implements SessionEventProvider {
22
23
  *
23
24
  * @param event SessionCompleteEvent
24
25
  */
25
- async complete(_event: SessionCompleteEvent): Promise<void> {
26
- // no op
26
+ async complete(event: SessionCompleteEvent): Promise<void> {
27
+ internal.info(
28
+ '[session-local] complete event (no-op): %s, userData: %s',
29
+ event.id,
30
+ event.userData ? `${event.userData.length} bytes` : 'none'
31
+ );
27
32
  }
28
33
  }
package/src/session.ts CHANGED
@@ -72,6 +72,19 @@ export interface Thread {
72
72
  */
73
73
  state: Map<string, unknown>;
74
74
 
75
+ /**
76
+ * Unencrypted metadata for filtering and querying threads.
77
+ * Unlike state, metadata is stored as-is in the database with GIN indexes
78
+ * for efficient filtering. Initialized to empty object, only persisted if non-empty.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * ctx.thread.metadata.userId = 'user123';
83
+ * ctx.thread.metadata.department = 'sales';
84
+ * ```
85
+ */
86
+ metadata: Record<string, unknown>;
87
+
75
88
  /**
76
89
  * Register an event listener for when the thread is destroyed.
77
90
  * Thread is destroyed when it expires or is manually destroyed.
@@ -182,6 +195,19 @@ export interface Session {
182
195
  */
183
196
  state: Map<string, unknown>;
184
197
 
198
+ /**
199
+ * Unencrypted metadata for filtering and querying sessions.
200
+ * Unlike state, metadata is stored as-is in the database with GIN indexes
201
+ * for efficient filtering. Initialized to empty object, only persisted if non-empty.
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * ctx.session.metadata.userId = 'user123';
206
+ * ctx.session.metadata.requestType = 'chat';
207
+ * ```
208
+ */
209
+ metadata: Record<string, unknown>;
210
+
185
211
  /**
186
212
  * Register an event listener for when the session completes.
187
213
  * Fired after the agent handler returns and response is sent.
@@ -653,13 +679,20 @@ export class DefaultThread implements Thread {
653
679
  #initialStateJson: string | undefined;
654
680
  readonly id: string;
655
681
  readonly state: Map<string, unknown>;
682
+ metadata: Record<string, unknown>;
656
683
  private provider: ThreadProvider;
657
684
 
658
- constructor(provider: ThreadProvider, id: string, initialStateJson?: string) {
685
+ constructor(
686
+ provider: ThreadProvider,
687
+ id: string,
688
+ initialStateJson?: string,
689
+ metadata?: Record<string, unknown>
690
+ ) {
659
691
  this.provider = provider;
660
692
  this.id = id;
661
693
  this.state = new Map();
662
694
  this.#initialStateJson = initialStateJson;
695
+ this.metadata = metadata || {};
663
696
  }
664
697
 
665
698
  addEventListener(eventName: ThreadEventName, callback: ThreadEventCallback<any>): void {
@@ -707,10 +740,10 @@ export class DefaultThread implements Thread {
707
740
  }
708
741
 
709
742
  /**
710
- * Check if thread has any data
743
+ * Check if thread has any data (state or metadata)
711
744
  */
712
745
  empty(): boolean {
713
- return this.state.size === 0;
746
+ return this.state.size === 0 && Object.keys(this.metadata).length === 0;
714
747
  }
715
748
 
716
749
  /**
@@ -718,7 +751,24 @@ export class DefaultThread implements Thread {
718
751
  * @internal
719
752
  */
720
753
  getSerializedState(): string {
721
- return JSON.stringify(Object.fromEntries(this.state));
754
+ const hasState = this.state.size > 0;
755
+ const hasMetadata = Object.keys(this.metadata).length > 0;
756
+
757
+ if (!hasState && !hasMetadata) {
758
+ return '';
759
+ }
760
+
761
+ const data: { state?: Record<string, unknown>; metadata?: Record<string, unknown> } = {};
762
+
763
+ if (hasState) {
764
+ data.state = Object.fromEntries(this.state);
765
+ }
766
+
767
+ if (hasMetadata) {
768
+ data.metadata = this.metadata;
769
+ }
770
+
771
+ return JSON.stringify(data);
722
772
  }
723
773
  }
724
774
 
@@ -726,11 +776,13 @@ export class DefaultSession implements Session {
726
776
  readonly id: string;
727
777
  readonly thread: Thread;
728
778
  readonly state: Map<string, unknown>;
779
+ metadata: Record<string, unknown>;
729
780
 
730
- constructor(thread: Thread, id: string) {
781
+ constructor(thread: Thread, id: string, metadata?: Record<string, unknown>) {
731
782
  this.id = id;
732
783
  this.thread = thread;
733
784
  this.state = new Map();
785
+ this.metadata = metadata || {};
734
786
  }
735
787
 
736
788
  addEventListener(eventName: SessionEventName, callback: SessionEventCallback<any>): void {
@@ -1032,7 +1084,11 @@ export class ThreadWebSocketClient {
1032
1084
  });
1033
1085
  }
1034
1086
 
1035
- async save(threadId: string, userData: string): Promise<void> {
1087
+ async save(
1088
+ threadId: string,
1089
+ userData: string,
1090
+ threadMetadata?: Record<string, unknown>
1091
+ ): Promise<void> {
1036
1092
  // Wait for connection/reconnection if in progress
1037
1093
  if (this.wsConnecting) {
1038
1094
  await this.wsConnecting;
@@ -1058,10 +1114,20 @@ export class ThreadWebSocketClient {
1058
1114
  reject,
1059
1115
  });
1060
1116
 
1117
+ const data: { thread_id: string; user_data: string; metadata?: Record<string, unknown> } =
1118
+ {
1119
+ thread_id: threadId,
1120
+ user_data: userData,
1121
+ };
1122
+
1123
+ if (threadMetadata && Object.keys(threadMetadata).length > 0) {
1124
+ data.metadata = threadMetadata;
1125
+ }
1126
+
1061
1127
  const message = {
1062
1128
  id: requestId,
1063
1129
  action: 'save',
1064
- data: { thread_id: threadId, user_data: userData },
1130
+ data,
1065
1131
  };
1066
1132
 
1067
1133
  this.ws!.send(JSON.stringify(message));
@@ -1174,26 +1240,59 @@ export class DefaultThreadProvider implements ThreadProvider {
1174
1240
  async restore(ctx: Context<Env>): Promise<Thread> {
1175
1241
  const threadId = await this.threadIDProvider!.getThreadId(this.appState!, ctx);
1176
1242
  validateThreadIdOrThrow(threadId);
1243
+ internal.info('[thread] restoring thread %s', threadId);
1177
1244
 
1178
1245
  // Wait for WebSocket connection if still connecting
1179
1246
  if (this.wsConnecting) {
1247
+ internal.info('[thread] waiting for WebSocket connection');
1180
1248
  await this.wsConnecting;
1181
1249
  }
1182
1250
 
1183
- // Restore thread state from WebSocket if available
1251
+ // Restore thread state and metadata from WebSocket if available
1184
1252
  let initialStateJson: string | undefined;
1253
+ let restoredMetadata: Record<string, unknown> | undefined;
1185
1254
  if (this.wsClient) {
1186
1255
  try {
1256
+ internal.info('[thread] restoring state from WebSocket');
1187
1257
  const restoredData = await this.wsClient.restore(threadId);
1188
1258
  if (restoredData) {
1189
1259
  initialStateJson = restoredData;
1260
+ internal.info('[thread] restored state: %d bytes', restoredData.length);
1261
+ // Parse to check if it includes metadata
1262
+ try {
1263
+ const parsed = JSON.parse(restoredData);
1264
+ // New format: { state?: {...}, metadata?: {...} }
1265
+ if (
1266
+ parsed &&
1267
+ typeof parsed === 'object' &&
1268
+ ('state' in parsed || 'metadata' in parsed)
1269
+ ) {
1270
+ if (parsed.metadata) {
1271
+ restoredMetadata = parsed.metadata;
1272
+ }
1273
+ // Update initialStateJson to be just the state part for backwards compatibility
1274
+ if (parsed.state) {
1275
+ initialStateJson = JSON.stringify(parsed.state);
1276
+ } else {
1277
+ initialStateJson = undefined;
1278
+ }
1279
+ }
1280
+ // else: Old format (just state object), keep as-is
1281
+ } catch {
1282
+ // Keep original if parse fails
1283
+ }
1284
+ } else {
1285
+ internal.info('[thread] no existing state found');
1190
1286
  }
1191
- } catch {
1287
+ } catch (err) {
1288
+ internal.info('[thread] WebSocket restore failed: %s', err);
1192
1289
  // Continue with empty state rather than failing
1193
1290
  }
1291
+ } else {
1292
+ internal.info('[thread] no WebSocket client available');
1194
1293
  }
1195
1294
 
1196
- const thread = new DefaultThread(this, threadId, initialStateJson);
1295
+ const thread = new DefaultThread(this, threadId, initialStateJson, restoredMetadata);
1197
1296
 
1198
1297
  // Populate thread state from restored data
1199
1298
  if (initialStateJson) {
@@ -1202,7 +1301,9 @@ export class DefaultThreadProvider implements ThreadProvider {
1202
1301
  for (const [key, value] of Object.entries(data)) {
1203
1302
  thread.state.set(key, value);
1204
1303
  }
1205
- } catch {
1304
+ internal.info('[thread] populated state with %d keys', thread.state.size);
1305
+ } catch (err) {
1306
+ internal.info('[thread] failed to parse state JSON: %s', err);
1206
1307
  // Continue with empty state if parsing fails
1207
1308
  }
1208
1309
  }
@@ -1213,8 +1314,16 @@ export class DefaultThreadProvider implements ThreadProvider {
1213
1314
 
1214
1315
  async save(thread: Thread): Promise<void> {
1215
1316
  if (thread instanceof DefaultThread) {
1317
+ internal.info(
1318
+ '[thread] DefaultThreadProvider.save() - thread %s, isDirty: %s, hasWsClient: %s',
1319
+ thread.id,
1320
+ thread.isDirty(),
1321
+ !!this.wsClient
1322
+ );
1323
+
1216
1324
  // Wait for WebSocket connection if still connecting
1217
1325
  if (this.wsConnecting) {
1326
+ internal.info('[thread] waiting for WebSocket connection');
1218
1327
  await this.wsConnecting;
1219
1328
  }
1220
1329
 
@@ -1222,10 +1331,20 @@ export class DefaultThreadProvider implements ThreadProvider {
1222
1331
  if (this.wsClient && thread.isDirty()) {
1223
1332
  try {
1224
1333
  const serialized = thread.getSerializedState();
1225
- await this.wsClient.save(thread.id, serialized);
1226
- } catch {
1334
+ internal.info(
1335
+ '[thread] saving to WebSocket, serialized length: %d',
1336
+ serialized.length
1337
+ );
1338
+ const metadata =
1339
+ Object.keys(thread.metadata).length > 0 ? thread.metadata : undefined;
1340
+ await this.wsClient.save(thread.id, serialized, metadata);
1341
+ internal.info('[thread] WebSocket save completed');
1342
+ } catch (err) {
1343
+ internal.info('[thread] WebSocket save failed: %s', err);
1227
1344
  // Don't throw - allow request to complete even if save fails
1228
1345
  }
1346
+ } else {
1347
+ internal.info('[thread] skipping save - no wsClient or thread not dirty');
1229
1348
  }
1230
1349
  }
1231
1350
  }
@@ -1269,20 +1388,30 @@ export class DefaultSessionProvider implements SessionProvider {
1269
1388
  }
1270
1389
 
1271
1390
  async restore(thread: Thread, sessionId: string): Promise<Session> {
1391
+ internal.info('[session] restoring session %s for thread %s', sessionId, thread.id);
1272
1392
  let session = this.sessions.get(sessionId);
1273
1393
  if (!session) {
1274
1394
  session = new DefaultSession(thread, sessionId);
1275
1395
  this.sessions.set(sessionId, session);
1396
+ internal.info('[session] created new session, firing session.started');
1276
1397
  await fireEvent('session.started', session);
1398
+ } else {
1399
+ internal.info('[session] found existing session');
1277
1400
  }
1278
1401
  return session;
1279
1402
  }
1280
1403
 
1281
1404
  async save(session: Session): Promise<void> {
1282
1405
  if (session instanceof DefaultSession) {
1406
+ internal.info(
1407
+ '[session] DefaultSessionProvider.save() - firing completed event for session %s',
1408
+ session.id
1409
+ );
1283
1410
  try {
1284
1411
  await session.fireEvent('completed');
1412
+ internal.info('[session] session.fireEvent completed, firing app event');
1285
1413
  await fireEvent('session.completed', session);
1414
+ internal.info('[session] session.completed app event fired');
1286
1415
  } finally {
1287
1416
  this.sessions.delete(session.id);
1288
1417
  sessionEventListeners.delete(session);
package/src/workbench.ts CHANGED
@@ -6,8 +6,7 @@ import { createRouter } from './router';
6
6
  import type { WebSocketConnection } from './router';
7
7
  import { privateContext } from './_server';
8
8
  import { getThreadProvider } from './_services';
9
- import { readFileSync, existsSync } from 'node:fs';
10
- import { join } from 'node:path';
9
+ import { loadBuildMetadata, getAgentMetadataByAgentId, hasMetadata } from './_metadata';
11
10
 
12
11
  export const createWorkbenchExecutionRoute = (): Handler => {
13
12
  const authHeader = process.env.AGENTUITY_WORKBENCH_APIKEY
@@ -49,16 +48,11 @@ export const createWorkbenchExecutionRoute = (): Handler => {
49
48
  }
50
49
 
51
50
  // Read metadata to find agent name by agentId
52
- const metadataPath = join(process.cwd(), '.agentuity', 'agentuity.metadata.json');
53
- if (!existsSync(metadataPath)) {
54
- return ctx.json({ error: 'Metadata file not found' }, { status: 500 });
55
- }
56
-
57
- const fileContent = readFileSync(metadataPath, 'utf-8');
58
- const metadata = JSON.parse(fileContent);
59
- const agentMeta = metadata.agents?.find((a: { agentId: string }) => a.agentId === agentId);
60
-
51
+ const agentMeta = getAgentMetadataByAgentId(agentId);
61
52
  if (!agentMeta) {
53
+ if (!hasMetadata()) {
54
+ return ctx.json({ error: 'Metadata file not found' }, { status: 500 });
55
+ }
62
56
  return ctx.text('Agent not found', { status: 404 });
63
57
  }
64
58
 
@@ -75,6 +69,8 @@ export const createWorkbenchExecutionRoute = (): Handler => {
75
69
  const _ctx = privateContext(ctx);
76
70
  if (agentObj.metadata?.id) {
77
71
  _ctx.var.agentIds.add(agentObj.metadata.id);
72
+ }
73
+ if (agentObj.metadata?.agentId) {
78
74
  _ctx.var.agentIds.add(agentObj.metadata.agentId);
79
75
  }
80
76
 
@@ -295,16 +291,11 @@ export const createWorkbenchSampleRoute = (): Handler => {
295
291
  }
296
292
 
297
293
  // Read metadata to find agent name by agentId
298
- const metadataPath = join(process.cwd(), '.agentuity', 'agentuity.metadata.json');
299
- if (!existsSync(metadataPath)) {
300
- return ctx.json({ error: 'Metadata file not found' }, { status: 500 });
301
- }
302
-
303
- const fileContent = readFileSync(metadataPath, 'utf-8');
304
- const metadata = JSON.parse(fileContent);
305
- const agentMeta = metadata.agents?.find((a: { agentId: string }) => a.agentId === agentId);
306
-
294
+ const agentMeta = getAgentMetadataByAgentId(agentId);
307
295
  if (!agentMeta) {
296
+ if (!hasMetadata()) {
297
+ return ctx.json({ error: 'Metadata file not found' }, { status: 500 });
298
+ }
308
299
  return ctx.text('Agent not found', { status: 404 });
309
300
  }
310
301
 
@@ -446,9 +437,8 @@ export const createWorkbenchMetadataRoute = (): Handler => {
446
437
  }
447
438
 
448
439
  // Read metadata from agentuity.metadata.json file
449
- const metadataPath = join(process.cwd(), '.agentuity', 'agentuity.metadata.json');
450
-
451
- if (!existsSync(metadataPath)) {
440
+ const metadata = loadBuildMetadata();
441
+ if (!metadata) {
452
442
  return ctx.json(
453
443
  { error: 'Metadata file not found. Run build to generate metadata.' },
454
444
  { status: 500 }
@@ -456,9 +446,6 @@ export const createWorkbenchMetadataRoute = (): Handler => {
456
446
  }
457
447
 
458
448
  try {
459
- const fileContent = readFileSync(metadataPath, 'utf-8');
460
- const metadata = JSON.parse(fileContent);
461
-
462
449
  // Get runtime agents for JSON schema generation
463
450
  const agents = getAgents();
464
451
  const agentsByName = new Map();