@alexnetrebskii/hive-agent 0.16.0 → 0.17.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.
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +17 -10
- package/dist/agent.js.map +1 -1
- package/dist/context-manager.d.ts +52 -0
- package/dist/context-manager.d.ts.map +1 -0
- package/dist/context-manager.js +124 -0
- package/dist/context-manager.js.map +1 -0
- package/dist/context.d.ts +221 -37
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +627 -99
- package/dist/context.js.map +1 -1
- package/dist/executor.d.ts +1 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +0 -3
- package/dist/executor.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +7 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/context.js
CHANGED
|
@@ -1,124 +1,652 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Context
|
|
2
|
+
* Context - A virtual filesystem for agent communication
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
4
|
+
* Enables tools and sub-agents to read/write shared data without passing
|
|
5
|
+
* content through return values. Similar to how Claude Code uses the
|
|
6
|
+
* actual filesystem for agent coordination.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const context = new Context({
|
|
11
|
+
* schemas: {
|
|
12
|
+
* 'plan/current': {
|
|
13
|
+
* type: 'object',
|
|
14
|
+
* properties: {
|
|
15
|
+
* title: { type: 'string' },
|
|
16
|
+
* days: { type: 'array' }
|
|
17
|
+
* },
|
|
18
|
+
* required: ['title', 'days']
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* // Pre-populate before run
|
|
24
|
+
* context.write('user/preferences', { theme: 'dark' })
|
|
25
|
+
*
|
|
26
|
+
* const result = await hive.run(message, { context })
|
|
27
|
+
*
|
|
28
|
+
* // Read results after run (validated against schema)
|
|
29
|
+
* const plan = context.read('plan/current')
|
|
30
|
+
* ```
|
|
8
31
|
*/
|
|
9
|
-
export function estimateTokens(text) {
|
|
10
|
-
return Math.ceil(text.length / 4);
|
|
11
|
-
}
|
|
12
32
|
/**
|
|
13
|
-
*
|
|
33
|
+
* Context provides a virtual filesystem for agent communication
|
|
14
34
|
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
35
|
+
export class Context {
|
|
36
|
+
data = new Map();
|
|
37
|
+
validators = new Map();
|
|
38
|
+
/** Extension to type mapping */
|
|
39
|
+
static EXT_MAP = {
|
|
40
|
+
'.json': 'json',
|
|
41
|
+
'.md': 'md',
|
|
42
|
+
'.txt': 'txt',
|
|
43
|
+
'.num': 'num',
|
|
44
|
+
'.bool': 'bool',
|
|
45
|
+
'.schema': 'schema',
|
|
46
|
+
'.zod': 'zod',
|
|
47
|
+
'.validate': 'validate'
|
|
48
|
+
};
|
|
49
|
+
constructor(config) {
|
|
50
|
+
if (config?.validators) {
|
|
51
|
+
for (const [key, value] of Object.entries(config.validators)) {
|
|
52
|
+
this.registerValidator(key, value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
18
55
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Register a validator for a path (can also be called after construction)
|
|
58
|
+
*
|
|
59
|
+
* For paths with format extensions (.json, .md, etc.), the validator adds
|
|
60
|
+
* additional validation on top of the basic format check.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Basic format validation only
|
|
64
|
+
* context.registerValidator('data/config.json', null)
|
|
65
|
+
*
|
|
66
|
+
* // Format + JSON Schema validation
|
|
67
|
+
* context.registerValidator('data/config.json', { type: 'object', properties: {...} })
|
|
68
|
+
*
|
|
69
|
+
* // Format + Zod validation
|
|
70
|
+
* context.registerValidator('data/user.json', z.object({ name: z.string() }))
|
|
71
|
+
*/
|
|
72
|
+
registerValidator(key, value) {
|
|
73
|
+
// Keep the full path (with extension) as the key
|
|
74
|
+
const path = key;
|
|
75
|
+
if (!value) {
|
|
76
|
+
// Just register the path for format validation (no additional validator)
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Determine validator type from value
|
|
80
|
+
if (typeof value === 'function') {
|
|
81
|
+
this.validators.set(path, { type: 'validate', validate: value });
|
|
82
|
+
}
|
|
83
|
+
else if (this.isZodLike(value)) {
|
|
84
|
+
this.validators.set(path, { type: 'zod', zod: value });
|
|
85
|
+
}
|
|
86
|
+
else if ('type' in value && value.type === 'object') {
|
|
87
|
+
this.validators.set(path, { type: 'schema', schema: value });
|
|
88
|
+
}
|
|
89
|
+
else if ('schema' in value || 'validate' in value || 'zod' in value) {
|
|
90
|
+
const cs = value;
|
|
91
|
+
if (cs.zod) {
|
|
92
|
+
this.validators.set(path, { type: 'zod', zod: cs.zod, description: cs.description });
|
|
93
|
+
}
|
|
94
|
+
else if (cs.validate) {
|
|
95
|
+
this.validators.set(path, { type: 'validate', validate: cs.validate, description: cs.description });
|
|
96
|
+
}
|
|
97
|
+
else if (cs.schema) {
|
|
98
|
+
this.validators.set(path, { type: 'schema', schema: cs.schema, description: cs.description });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
38
101
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
/** Parse path and extension */
|
|
103
|
+
parsePathExt(key) {
|
|
104
|
+
for (const [suffix, type] of Object.entries(Context.EXT_MAP)) {
|
|
105
|
+
if (key.endsWith(suffix)) {
|
|
106
|
+
return { path: key.slice(0, -suffix.length), ext: type };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { path: key, ext: null };
|
|
110
|
+
}
|
|
111
|
+
/** Check if extension is a format type */
|
|
112
|
+
isFormatExt(ext) {
|
|
113
|
+
return ['json', 'md', 'txt', 'num', 'bool'].includes(ext);
|
|
114
|
+
}
|
|
115
|
+
/** Check if value looks like a Zod schema */
|
|
116
|
+
isZodLike(value) {
|
|
117
|
+
return (typeof value === 'object' &&
|
|
118
|
+
value !== null &&
|
|
119
|
+
'safeParse' in value &&
|
|
120
|
+
typeof value.safeParse === 'function');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Write a value to the context at the given path
|
|
124
|
+
*
|
|
125
|
+
* @param path - Dot-separated path (e.g., 'meals.today', 'user.preferences')
|
|
126
|
+
* @param value - Any JSON-serializable value
|
|
127
|
+
* @param writtenBy - Optional identifier of who wrote this (tool name, agent name)
|
|
128
|
+
* @returns WriteResult with success status and any validation errors
|
|
129
|
+
*/
|
|
130
|
+
write(path, value, writtenBy) {
|
|
131
|
+
const { ext } = this.parsePathExt(path);
|
|
132
|
+
const validator = this.validators.get(path);
|
|
133
|
+
let errors = [];
|
|
134
|
+
// Step 1: Format validation (from extension)
|
|
135
|
+
if (ext && this.isFormatExt(ext)) {
|
|
136
|
+
errors = this.validateFormat(value, ext, path);
|
|
137
|
+
if (errors.length > 0) {
|
|
138
|
+
return { success: false, errors };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Step 2: Additional validation (from registered validator)
|
|
142
|
+
if (validator) {
|
|
143
|
+
if (validator.validate) {
|
|
144
|
+
const result = validator.validate(value);
|
|
145
|
+
if (result && result.length > 0) {
|
|
146
|
+
errors = result;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else if (validator.schema) {
|
|
150
|
+
errors = this.validateSchema(value, validator.schema, path);
|
|
151
|
+
}
|
|
152
|
+
else if (validator.zod) {
|
|
153
|
+
errors = this.validateZod(value, validator.zod, path);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (errors.length > 0) {
|
|
157
|
+
return { success: false, errors };
|
|
158
|
+
}
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
const existing = this.data.get(path);
|
|
161
|
+
this.data.set(path, {
|
|
162
|
+
value,
|
|
163
|
+
createdAt: existing?.createdAt || now,
|
|
164
|
+
updatedAt: now,
|
|
165
|
+
writtenBy
|
|
166
|
+
});
|
|
167
|
+
return { success: true };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get validator info for a path
|
|
171
|
+
*/
|
|
172
|
+
getValidator(path) {
|
|
173
|
+
const v = this.validators.get(path);
|
|
174
|
+
if (!v)
|
|
175
|
+
return undefined;
|
|
176
|
+
return { type: v.type, schema: v.schema, description: v.description };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get schema for a path (if JSON schema validator)
|
|
180
|
+
*/
|
|
181
|
+
getSchema(path) {
|
|
182
|
+
const v = this.validators.get(path);
|
|
183
|
+
return v?.schema;
|
|
84
184
|
}
|
|
85
185
|
/**
|
|
86
|
-
*
|
|
186
|
+
* Get validator description for a path
|
|
87
187
|
*/
|
|
88
|
-
|
|
89
|
-
this.
|
|
188
|
+
getValidatorDescription(path) {
|
|
189
|
+
return this.validators.get(path)?.description;
|
|
90
190
|
}
|
|
91
191
|
/**
|
|
92
|
-
* Get
|
|
192
|
+
* Get all paths with validators
|
|
93
193
|
*/
|
|
94
|
-
|
|
95
|
-
return
|
|
194
|
+
getValidatorPaths() {
|
|
195
|
+
return Array.from(this.validators.keys());
|
|
96
196
|
}
|
|
97
197
|
/**
|
|
98
|
-
*
|
|
198
|
+
* Validate a value against a JSON schema
|
|
99
199
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
200
|
+
validateSchema(value, schema, basePath) {
|
|
201
|
+
const errors = [];
|
|
202
|
+
// Type check
|
|
203
|
+
if (schema.type === 'object') {
|
|
204
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
205
|
+
errors.push({
|
|
206
|
+
path: basePath,
|
|
207
|
+
message: 'Expected object',
|
|
208
|
+
expected: 'object',
|
|
209
|
+
received: value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value
|
|
210
|
+
});
|
|
211
|
+
return errors;
|
|
212
|
+
}
|
|
213
|
+
const obj = value;
|
|
214
|
+
// Check required properties
|
|
215
|
+
if (schema.required) {
|
|
216
|
+
for (const prop of schema.required) {
|
|
217
|
+
if (!(prop in obj)) {
|
|
218
|
+
errors.push({
|
|
219
|
+
path: `${basePath}.${prop}`,
|
|
220
|
+
message: `Missing required property: ${prop}`,
|
|
221
|
+
expected: 'present'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Validate properties
|
|
227
|
+
if (schema.properties) {
|
|
228
|
+
for (const [prop, propSchema] of Object.entries(schema.properties)) {
|
|
229
|
+
if (prop in obj) {
|
|
230
|
+
const propErrors = this.validateProperty(obj[prop], propSchema, `${basePath}.${prop}`);
|
|
231
|
+
errors.push(...propErrors);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return errors;
|
|
102
237
|
}
|
|
103
238
|
/**
|
|
104
|
-
*
|
|
239
|
+
* Validate a single property
|
|
105
240
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
241
|
+
validateProperty(value, schema, path) {
|
|
242
|
+
const errors = [];
|
|
243
|
+
// Type validation
|
|
244
|
+
if (schema.type === 'string') {
|
|
245
|
+
if (typeof value !== 'string') {
|
|
246
|
+
errors.push({
|
|
247
|
+
path,
|
|
248
|
+
message: 'Expected string',
|
|
249
|
+
expected: 'string',
|
|
250
|
+
received: typeof value
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else if (schema.enum && !schema.enum.includes(value)) {
|
|
254
|
+
errors.push({
|
|
255
|
+
path,
|
|
256
|
+
message: `Value must be one of: ${schema.enum.join(', ')}`,
|
|
257
|
+
expected: schema.enum.join(' | '),
|
|
258
|
+
received: value
|
|
259
|
+
});
|
|
260
|
+
}
|
|
110
261
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
262
|
+
else if (schema.type === 'number') {
|
|
263
|
+
if (typeof value !== 'number') {
|
|
264
|
+
errors.push({
|
|
265
|
+
path,
|
|
266
|
+
message: 'Expected number',
|
|
267
|
+
expected: 'number',
|
|
268
|
+
received: typeof value
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else if (schema.type === 'boolean') {
|
|
273
|
+
if (typeof value !== 'boolean') {
|
|
274
|
+
errors.push({
|
|
275
|
+
path,
|
|
276
|
+
message: 'Expected boolean',
|
|
277
|
+
expected: 'boolean',
|
|
278
|
+
received: typeof value
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (schema.type === 'array') {
|
|
283
|
+
if (!Array.isArray(value)) {
|
|
284
|
+
errors.push({
|
|
285
|
+
path,
|
|
286
|
+
message: 'Expected array',
|
|
287
|
+
expected: 'array',
|
|
288
|
+
received: typeof value
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Could add items validation here if needed
|
|
292
|
+
}
|
|
293
|
+
else if (schema.type === 'object') {
|
|
294
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
295
|
+
errors.push({
|
|
296
|
+
path,
|
|
297
|
+
message: 'Expected object',
|
|
298
|
+
expected: 'object',
|
|
299
|
+
received: value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return errors;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Validate a value using Zod schema
|
|
307
|
+
*/
|
|
308
|
+
validateZod(value, zod, basePath) {
|
|
309
|
+
const result = zod.safeParse(value);
|
|
310
|
+
if (result.success) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
return result.error.errors.map(err => ({
|
|
314
|
+
path: err.path.length > 0 ? `${basePath}.${err.path.join('.')}` : basePath,
|
|
315
|
+
message: err.message
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Validate a value against a format extension
|
|
320
|
+
*/
|
|
321
|
+
validateFormat(value, format, path) {
|
|
322
|
+
switch (format) {
|
|
323
|
+
case 'json':
|
|
324
|
+
// Must be object or array (valid JSON structure)
|
|
325
|
+
if (typeof value !== 'object' || value === null) {
|
|
326
|
+
return [{
|
|
327
|
+
path,
|
|
328
|
+
message: 'Expected JSON object or array',
|
|
329
|
+
expected: 'object | array',
|
|
330
|
+
received: value === null ? 'null' : typeof value
|
|
331
|
+
}];
|
|
332
|
+
}
|
|
333
|
+
return [];
|
|
334
|
+
case 'md':
|
|
335
|
+
case 'txt':
|
|
336
|
+
// Must be string
|
|
337
|
+
if (typeof value !== 'string') {
|
|
338
|
+
return [{
|
|
339
|
+
path,
|
|
340
|
+
message: `Expected string for .${format}`,
|
|
341
|
+
expected: 'string',
|
|
342
|
+
received: typeof value
|
|
343
|
+
}];
|
|
344
|
+
}
|
|
345
|
+
return [];
|
|
346
|
+
case 'num':
|
|
347
|
+
// Must be number
|
|
348
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
349
|
+
return [{
|
|
350
|
+
path,
|
|
351
|
+
message: 'Expected number',
|
|
352
|
+
expected: 'number',
|
|
353
|
+
received: Number.isNaN(value) ? 'NaN' : typeof value
|
|
354
|
+
}];
|
|
355
|
+
}
|
|
356
|
+
return [];
|
|
357
|
+
case 'bool':
|
|
358
|
+
// Must be boolean
|
|
359
|
+
if (typeof value !== 'boolean') {
|
|
360
|
+
return [{
|
|
361
|
+
path,
|
|
362
|
+
message: 'Expected boolean',
|
|
363
|
+
expected: 'boolean',
|
|
364
|
+
received: typeof value
|
|
365
|
+
}];
|
|
366
|
+
}
|
|
367
|
+
return [];
|
|
119
368
|
default:
|
|
120
|
-
return
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Read a value from the context
|
|
374
|
+
*
|
|
375
|
+
* @param path - Path to read from
|
|
376
|
+
* @returns The value, or undefined if not found
|
|
377
|
+
*/
|
|
378
|
+
read(path) {
|
|
379
|
+
return this.data.get(path)?.value;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Check if a path exists in the context
|
|
383
|
+
*/
|
|
384
|
+
has(path) {
|
|
385
|
+
return this.data.has(path);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Delete a path from the context
|
|
389
|
+
*/
|
|
390
|
+
delete(path) {
|
|
391
|
+
return this.data.delete(path);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* List all paths, optionally filtered by prefix
|
|
395
|
+
*
|
|
396
|
+
* @param prefix - Optional prefix to filter paths (e.g., 'meals' lists 'meals.today', 'meals.yesterday')
|
|
397
|
+
* @returns Array of context items with metadata
|
|
398
|
+
*/
|
|
399
|
+
list(prefix) {
|
|
400
|
+
const items = [];
|
|
401
|
+
for (const [path, entry] of this.data) {
|
|
402
|
+
if (prefix && !path.startsWith(prefix)) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
items.push({
|
|
406
|
+
path,
|
|
407
|
+
updatedAt: entry.updatedAt,
|
|
408
|
+
writtenBy: entry.writtenBy,
|
|
409
|
+
preview: this.getPreview(entry.value)
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
// Sort by path for consistent ordering
|
|
413
|
+
return items.sort((a, b) => a.path.localeCompare(b.path));
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get all paths (just the keys, no metadata)
|
|
417
|
+
*/
|
|
418
|
+
keys(prefix) {
|
|
419
|
+
if (!prefix) {
|
|
420
|
+
return Array.from(this.data.keys()).sort();
|
|
421
|
+
}
|
|
422
|
+
return Array.from(this.data.keys())
|
|
423
|
+
.filter(k => k.startsWith(prefix))
|
|
424
|
+
.sort();
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Clear all data from the context
|
|
428
|
+
*/
|
|
429
|
+
clear() {
|
|
430
|
+
this.data.clear();
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Get the full entry with metadata
|
|
434
|
+
*/
|
|
435
|
+
getEntry(path) {
|
|
436
|
+
return this.data.get(path);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Export all data as a plain object
|
|
440
|
+
*/
|
|
441
|
+
toObject() {
|
|
442
|
+
const obj = {};
|
|
443
|
+
for (const [path, entry] of this.data) {
|
|
444
|
+
obj[path] = entry.value;
|
|
445
|
+
}
|
|
446
|
+
return obj;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Import data from a plain object
|
|
450
|
+
*/
|
|
451
|
+
fromObject(obj, writtenBy) {
|
|
452
|
+
for (const [path, value] of Object.entries(obj)) {
|
|
453
|
+
this.write(path, value, writtenBy);
|
|
121
454
|
}
|
|
122
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Get number of entries
|
|
458
|
+
*/
|
|
459
|
+
get size() {
|
|
460
|
+
return this.data.size;
|
|
461
|
+
}
|
|
462
|
+
getPreview(value) {
|
|
463
|
+
if (value === null)
|
|
464
|
+
return 'null';
|
|
465
|
+
if (value === undefined)
|
|
466
|
+
return 'undefined';
|
|
467
|
+
const type = typeof value;
|
|
468
|
+
if (type === 'string') {
|
|
469
|
+
const str = value;
|
|
470
|
+
return str.length > 50 ? `"${str.slice(0, 47)}..."` : `"${str}"`;
|
|
471
|
+
}
|
|
472
|
+
if (type === 'number' || type === 'boolean') {
|
|
473
|
+
return String(value);
|
|
474
|
+
}
|
|
475
|
+
if (Array.isArray(value)) {
|
|
476
|
+
return `Array[${value.length}]`;
|
|
477
|
+
}
|
|
478
|
+
if (type === 'object') {
|
|
479
|
+
const keys = Object.keys(value);
|
|
480
|
+
if (keys.length <= 3) {
|
|
481
|
+
return `{${keys.join(', ')}}`;
|
|
482
|
+
}
|
|
483
|
+
return `{${keys.slice(0, 3).join(', ')}, ...+${keys.length - 3}}`;
|
|
484
|
+
}
|
|
485
|
+
return type;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Create context tools for agents to interact with Context
|
|
490
|
+
*/
|
|
491
|
+
export function createContextTools(context, agentName) {
|
|
492
|
+
// Build validator info for tool descriptions
|
|
493
|
+
const validatorPaths = context.getValidatorPaths();
|
|
494
|
+
const validatorInfo = validatorPaths.length > 0
|
|
495
|
+
? `\n\nPaths with validation:\n${validatorPaths.map(p => {
|
|
496
|
+
const v = context.getValidator(p);
|
|
497
|
+
const desc = v?.description || (v?.type === 'schema' ? 'JSON Schema' : 'custom validator');
|
|
498
|
+
return `- ${p}: ${desc}`;
|
|
499
|
+
}).join('\n')}`
|
|
500
|
+
: '';
|
|
501
|
+
return [
|
|
502
|
+
{
|
|
503
|
+
name: 'context_ls',
|
|
504
|
+
description: `List all paths in the context, optionally filtered by prefix.
|
|
505
|
+
|
|
506
|
+
The context is a virtual filesystem where tools and agents can read/write data.
|
|
507
|
+
Use this to discover what data is available.${validatorInfo}
|
|
508
|
+
|
|
509
|
+
Examples:
|
|
510
|
+
- { } - list all paths
|
|
511
|
+
- { "prefix": "meals" } - list paths starting with "meals"`,
|
|
512
|
+
parameters: {
|
|
513
|
+
type: 'object',
|
|
514
|
+
properties: {
|
|
515
|
+
prefix: {
|
|
516
|
+
type: 'string',
|
|
517
|
+
description: 'Optional prefix to filter paths'
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
execute: async (params) => {
|
|
522
|
+
const prefix = params.prefix;
|
|
523
|
+
const items = context.list(prefix);
|
|
524
|
+
// Include validator info in response
|
|
525
|
+
const validators = context.getValidatorPaths()
|
|
526
|
+
.filter(p => !prefix || p.startsWith(prefix))
|
|
527
|
+
.map(p => {
|
|
528
|
+
const v = context.getValidator(p);
|
|
529
|
+
return {
|
|
530
|
+
path: p,
|
|
531
|
+
type: v?.type,
|
|
532
|
+
description: v?.description,
|
|
533
|
+
schema: v?.schema
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
if (items.length === 0 && validators.length === 0) {
|
|
537
|
+
return {
|
|
538
|
+
success: true,
|
|
539
|
+
data: {
|
|
540
|
+
message: prefix ? `No entries found with prefix "${prefix}"` : 'Context is empty',
|
|
541
|
+
items: [],
|
|
542
|
+
validators: []
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
success: true,
|
|
548
|
+
data: {
|
|
549
|
+
count: items.length,
|
|
550
|
+
items,
|
|
551
|
+
validators: validators.length > 0 ? validators : undefined
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: 'context_read',
|
|
558
|
+
description: `Read a value from the shared context.
|
|
559
|
+
|
|
560
|
+
Returns the stored value at the given path, or null if not found.
|
|
561
|
+
|
|
562
|
+
Examples:
|
|
563
|
+
- { "path": "meals.today" }
|
|
564
|
+
- { "path": "user.preferences" }`,
|
|
565
|
+
parameters: {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {
|
|
568
|
+
path: {
|
|
569
|
+
type: 'string',
|
|
570
|
+
description: 'The path to read from'
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
required: ['path']
|
|
574
|
+
},
|
|
575
|
+
execute: async (params) => {
|
|
576
|
+
const path = params.path;
|
|
577
|
+
const entry = context.getEntry(path);
|
|
578
|
+
if (!entry) {
|
|
579
|
+
return {
|
|
580
|
+
success: true,
|
|
581
|
+
data: {
|
|
582
|
+
found: false,
|
|
583
|
+
path,
|
|
584
|
+
value: null
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
success: true,
|
|
590
|
+
data: {
|
|
591
|
+
found: true,
|
|
592
|
+
path,
|
|
593
|
+
value: entry.value,
|
|
594
|
+
updatedAt: entry.updatedAt,
|
|
595
|
+
writtenBy: entry.writtenBy
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: 'context_write',
|
|
602
|
+
description: `Write a value to the context.
|
|
603
|
+
|
|
604
|
+
Use this to store data that should be available to other tools, agents, or after the run completes.
|
|
605
|
+
Some paths have validators - if validation fails, you'll get an error with details.${validatorInfo}
|
|
606
|
+
|
|
607
|
+
Examples:
|
|
608
|
+
- { "path": "meals.today", "value": { "breakfast": "eggs", "calories": 200 } }
|
|
609
|
+
- { "path": "analysis.result", "value": "The data shows positive trends" }`,
|
|
610
|
+
parameters: {
|
|
611
|
+
type: 'object',
|
|
612
|
+
properties: {
|
|
613
|
+
path: {
|
|
614
|
+
type: 'string',
|
|
615
|
+
description: 'The path to write to'
|
|
616
|
+
},
|
|
617
|
+
value: {
|
|
618
|
+
type: 'string',
|
|
619
|
+
description: 'The value to store (any JSON value)'
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
required: ['path', 'value']
|
|
623
|
+
},
|
|
624
|
+
execute: async (params) => {
|
|
625
|
+
const path = params.path;
|
|
626
|
+
const value = params.value;
|
|
627
|
+
const result = context.write(path, value, agentName);
|
|
628
|
+
if (!result.success) {
|
|
629
|
+
// Get schema for helpful error message
|
|
630
|
+
const schema = context.getSchema(path);
|
|
631
|
+
return {
|
|
632
|
+
success: false,
|
|
633
|
+
error: 'Validation failed',
|
|
634
|
+
data: {
|
|
635
|
+
path,
|
|
636
|
+
errors: result.errors,
|
|
637
|
+
expectedSchema: schema
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
success: true,
|
|
643
|
+
data: {
|
|
644
|
+
path,
|
|
645
|
+
written: true
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
];
|
|
123
651
|
}
|
|
124
652
|
//# sourceMappingURL=context.js.map
|