@soleri/core 2.7.0 → 2.9.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/extensions/index.d.ts +3 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/middleware.d.ts +13 -0
- package/dist/extensions/middleware.d.ts.map +1 -0
- package/dist/extensions/middleware.js +47 -0
- package/dist/extensions/middleware.js.map +1 -0
- package/dist/extensions/types.d.ts +64 -0
- package/dist/extensions/types.d.ts.map +1 -0
- package/dist/extensions/types.js +2 -0
- package/dist/extensions/types.js.map +1 -0
- package/dist/index.d.ts +8 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -16
- package/dist/index.js.map +1 -1
- package/dist/planning/gap-analysis.d.ts +2 -1
- package/dist/planning/gap-analysis.d.ts.map +1 -1
- package/dist/planning/gap-analysis.js +70 -1
- package/dist/planning/gap-analysis.js.map +1 -1
- package/dist/planning/gap-types.d.ts +8 -3
- package/dist/planning/gap-types.d.ts.map +1 -1
- package/dist/planning/gap-types.js +9 -1
- package/dist/planning/gap-types.js.map +1 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +17 -5
- package/dist/planning/planner.js.map +1 -1
- package/dist/runtime/core-ops.d.ts +1 -1
- package/dist/runtime/core-ops.js +1 -1
- package/dist/runtime/facades/admin-facade.d.ts +8 -0
- package/dist/runtime/facades/admin-facade.d.ts.map +1 -0
- package/dist/runtime/facades/admin-facade.js +90 -0
- package/dist/runtime/facades/admin-facade.js.map +1 -0
- package/dist/runtime/facades/brain-facade.d.ts +8 -0
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -0
- package/dist/runtime/facades/brain-facade.js +294 -0
- package/dist/runtime/facades/brain-facade.js.map +1 -0
- package/dist/runtime/facades/cognee-facade.d.ts +8 -0
- package/dist/runtime/facades/cognee-facade.d.ts.map +1 -0
- package/dist/runtime/facades/cognee-facade.js +154 -0
- package/dist/runtime/facades/cognee-facade.js.map +1 -0
- package/dist/runtime/facades/control-facade.d.ts +8 -0
- package/dist/runtime/facades/control-facade.d.ts.map +1 -0
- package/dist/runtime/facades/control-facade.js +244 -0
- package/dist/runtime/facades/control-facade.js.map +1 -0
- package/dist/runtime/facades/curator-facade.d.ts +8 -0
- package/dist/runtime/facades/curator-facade.d.ts.map +1 -0
- package/dist/runtime/facades/curator-facade.js +117 -0
- package/dist/runtime/facades/curator-facade.js.map +1 -0
- package/dist/runtime/facades/index.d.ts +10 -0
- package/dist/runtime/facades/index.d.ts.map +1 -0
- package/dist/runtime/facades/index.js +71 -0
- package/dist/runtime/facades/index.js.map +1 -0
- package/dist/runtime/facades/loop-facade.d.ts +8 -0
- package/dist/runtime/facades/loop-facade.d.ts.map +1 -0
- package/dist/runtime/facades/loop-facade.js +9 -0
- package/dist/runtime/facades/loop-facade.js.map +1 -0
- package/dist/runtime/facades/memory-facade.d.ts +8 -0
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -0
- package/dist/runtime/facades/memory-facade.js +108 -0
- package/dist/runtime/facades/memory-facade.js.map +1 -0
- package/dist/runtime/facades/orchestrate-facade.d.ts +8 -0
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -0
- package/dist/runtime/facades/orchestrate-facade.js +58 -0
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -0
- package/dist/runtime/facades/plan-facade.d.ts +8 -0
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -0
- package/dist/runtime/facades/plan-facade.js +110 -0
- package/dist/runtime/facades/plan-facade.js.map +1 -0
- package/dist/runtime/facades/vault-facade.d.ts +8 -0
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -0
- package/dist/runtime/facades/vault-facade.js +194 -0
- package/dist/runtime/facades/vault-facade.js.map +1 -0
- package/dist/runtime/grading-ops.d.ts +1 -1
- package/dist/runtime/grading-ops.js +2 -2
- package/dist/runtime/grading-ops.js.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +2 -2
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +37 -2
- package/dist/runtime/vault-extra-ops.js.map +1 -1
- package/dist/streams/index.d.ts +4 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/index.js +3 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/normalize.d.ts +14 -0
- package/dist/streams/normalize.d.ts.map +1 -0
- package/dist/streams/normalize.js +43 -0
- package/dist/streams/normalize.js.map +1 -0
- package/dist/streams/replayable-stream.d.ts +19 -0
- package/dist/streams/replayable-stream.d.ts.map +1 -0
- package/dist/streams/replayable-stream.js +90 -0
- package/dist/streams/replayable-stream.js.map +1 -0
- package/dist/vault/content-hash.d.ts +16 -0
- package/dist/vault/content-hash.d.ts.map +1 -0
- package/dist/vault/content-hash.js +21 -0
- package/dist/vault/content-hash.js.map +1 -0
- package/dist/vault/vault.d.ts +9 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +49 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/content-hash.test.ts +60 -0
- package/src/__tests__/core-ops.test.ts +10 -7
- package/src/__tests__/extensions.test.ts +233 -0
- package/src/__tests__/grading-ops.test.ts +2 -2
- package/src/__tests__/memory-cross-project-ops.test.ts +2 -2
- package/src/__tests__/normalize.test.ts +75 -0
- package/src/__tests__/playbook.test.ts +4 -4
- package/src/__tests__/replayable-stream.test.ts +66 -0
- package/src/__tests__/vault-extra-ops.test.ts +1 -1
- package/src/__tests__/vault.test.ts +72 -0
- package/src/extensions/index.ts +2 -0
- package/src/extensions/middleware.ts +53 -0
- package/src/extensions/types.ts +64 -0
- package/src/index.ts +14 -17
- package/src/planning/gap-analysis.ts +95 -1
- package/src/planning/gap-types.ts +12 -2
- package/src/planning/planner.ts +17 -5
- package/src/runtime/facades/admin-facade.ts +101 -0
- package/src/runtime/facades/brain-facade.ts +331 -0
- package/src/runtime/facades/cognee-facade.ts +162 -0
- package/src/runtime/facades/control-facade.ts +279 -0
- package/src/runtime/facades/curator-facade.ts +132 -0
- package/src/runtime/facades/index.ts +74 -0
- package/src/runtime/facades/loop-facade.ts +12 -0
- package/src/runtime/facades/memory-facade.ts +114 -0
- package/src/runtime/facades/orchestrate-facade.ts +68 -0
- package/src/runtime/facades/plan-facade.ts +119 -0
- package/src/runtime/facades/vault-facade.ts +223 -0
- package/src/runtime/grading-ops.ts +2 -2
- package/src/runtime/vault-extra-ops.ts +38 -2
- package/src/streams/index.ts +3 -0
- package/src/streams/normalize.ts +56 -0
- package/src/streams/replayable-stream.ts +92 -0
- package/src/vault/content-hash.ts +31 -0
- package/src/vault/vault.ts +73 -3
- package/src/runtime/core-ops.ts +0 -1443
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { computeContentHash } from '../vault/content-hash.js';
|
|
3
|
+
|
|
4
|
+
describe('computeContentHash', () => {
|
|
5
|
+
const base = {
|
|
6
|
+
type: 'pattern',
|
|
7
|
+
domain: 'testing',
|
|
8
|
+
title: 'Test Pattern',
|
|
9
|
+
description: 'A test pattern.',
|
|
10
|
+
tags: ['a', 'b'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it('returns 40-char hex string', () => {
|
|
14
|
+
const hash = computeContentHash(base);
|
|
15
|
+
expect(hash).toMatch(/^[0-9a-f]{40}$/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('is deterministic', () => {
|
|
19
|
+
expect(computeContentHash(base)).toBe(computeContentHash(base));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('normalizes tag order', () => {
|
|
23
|
+
const a = computeContentHash({ ...base, tags: ['b', 'a'] });
|
|
24
|
+
const b = computeContentHash({ ...base, tags: ['a', 'b'] });
|
|
25
|
+
expect(a).toBe(b);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('normalizes whitespace', () => {
|
|
29
|
+
const a = computeContentHash(base);
|
|
30
|
+
const b = computeContentHash({
|
|
31
|
+
...base,
|
|
32
|
+
title: ' Test Pattern ',
|
|
33
|
+
description: ' A test pattern. ',
|
|
34
|
+
});
|
|
35
|
+
expect(a).toBe(b);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('normalizes domain case', () => {
|
|
39
|
+
const a = computeContentHash(base);
|
|
40
|
+
const b = computeContentHash({ ...base, domain: 'TESTING' });
|
|
41
|
+
expect(a).toBe(b);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('different content produces different hash', () => {
|
|
45
|
+
const a = computeContentHash(base);
|
|
46
|
+
const b = computeContentHash({ ...base, title: 'Different' });
|
|
47
|
+
expect(a).not.toBe(b);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles missing optional fields', () => {
|
|
51
|
+
const hash = computeContentHash({ type: 'rule', domain: 'd', title: 't', description: 'd' });
|
|
52
|
+
expect(hash).toMatch(/^[0-9a-f]{40}$/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('example and counterExample affect hash', () => {
|
|
56
|
+
const a = computeContentHash({ ...base, example: 'do this' });
|
|
57
|
+
const b = computeContentHash({ ...base, example: 'do that' });
|
|
58
|
+
expect(a).not.toBe(b);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -3,11 +3,11 @@ import { mkdirSync, rmSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createSemanticFacades } from '../runtime/facades/index.js';
|
|
7
7
|
import type { AgentRuntime } from '../runtime/types.js';
|
|
8
8
|
import type { OpDefinition } from '../facades/types.js';
|
|
9
9
|
|
|
10
|
-
describe('
|
|
10
|
+
describe('createSemanticFacades', () => {
|
|
11
11
|
let runtime: AgentRuntime;
|
|
12
12
|
let ops: OpDefinition[];
|
|
13
13
|
let plannerDir: string;
|
|
@@ -20,7 +20,7 @@ describe('createCoreOps', () => {
|
|
|
20
20
|
vaultPath: ':memory:',
|
|
21
21
|
plansPath: join(plannerDir, 'plans.json'),
|
|
22
22
|
});
|
|
23
|
-
ops =
|
|
23
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
afterEach(() => {
|
|
@@ -34,8 +34,8 @@ describe('createCoreOps', () => {
|
|
|
34
34
|
return op;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
it('should return
|
|
38
|
-
expect(ops.length).toBe(
|
|
37
|
+
it('should return 209 ops', () => {
|
|
38
|
+
expect(ops.length).toBe(209);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it('should have all expected op names', () => {
|
|
@@ -159,6 +159,9 @@ describe('createCoreOps', () => {
|
|
|
159
159
|
expect(names).toContain('vault_archive');
|
|
160
160
|
expect(names).toContain('vault_restore');
|
|
161
161
|
expect(names).toContain('vault_optimize');
|
|
162
|
+
// Vault content hashing (#166)
|
|
163
|
+
expect(names).toContain('vault_content_hash');
|
|
164
|
+
expect(names).toContain('vault_dedup_status');
|
|
162
165
|
// Admin (8)
|
|
163
166
|
expect(names).toContain('admin_health');
|
|
164
167
|
expect(names).toContain('admin_tool_list');
|
|
@@ -264,7 +267,7 @@ describe('createCoreOps', () => {
|
|
|
264
267
|
runtime.brain.rebuildVocabulary();
|
|
265
268
|
|
|
266
269
|
// Re-create ops since brain state changed
|
|
267
|
-
ops =
|
|
270
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
268
271
|
const results = (await findOp('search').handler({ query: 'core ops test' })) as unknown[];
|
|
269
272
|
expect(results.length).toBeGreaterThan(0);
|
|
270
273
|
});
|
|
@@ -371,7 +374,7 @@ describe('createCoreOps', () => {
|
|
|
371
374
|
tags: ['test'],
|
|
372
375
|
},
|
|
373
376
|
]);
|
|
374
|
-
ops =
|
|
377
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
375
378
|
const result = (await findOp('brain_feedback').handler({
|
|
376
379
|
query: 'test',
|
|
377
380
|
entryId: 'bf-1',
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { wrapWithMiddleware } from '../extensions/middleware.js';
|
|
3
|
+
import type { FacadeConfig } from '../facades/types.js';
|
|
4
|
+
import type { OpMiddleware, AgentExtensions } from '../extensions/types.js';
|
|
5
|
+
|
|
6
|
+
describe('extensions', () => {
|
|
7
|
+
describe('AgentExtensions type', () => {
|
|
8
|
+
it('should accept empty extensions', () => {
|
|
9
|
+
const ext: AgentExtensions = {};
|
|
10
|
+
expect(ext.ops).toBeUndefined();
|
|
11
|
+
expect(ext.facades).toBeUndefined();
|
|
12
|
+
expect(ext.middleware).toBeUndefined();
|
|
13
|
+
expect(ext.hooks).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should accept extensions with ops', () => {
|
|
17
|
+
const ext: AgentExtensions = {
|
|
18
|
+
ops: [
|
|
19
|
+
{
|
|
20
|
+
name: 'custom_op',
|
|
21
|
+
description: 'A custom op',
|
|
22
|
+
auth: 'read',
|
|
23
|
+
handler: async () => ({ ok: true }),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
expect(ext.ops).toHaveLength(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should accept extensions with facades', () => {
|
|
31
|
+
const ext: AgentExtensions = {
|
|
32
|
+
facades: [
|
|
33
|
+
{
|
|
34
|
+
name: 'my_facade',
|
|
35
|
+
description: 'Custom facade',
|
|
36
|
+
ops: [
|
|
37
|
+
{
|
|
38
|
+
name: 'do_thing',
|
|
39
|
+
description: 'Does a thing',
|
|
40
|
+
auth: 'write',
|
|
41
|
+
handler: async () => ({ done: true }),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
expect(ext.facades).toHaveLength(1);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('wrapWithMiddleware', () => {
|
|
52
|
+
it('should wrap facade ops with before middleware', async () => {
|
|
53
|
+
const calls: string[] = [];
|
|
54
|
+
const facade: FacadeConfig = {
|
|
55
|
+
name: 'test',
|
|
56
|
+
description: 'Test facade',
|
|
57
|
+
ops: [
|
|
58
|
+
{
|
|
59
|
+
name: 'greet',
|
|
60
|
+
description: 'Say hello',
|
|
61
|
+
auth: 'read',
|
|
62
|
+
handler: async (params) => {
|
|
63
|
+
calls.push('handler');
|
|
64
|
+
return { message: `Hello ${params.name}` };
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const mw: OpMiddleware = {
|
|
71
|
+
name: 'logger',
|
|
72
|
+
before: async (ctx) => {
|
|
73
|
+
calls.push(`before:${ctx.op}`);
|
|
74
|
+
return ctx.params;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
wrapWithMiddleware([facade], [mw]);
|
|
79
|
+
const result = await facade.ops[0].handler({ name: 'World' });
|
|
80
|
+
|
|
81
|
+
expect(calls).toEqual(['before:greet', 'handler']);
|
|
82
|
+
expect(result).toEqual({ message: 'Hello World' });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should wrap facade ops with after middleware', async () => {
|
|
86
|
+
const facade: FacadeConfig = {
|
|
87
|
+
name: 'test',
|
|
88
|
+
description: 'Test facade',
|
|
89
|
+
ops: [
|
|
90
|
+
{
|
|
91
|
+
name: 'greet',
|
|
92
|
+
description: 'Say hello',
|
|
93
|
+
auth: 'read',
|
|
94
|
+
handler: async () => ({ message: 'Hello' }),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const mw: OpMiddleware = {
|
|
100
|
+
name: 'enricher',
|
|
101
|
+
after: async (ctx) => {
|
|
102
|
+
const data = ctx.result as Record<string, unknown>;
|
|
103
|
+
return { ...data, enriched: true };
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
wrapWithMiddleware([facade], [mw]);
|
|
108
|
+
const result = await facade.ops[0].handler({});
|
|
109
|
+
expect(result).toEqual({ message: 'Hello', enriched: true });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should chain multiple middleware in order', async () => {
|
|
113
|
+
const order: string[] = [];
|
|
114
|
+
const facade: FacadeConfig = {
|
|
115
|
+
name: 'test',
|
|
116
|
+
description: 'Test',
|
|
117
|
+
ops: [
|
|
118
|
+
{
|
|
119
|
+
name: 'op1',
|
|
120
|
+
description: 'Op',
|
|
121
|
+
auth: 'read',
|
|
122
|
+
handler: async () => {
|
|
123
|
+
order.push('handler');
|
|
124
|
+
return { v: 1 };
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const mw1: OpMiddleware = {
|
|
131
|
+
name: 'first',
|
|
132
|
+
before: async (ctx) => {
|
|
133
|
+
order.push('first:before');
|
|
134
|
+
return ctx.params;
|
|
135
|
+
},
|
|
136
|
+
after: async (ctx) => {
|
|
137
|
+
order.push('first:after');
|
|
138
|
+
return ctx.result;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const mw2: OpMiddleware = {
|
|
142
|
+
name: 'second',
|
|
143
|
+
before: async (ctx) => {
|
|
144
|
+
order.push('second:before');
|
|
145
|
+
return ctx.params;
|
|
146
|
+
},
|
|
147
|
+
after: async (ctx) => {
|
|
148
|
+
order.push('second:after');
|
|
149
|
+
return ctx.result;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
wrapWithMiddleware([facade], [mw1, mw2]);
|
|
154
|
+
await facade.ops[0].handler({});
|
|
155
|
+
|
|
156
|
+
expect(order).toEqual([
|
|
157
|
+
'first:before',
|
|
158
|
+
'second:before',
|
|
159
|
+
'handler',
|
|
160
|
+
'second:after',
|
|
161
|
+
'first:after',
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should allow before middleware to modify params', async () => {
|
|
166
|
+
const facade: FacadeConfig = {
|
|
167
|
+
name: 'test',
|
|
168
|
+
description: 'Test',
|
|
169
|
+
ops: [
|
|
170
|
+
{
|
|
171
|
+
name: 'echo',
|
|
172
|
+
description: 'Echo',
|
|
173
|
+
auth: 'read',
|
|
174
|
+
handler: async (params) => params,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const mw: OpMiddleware = {
|
|
180
|
+
name: 'injector',
|
|
181
|
+
before: async (ctx) => ({ ...ctx.params, injected: true }),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
wrapWithMiddleware([facade], [mw]);
|
|
185
|
+
const result = await facade.ops[0].handler({ original: true });
|
|
186
|
+
expect(result).toEqual({ original: true, injected: true });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should handle empty middleware array (no-op)', async () => {
|
|
190
|
+
const facade: FacadeConfig = {
|
|
191
|
+
name: 'test',
|
|
192
|
+
description: 'Test',
|
|
193
|
+
ops: [
|
|
194
|
+
{
|
|
195
|
+
name: 'op',
|
|
196
|
+
description: 'Op',
|
|
197
|
+
auth: 'read',
|
|
198
|
+
handler: async () => ({ ok: true }),
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
wrapWithMiddleware([facade], []);
|
|
204
|
+
const result = await facade.ops[0].handler({});
|
|
205
|
+
expect(result).toEqual({ ok: true });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should propagate middleware errors', async () => {
|
|
209
|
+
const facade: FacadeConfig = {
|
|
210
|
+
name: 'test',
|
|
211
|
+
description: 'Test',
|
|
212
|
+
ops: [
|
|
213
|
+
{
|
|
214
|
+
name: 'op',
|
|
215
|
+
description: 'Op',
|
|
216
|
+
auth: 'read',
|
|
217
|
+
handler: async () => ({ ok: true }),
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const mw: OpMiddleware = {
|
|
223
|
+
name: 'blocker',
|
|
224
|
+
before: async () => {
|
|
225
|
+
throw new Error('Blocked by policy');
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
wrapWithMiddleware([facade], [mw]);
|
|
230
|
+
await expect(facade.ops[0].handler({})).rejects.toThrow('Blocked by policy');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -3,7 +3,7 @@ import { mkdirSync, rmSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createSemanticFacades } from '../runtime/facades/index.js';
|
|
7
7
|
import type { AgentRuntime } from '../runtime/types.js';
|
|
8
8
|
import type { OpDefinition } from '../facades/types.js';
|
|
9
9
|
|
|
@@ -20,7 +20,7 @@ describe('Grading Ops', () => {
|
|
|
20
20
|
vaultPath: ':memory:',
|
|
21
21
|
plansPath: join(plannerDir, 'plans.json'),
|
|
22
22
|
});
|
|
23
|
-
ops =
|
|
23
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
afterEach(() => {
|
|
@@ -3,7 +3,7 @@ import { mkdirSync, rmSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createSemanticFacades } from '../runtime/facades/index.js';
|
|
7
7
|
import type { AgentRuntime } from '../runtime/types.js';
|
|
8
8
|
import type { OpDefinition } from '../facades/types.js';
|
|
9
9
|
|
|
@@ -20,7 +20,7 @@ describe('Memory Cross-Project Ops', () => {
|
|
|
20
20
|
vaultPath: ':memory:',
|
|
21
21
|
plansPath: join(tempDir, 'plans.json'),
|
|
22
22
|
});
|
|
23
|
-
ops =
|
|
23
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
afterEach(() => {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { normalize, collect } from '../streams/normalize.js';
|
|
3
|
+
|
|
4
|
+
describe('normalize', () => {
|
|
5
|
+
it('passes through a single value', async () => {
|
|
6
|
+
expect(await collect(normalize(42))).toEqual([42]);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('flattens an array', async () => {
|
|
10
|
+
expect(await collect(normalize([1, 2, 3]))).toEqual([1, 2, 3]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('resolves a promise', async () => {
|
|
14
|
+
expect(await collect(normalize(Promise.resolve(99)))).toEqual([99]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('resolves a promise of array', async () => {
|
|
18
|
+
expect(await collect(normalize(Promise.resolve([4, 5])))).toEqual([4, 5]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('consumes an async iterable', async () => {
|
|
22
|
+
async function* gen() {
|
|
23
|
+
yield 'a';
|
|
24
|
+
yield 'b';
|
|
25
|
+
}
|
|
26
|
+
expect(await collect(normalize(gen()))).toEqual(['a', 'b']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('flattens nested arrays', async () => {
|
|
30
|
+
const input = [
|
|
31
|
+
[1, 2],
|
|
32
|
+
[3, [4, 5]],
|
|
33
|
+
];
|
|
34
|
+
expect(await collect(normalize(input))).toEqual([1, 2, 3, 4, 5]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('handles mixed nesting', async () => {
|
|
38
|
+
async function* gen() {
|
|
39
|
+
yield 30;
|
|
40
|
+
}
|
|
41
|
+
const input = [10, Promise.resolve(20), gen()];
|
|
42
|
+
expect(await collect(normalize(input))).toEqual([10, 20, 30]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('handles empty array', async () => {
|
|
46
|
+
expect(await collect(normalize([]))).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('handles string as leaf (not iterable)', async () => {
|
|
50
|
+
expect(await collect(normalize('hello'))).toEqual(['hello']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('handles deeply nested structure', async () => {
|
|
54
|
+
const input = [[[1]], [[2, [3]]]];
|
|
55
|
+
expect(await collect(normalize(input))).toEqual([1, 2, 3]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('collect', () => {
|
|
60
|
+
it('collects async iterable to array', async () => {
|
|
61
|
+
async function* gen() {
|
|
62
|
+
yield 1;
|
|
63
|
+
yield 2;
|
|
64
|
+
yield 3;
|
|
65
|
+
}
|
|
66
|
+
expect(await collect(gen())).toEqual([1, 2, 3]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns empty for empty iterable', async () => {
|
|
70
|
+
async function* gen() {
|
|
71
|
+
/* empty */
|
|
72
|
+
}
|
|
73
|
+
expect(await collect(gen())).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -7,7 +7,7 @@ import { validatePlaybook, parsePlaybookFromEntry } from '../vault/playbook.js';
|
|
|
7
7
|
import type { Playbook } from '../vault/playbook.js';
|
|
8
8
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
9
9
|
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
10
|
-
import {
|
|
10
|
+
import { createSemanticFacades } from '../runtime/facades/index.js';
|
|
11
11
|
import type { AgentRuntime, OpDefinition } from '../runtime/types.js';
|
|
12
12
|
|
|
13
13
|
function makePlaybook(overrides: Partial<Playbook> = {}): Playbook {
|
|
@@ -189,7 +189,7 @@ describe('playbook_create op', () => {
|
|
|
189
189
|
vaultPath: ':memory:',
|
|
190
190
|
plansPath: join(plannerDir, 'plans.json'),
|
|
191
191
|
});
|
|
192
|
-
ops =
|
|
192
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
afterEach(() => {
|
|
@@ -300,7 +300,7 @@ describe('playbook_match op', () => {
|
|
|
300
300
|
vaultPath: ':memory:',
|
|
301
301
|
plansPath: join(plannerDir, 'plans.json'),
|
|
302
302
|
});
|
|
303
|
-
ops =
|
|
303
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
304
304
|
});
|
|
305
305
|
|
|
306
306
|
afterEach(() => {
|
|
@@ -356,7 +356,7 @@ describe('playbook_seed op', () => {
|
|
|
356
356
|
vaultPath: ':memory:',
|
|
357
357
|
plansPath: join(plannerDir, 'plans.json'),
|
|
358
358
|
});
|
|
359
|
-
ops =
|
|
359
|
+
ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
|
|
360
360
|
});
|
|
361
361
|
|
|
362
362
|
afterEach(() => {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ReplayableStream } from '../streams/replayable-stream.js';
|
|
3
|
+
|
|
4
|
+
async function* generate(items: number[]): AsyncIterable<number> {
|
|
5
|
+
for (const item of items) yield item;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let sourceCallCount = 0;
|
|
9
|
+
async function* trackedGenerate(items: number[]): AsyncIterable<number> {
|
|
10
|
+
sourceCallCount++;
|
|
11
|
+
for (const item of items) yield item;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('ReplayableStream', () => {
|
|
15
|
+
it('single consumer iterates all items', async () => {
|
|
16
|
+
const stream = new ReplayableStream(generate([1, 2, 3]));
|
|
17
|
+
const result = await stream.collect();
|
|
18
|
+
expect(result).toEqual([1, 2, 3]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('multiple consumers each see full stream', async () => {
|
|
22
|
+
const stream = new ReplayableStream(generate([10, 20, 30]));
|
|
23
|
+
const a = stream.collect();
|
|
24
|
+
const b = stream.collect();
|
|
25
|
+
expect(await a).toEqual([10, 20, 30]);
|
|
26
|
+
expect(await b).toEqual([10, 20, 30]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('source executes exactly once', async () => {
|
|
30
|
+
sourceCallCount = 0;
|
|
31
|
+
const stream = new ReplayableStream(trackedGenerate([1, 2]));
|
|
32
|
+
await stream.collect();
|
|
33
|
+
await stream.collect();
|
|
34
|
+
await stream.collect();
|
|
35
|
+
expect(sourceCallCount).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('bufferedCount tracks buffer size', async () => {
|
|
39
|
+
const stream = new ReplayableStream(generate([1, 2, 3]));
|
|
40
|
+
expect(stream.bufferedCount).toBe(0);
|
|
41
|
+
await stream.collect();
|
|
42
|
+
expect(stream.bufferedCount).toBe(3);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('empty stream returns empty array', async () => {
|
|
46
|
+
const stream = new ReplayableStream(generate([]));
|
|
47
|
+
expect(await stream.collect()).toEqual([]);
|
|
48
|
+
expect(stream.isDone).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('error propagates to all consumers', async () => {
|
|
52
|
+
async function* failing(): AsyncIterable<number> {
|
|
53
|
+
yield 1;
|
|
54
|
+
throw new Error('source failed');
|
|
55
|
+
}
|
|
56
|
+
const stream = new ReplayableStream(failing());
|
|
57
|
+
await expect(stream.collect()).rejects.toThrow('source failed');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('for-await-of works', async () => {
|
|
61
|
+
const stream = new ReplayableStream(generate([5, 6]));
|
|
62
|
+
const items: number[] = [];
|
|
63
|
+
for await (const item of stream) items.push(item);
|
|
64
|
+
expect(items).toEqual([5, 6]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { Vault } from '../vault/vault.js';
|
|
3
|
+
import { computeContentHash } from '../vault/content-hash.js';
|
|
3
4
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
4
5
|
|
|
5
6
|
function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
|
|
@@ -675,4 +676,75 @@ describe('Vault', () => {
|
|
|
675
676
|
v.close();
|
|
676
677
|
});
|
|
677
678
|
});
|
|
679
|
+
|
|
680
|
+
describe('Content-addressable hashing', () => {
|
|
681
|
+
it('seed populates content_hash', () => {
|
|
682
|
+
vault.seed([
|
|
683
|
+
{
|
|
684
|
+
id: 'ch-1',
|
|
685
|
+
type: 'pattern',
|
|
686
|
+
domain: 'test',
|
|
687
|
+
title: 'Hash test',
|
|
688
|
+
severity: 'warning',
|
|
689
|
+
description: 'Desc',
|
|
690
|
+
tags: ['a'],
|
|
691
|
+
},
|
|
692
|
+
]);
|
|
693
|
+
const hash = computeContentHash({
|
|
694
|
+
type: 'pattern',
|
|
695
|
+
domain: 'test',
|
|
696
|
+
title: 'Hash test',
|
|
697
|
+
description: 'Desc',
|
|
698
|
+
tags: ['a'],
|
|
699
|
+
});
|
|
700
|
+
expect(vault.findByContentHash(hash)).toBe('ch-1');
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('findByContentHash returns null for unknown hash', () => {
|
|
704
|
+
expect(vault.findByContentHash('0000000000000000000000000000000000000000')).toBeNull();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('contentHashStats returns correct counts', () => {
|
|
708
|
+
vault.seed([
|
|
709
|
+
{
|
|
710
|
+
id: 'hs-1',
|
|
711
|
+
type: 'pattern',
|
|
712
|
+
domain: 'd',
|
|
713
|
+
title: 'T1',
|
|
714
|
+
severity: 'warning',
|
|
715
|
+
description: 'D1',
|
|
716
|
+
tags: ['a'],
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
id: 'hs-2',
|
|
720
|
+
type: 'rule',
|
|
721
|
+
domain: 'd',
|
|
722
|
+
title: 'T2',
|
|
723
|
+
severity: 'warning',
|
|
724
|
+
description: 'D2',
|
|
725
|
+
tags: ['b'],
|
|
726
|
+
},
|
|
727
|
+
]);
|
|
728
|
+
const stats = vault.contentHashStats();
|
|
729
|
+
expect(stats.total).toBeGreaterThanOrEqual(2);
|
|
730
|
+
expect(stats.hashed).toBe(stats.total);
|
|
731
|
+
expect(stats.uniqueHashes).toBe(stats.total);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('backfill hashes existing entries on re-initialize', () => {
|
|
735
|
+
vault.seed([
|
|
736
|
+
{
|
|
737
|
+
id: 'bf-1',
|
|
738
|
+
type: 'pattern',
|
|
739
|
+
domain: 'd',
|
|
740
|
+
title: 'Backfill',
|
|
741
|
+
severity: 'warning',
|
|
742
|
+
description: 'D',
|
|
743
|
+
tags: [],
|
|
744
|
+
},
|
|
745
|
+
]);
|
|
746
|
+
const stats = vault.contentHashStats();
|
|
747
|
+
expect(stats.hashed).toBe(stats.total);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
678
750
|
});
|