@helmiq/crew 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/defaults/personas/architect.persona.yaml +72 -0
- package/defaults/personas/engineer.persona.yaml +137 -0
- package/defaults/personas/persona-spec.schema.yaml +149 -0
- package/defaults/personas/reviewer.persona.yaml +47 -0
- package/defaults/rubrics/adr.rubric.yaml +48 -0
- package/defaults/rubrics/code-review.rubric.yaml +39 -0
- package/defaults/rubrics/pull-request.rubric.yaml +40 -0
- package/dist/actions/actions.test.d.ts +2 -0
- package/dist/actions/actions.test.d.ts.map +1 -0
- package/dist/actions/actions.test.js +158 -0
- package/dist/actions/direct-dispatcher.d.ts +10 -0
- package/dist/actions/direct-dispatcher.d.ts.map +1 -0
- package/dist/actions/direct-dispatcher.js +27 -0
- package/dist/actions/dispatcher.d.ts +11 -0
- package/dist/actions/dispatcher.d.ts.map +1 -0
- package/dist/actions/dispatcher.js +1 -0
- package/dist/actions/index.d.ts +7 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +3 -0
- package/dist/actions/registry.d.ts +13 -0
- package/dist/actions/registry.d.ts.map +1 -0
- package/dist/actions/registry.js +40 -0
- package/dist/actions/resolver.d.ts +47 -0
- package/dist/actions/resolver.d.ts.map +1 -0
- package/dist/actions/resolver.js +43 -0
- package/dist/cli/cli.test.d.ts +2 -0
- package/dist/cli/cli.test.d.ts.map +1 -0
- package/dist/cli/cli.test.js +392 -0
- package/dist/cli/run.d.ts +45 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +236 -0
- package/dist/common/errors.d.ts +76 -0
- package/dist/common/errors.d.ts.map +1 -0
- package/dist/common/errors.js +74 -0
- package/dist/config/config.test.d.ts +2 -0
- package/dist/config/config.test.d.ts.map +1 -0
- package/dist/config/config.test.js +691 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/loader.d.ts +16 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +56 -0
- package/dist/config/model-resolver.d.ts +24 -0
- package/dist/config/model-resolver.d.ts.map +1 -0
- package/dist/config/model-resolver.js +39 -0
- package/dist/config/resolver.d.ts +22 -0
- package/dist/config/resolver.d.ts.map +1 -0
- package/dist/config/resolver.js +115 -0
- package/dist/config/schemas.d.ts +266 -0
- package/dist/config/schemas.d.ts.map +1 -0
- package/dist/config/schemas.js +115 -0
- package/dist/context/artifact-reader.d.ts +12 -0
- package/dist/context/artifact-reader.d.ts.map +1 -0
- package/dist/context/artifact-reader.js +92 -0
- package/dist/context/assembler.d.ts +22 -0
- package/dist/context/assembler.d.ts.map +1 -0
- package/dist/context/assembler.js +126 -0
- package/dist/context/code-reader.d.ts +14 -0
- package/dist/context/code-reader.d.ts.map +1 -0
- package/dist/context/code-reader.js +56 -0
- package/dist/context/context.test.d.ts +2 -0
- package/dist/context/context.test.d.ts.map +1 -0
- package/dist/context/context.test.js +260 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +5 -0
- package/dist/context/section-extractor.d.ts +9 -0
- package/dist/context/section-extractor.d.ts.map +1 -0
- package/dist/context/section-extractor.js +32 -0
- package/dist/context/token-budget.d.ts +11 -0
- package/dist/context/token-budget.d.ts.map +1 -0
- package/dist/context/token-budget.js +22 -0
- package/dist/control/control.test.d.ts +2 -0
- package/dist/control/control.test.d.ts.map +1 -0
- package/dist/control/control.test.js +137 -0
- package/dist/control/id-generator.d.ts +12 -0
- package/dist/control/id-generator.d.ts.map +1 -0
- package/dist/control/id-generator.js +20 -0
- package/dist/control/index.d.ts +5 -0
- package/dist/control/index.d.ts.map +1 -0
- package/dist/control/index.js +3 -0
- package/dist/control/lock-manager.d.ts +13 -0
- package/dist/control/lock-manager.d.ts.map +1 -0
- package/dist/control/lock-manager.js +72 -0
- package/dist/control/run-state.d.ts +16 -0
- package/dist/control/run-state.d.ts.map +1 -0
- package/dist/control/run-state.js +55 -0
- package/dist/engine/composite.d.ts +34 -0
- package/dist/engine/composite.d.ts.map +1 -0
- package/dist/engine/composite.js +192 -0
- package/dist/engine/composite.test.d.ts +2 -0
- package/dist/engine/composite.test.d.ts.map +1 -0
- package/dist/engine/composite.test.js +1947 -0
- package/dist/engine/engine.test.d.ts +2 -0
- package/dist/engine/engine.test.d.ts.map +1 -0
- package/dist/engine/engine.test.js +334 -0
- package/dist/engine/index.d.ts +10 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +5 -0
- package/dist/engine/llm-client.d.ts +27 -0
- package/dist/engine/llm-client.d.ts.map +1 -0
- package/dist/engine/llm-client.js +46 -0
- package/dist/engine/simple.d.ts +21 -0
- package/dist/engine/simple.d.ts.map +1 -0
- package/dist/engine/simple.js +59 -0
- package/dist/engine/tool-dispatch.d.ts +37 -0
- package/dist/engine/tool-dispatch.d.ts.map +1 -0
- package/dist/engine/tool-dispatch.js +146 -0
- package/dist/engine/tool-dispatch.test.d.ts +2 -0
- package/dist/engine/tool-dispatch.test.d.ts.map +1 -0
- package/dist/engine/tool-dispatch.test.js +348 -0
- package/dist/engine/tool-filter.d.ts +13 -0
- package/dist/engine/tool-filter.d.ts.map +1 -0
- package/dist/engine/tool-filter.js +25 -0
- package/dist/evaluation/evaluation.test.d.ts +2 -0
- package/dist/evaluation/evaluation.test.d.ts.map +1 -0
- package/dist/evaluation/evaluation.test.js +490 -0
- package/dist/evaluation/evaluator.d.ts +19 -0
- package/dist/evaluation/evaluator.d.ts.map +1 -0
- package/dist/evaluation/evaluator.js +78 -0
- package/dist/evaluation/index.d.ts +4 -0
- package/dist/evaluation/index.d.ts.map +1 -0
- package/dist/evaluation/index.js +2 -0
- package/dist/evaluation/scorer.d.ts +38 -0
- package/dist/evaluation/scorer.d.ts.map +1 -0
- package/dist/evaluation/scorer.js +94 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/provider-factory.d.ts +11 -0
- package/dist/providers/provider-factory.d.ts.map +1 -0
- package/dist/providers/provider-factory.js +30 -0
- package/dist/publication/frontmatter.d.ts +21 -0
- package/dist/publication/frontmatter.d.ts.map +1 -0
- package/dist/publication/frontmatter.js +15 -0
- package/dist/publication/git-ops.d.ts +18 -0
- package/dist/publication/git-ops.d.ts.map +1 -0
- package/dist/publication/git-ops.js +74 -0
- package/dist/publication/index.d.ts +9 -0
- package/dist/publication/index.d.ts.map +1 -0
- package/dist/publication/index.js +5 -0
- package/dist/publication/provenance-writer.d.ts +27 -0
- package/dist/publication/provenance-writer.d.ts.map +1 -0
- package/dist/publication/provenance-writer.js +21 -0
- package/dist/publication/publication.test.d.ts +2 -0
- package/dist/publication/publication.test.d.ts.map +1 -0
- package/dist/publication/publication.test.js +235 -0
- package/dist/publication/publisher.d.ts +32 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/publisher.js +113 -0
- package/dist/publication/secret-scanner.d.ts +6 -0
- package/dist/publication/secret-scanner.d.ts.map +1 -0
- package/dist/publication/secret-scanner.js +19 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +288 -0
- package/dist/tools/registry.test.d.ts +2 -0
- package/dist/tools/registry.test.d.ts.map +1 -0
- package/dist/tools/registry.test.js +131 -0
- package/dist/tools/tool-groups.d.ts +20 -0
- package/dist/tools/tool-groups.d.ts.map +1 -0
- package/dist/tools/tool-groups.js +48 -0
- package/dist/tools/tool-groups.test.d.ts +2 -0
- package/dist/tools/tool-groups.test.d.ts.map +1 -0
- package/dist/tools/tool-groups.test.js +127 -0
- package/dist/types/artifact-store.d.ts +33 -0
- package/dist/types/artifact-store.d.ts.map +1 -0
- package/dist/types/artifact-store.js +9 -0
- package/dist/types/evaluation-rubric.d.ts +18 -0
- package/dist/types/evaluation-rubric.d.ts.map +1 -0
- package/dist/types/evaluation-rubric.js +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/llm-provider.d.ts +47 -0
- package/dist/types/llm-provider.d.ts.map +1 -0
- package/dist/types/llm-provider.js +8 -0
- package/dist/types/persona-spec.d.ts +79 -0
- package/dist/types/persona-spec.d.ts.map +1 -0
- package/dist/types/persona-spec.js +1 -0
- package/dist/types/project-config.d.ts +28 -0
- package/dist/types/project-config.d.ts.map +1 -0
- package/dist/types/project-config.js +1 -0
- package/dist/types/provenance.d.ts +67 -0
- package/dist/types/provenance.d.ts.map +1 -0
- package/dist/types/provenance.js +1 -0
- package/dist/types/run-state.d.ts +11 -0
- package/dist/types/run-state.d.ts.map +1 -0
- package/dist/types/run-state.js +1 -0
- package/dist/types/tool-runtime.d.ts +43 -0
- package/dist/types/tool-runtime.d.ts.map +1 -0
- package/dist/types/tool-runtime.js +30 -0
- package/dist/workspace/detect.d.ts +11 -0
- package/dist/workspace/detect.d.ts.map +1 -0
- package/dist/workspace/detect.js +28 -0
- package/dist/workspace/detect.test.d.ts +2 -0
- package/dist/workspace/detect.test.d.ts.map +1 -0
- package/dist/workspace/detect.test.js +53 -0
- package/dist/workspace/index.d.ts +2 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.test.d.ts","sourceRoot":"","sources":["../../src/engine/engine.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { filterTools } from './tool-filter.js';
|
|
3
|
+
const mockModel = {
|
|
4
|
+
alias: 'claude-sonnet',
|
|
5
|
+
provider: 'anthropic',
|
|
6
|
+
concreteModel: 'claude-sonnet-4-20250514',
|
|
7
|
+
apiKeyEnv: 'ANTHROPIC_API_KEY',
|
|
8
|
+
};
|
|
9
|
+
const emptyContext = {
|
|
10
|
+
blocks: [],
|
|
11
|
+
totalTokens: 0,
|
|
12
|
+
budgetLimit: 100_000,
|
|
13
|
+
gaps: [],
|
|
14
|
+
};
|
|
15
|
+
function makePersona(overrides) {
|
|
16
|
+
return {
|
|
17
|
+
persona: {
|
|
18
|
+
name: 'test-persona',
|
|
19
|
+
identity: { role: 'Test Engineer' },
|
|
20
|
+
skills: ['feature-implementation'],
|
|
21
|
+
perception: { per_task: {} },
|
|
22
|
+
tasks: {},
|
|
23
|
+
tools: overrides?.tools ?? {
|
|
24
|
+
permitted: ['read-artifact', 'write-code', 'shell'],
|
|
25
|
+
denied: ['write-strategy-artifacts'],
|
|
26
|
+
},
|
|
27
|
+
cadence: {},
|
|
28
|
+
evaluation: { rubric: 'test.rubric.yaml' },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const pathDef = {
|
|
33
|
+
description: 'Tool for testing',
|
|
34
|
+
parameters: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: { path: { type: 'string' } },
|
|
37
|
+
required: ['path'],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const sampleTools = {
|
|
41
|
+
'read-artifact': pathDef,
|
|
42
|
+
'write-code': pathDef,
|
|
43
|
+
'write-strategy-artifacts': pathDef,
|
|
44
|
+
shell: pathDef,
|
|
45
|
+
'send-notifications': pathDef,
|
|
46
|
+
};
|
|
47
|
+
function toCrewToolSet(tools) {
|
|
48
|
+
const result = {};
|
|
49
|
+
for (const [name, def] of Object.entries(tools)) {
|
|
50
|
+
result[name] = { ...def, name, execute: vi.fn().mockResolvedValue({}) };
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
const sampleCrewTools = toCrewToolSet(sampleTools);
|
|
55
|
+
function mockProvider(overrides) {
|
|
56
|
+
return {
|
|
57
|
+
generateText: vi.fn().mockResolvedValue({
|
|
58
|
+
text: 'Generated artifact content',
|
|
59
|
+
toolCalls: [],
|
|
60
|
+
tokensIn: 100,
|
|
61
|
+
tokensOut: 50,
|
|
62
|
+
...overrides,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
describe('T-01-003b: tool permission enforcement', () => {
|
|
67
|
+
it('includes only permitted tools', () => {
|
|
68
|
+
const permissions = {
|
|
69
|
+
permitted: ['read-artifact', 'write-code', 'shell'],
|
|
70
|
+
denied: ['write-strategy-artifacts'],
|
|
71
|
+
};
|
|
72
|
+
const filtered = filterTools(sampleTools, permissions);
|
|
73
|
+
expect(Object.keys(filtered).sort()).toEqual(['read-artifact', 'shell', 'write-code'].sort());
|
|
74
|
+
});
|
|
75
|
+
it('excludes denied tools even if in permitted list', () => {
|
|
76
|
+
const permissions = {
|
|
77
|
+
permitted: ['read-artifact', 'write-code', 'write-strategy-artifacts', 'shell'],
|
|
78
|
+
denied: ['write-strategy-artifacts'],
|
|
79
|
+
};
|
|
80
|
+
const filtered = filterTools(sampleTools, permissions);
|
|
81
|
+
expect(Object.keys(filtered)).not.toContain('write-strategy-artifacts');
|
|
82
|
+
});
|
|
83
|
+
it('excludes tools not in the permitted list', () => {
|
|
84
|
+
const permissions = {
|
|
85
|
+
permitted: ['read-artifact'],
|
|
86
|
+
denied: [],
|
|
87
|
+
};
|
|
88
|
+
const filtered = filterTools(sampleTools, permissions);
|
|
89
|
+
expect(Object.keys(filtered)).toEqual(['read-artifact']);
|
|
90
|
+
});
|
|
91
|
+
it('applies sub-agent tool restriction as additional subset', () => {
|
|
92
|
+
const permissions = {
|
|
93
|
+
permitted: ['read-artifact', 'write-code', 'shell'],
|
|
94
|
+
denied: ['write-strategy-artifacts'],
|
|
95
|
+
};
|
|
96
|
+
const filtered = filterTools(sampleTools, permissions, ['write-code']);
|
|
97
|
+
expect(Object.keys(filtered)).toEqual(['write-code']);
|
|
98
|
+
});
|
|
99
|
+
it('returns empty set when no tools are permitted', () => {
|
|
100
|
+
const permissions = {
|
|
101
|
+
permitted: [],
|
|
102
|
+
denied: [],
|
|
103
|
+
};
|
|
104
|
+
const filtered = filterTools(sampleTools, permissions);
|
|
105
|
+
expect(Object.keys(filtered)).toHaveLength(0);
|
|
106
|
+
});
|
|
107
|
+
it('returns empty set when sub-agent restricts to empty', () => {
|
|
108
|
+
const permissions = {
|
|
109
|
+
permitted: ['read-artifact', 'write-code'],
|
|
110
|
+
denied: [],
|
|
111
|
+
};
|
|
112
|
+
const filtered = filterTools(sampleTools, permissions, ['nonexistent-tool']);
|
|
113
|
+
expect(Object.keys(filtered)).toHaveLength(0);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('T-01-003a: simple task execution', () => {
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
vi.restoreAllMocks();
|
|
119
|
+
});
|
|
120
|
+
it('constructs prompt from persona identity and task context and calls provider', async () => {
|
|
121
|
+
const { executeSimpleTask } = await import('./simple.js');
|
|
122
|
+
const llmClient = await import('./llm-client.js');
|
|
123
|
+
const provider = mockProvider();
|
|
124
|
+
const callLlmSpy = vi.spyOn(llmClient, 'callLlm').mockResolvedValue({
|
|
125
|
+
text: 'Generated artifact content',
|
|
126
|
+
toolCalls: [],
|
|
127
|
+
tokensIn: 100,
|
|
128
|
+
tokensOut: 50,
|
|
129
|
+
durationMs: 1500,
|
|
130
|
+
model: 'claude-sonnet-4-20250514',
|
|
131
|
+
});
|
|
132
|
+
const persona = makePersona();
|
|
133
|
+
const task = {
|
|
134
|
+
mode: 'simple',
|
|
135
|
+
trigger: ['manual'],
|
|
136
|
+
skill: 'feature-implementation',
|
|
137
|
+
produces: 'review-report',
|
|
138
|
+
};
|
|
139
|
+
const skills = { 'feature-implementation': 'Write a review report' };
|
|
140
|
+
const context = {
|
|
141
|
+
blocks: [
|
|
142
|
+
{
|
|
143
|
+
source: { type: 'requirements', path: 'work/CREW-01/requirements.md' },
|
|
144
|
+
content: 'Requirement content here',
|
|
145
|
+
tokens: 10,
|
|
146
|
+
priority: 'required',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
totalTokens: 10,
|
|
150
|
+
budgetLimit: 100_000,
|
|
151
|
+
gaps: [],
|
|
152
|
+
};
|
|
153
|
+
const result = await executeSimpleTask(persona, task, context, mockModel, provider, sampleCrewTools, skills);
|
|
154
|
+
expect(result.content).toBe('Generated artifact content');
|
|
155
|
+
expect(result.tokensIn).toBe(100);
|
|
156
|
+
expect(result.tokensOut).toBe(50);
|
|
157
|
+
expect(result.model).toBe('claude-sonnet-4-20250514');
|
|
158
|
+
expect(callLlmSpy).toHaveBeenCalledOnce();
|
|
159
|
+
const callArgs = callLlmSpy.mock.calls[0][0];
|
|
160
|
+
expect(callArgs.system).toContain('Test Engineer');
|
|
161
|
+
expect(callArgs.messages[0].content).toContain('review-report');
|
|
162
|
+
expect(callArgs.messages[0].content).toContain('Requirement content here');
|
|
163
|
+
expect(callArgs.messages[0].content).toContain('Write a review report');
|
|
164
|
+
});
|
|
165
|
+
it('filters tools before passing to provider', async () => {
|
|
166
|
+
const { executeSimpleTask } = await import('./simple.js');
|
|
167
|
+
const llmClient = await import('./llm-client.js');
|
|
168
|
+
const provider = mockProvider();
|
|
169
|
+
const callLlmSpy = vi.spyOn(llmClient, 'callLlm').mockResolvedValue({
|
|
170
|
+
text: 'result',
|
|
171
|
+
toolCalls: [],
|
|
172
|
+
tokensIn: 10,
|
|
173
|
+
tokensOut: 10,
|
|
174
|
+
durationMs: 100,
|
|
175
|
+
model: 'claude-sonnet-4-20250514',
|
|
176
|
+
});
|
|
177
|
+
const persona = makePersona({
|
|
178
|
+
tools: {
|
|
179
|
+
permitted: ['read-artifact'],
|
|
180
|
+
denied: ['write-strategy-artifacts'],
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const task = {
|
|
184
|
+
mode: 'simple',
|
|
185
|
+
trigger: ['manual'],
|
|
186
|
+
skill: 'feature-implementation',
|
|
187
|
+
produces: 'output',
|
|
188
|
+
};
|
|
189
|
+
await executeSimpleTask(persona, task, emptyContext, mockModel, provider, sampleCrewTools);
|
|
190
|
+
const toolsArg = callLlmSpy.mock.calls[0][0].tools;
|
|
191
|
+
expect(Object.keys(toolsArg)).toEqual(['read-artifact']);
|
|
192
|
+
expect(toolsArg['write-strategy-artifacts']).toBeUndefined();
|
|
193
|
+
expect(toolsArg['write-code']).toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
it('includes context gaps in the user prompt', async () => {
|
|
196
|
+
const { executeSimpleTask } = await import('./simple.js');
|
|
197
|
+
const llmClient = await import('./llm-client.js');
|
|
198
|
+
const provider = mockProvider();
|
|
199
|
+
const callLlmSpy = vi.spyOn(llmClient, 'callLlm').mockResolvedValue({
|
|
200
|
+
text: 'result',
|
|
201
|
+
toolCalls: [],
|
|
202
|
+
tokensIn: 10,
|
|
203
|
+
tokensOut: 10,
|
|
204
|
+
durationMs: 100,
|
|
205
|
+
model: 'claude-sonnet-4-20250514',
|
|
206
|
+
});
|
|
207
|
+
const contextWithGaps = {
|
|
208
|
+
blocks: [],
|
|
209
|
+
totalTokens: 0,
|
|
210
|
+
budgetLimit: 100_000,
|
|
211
|
+
gaps: ['design (scope: {"epic":"CREW-01"})'],
|
|
212
|
+
};
|
|
213
|
+
const persona = makePersona();
|
|
214
|
+
const task = {
|
|
215
|
+
mode: 'simple',
|
|
216
|
+
trigger: ['manual'],
|
|
217
|
+
skill: 'feature-implementation',
|
|
218
|
+
produces: 'output',
|
|
219
|
+
};
|
|
220
|
+
await executeSimpleTask(persona, task, contextWithGaps, mockModel, provider, {});
|
|
221
|
+
const userContent = callLlmSpy.mock.calls[0][0].messages[0].content;
|
|
222
|
+
expect(userContent).toContain('not found');
|
|
223
|
+
expect(userContent).toContain('design');
|
|
224
|
+
expect(userContent).toContain('Do not invent');
|
|
225
|
+
});
|
|
226
|
+
it('produces content after tool dispatch completes', async () => {
|
|
227
|
+
const { executeSimpleTask } = await import('./simple.js');
|
|
228
|
+
const llmClient = await import('./llm-client.js');
|
|
229
|
+
let callIdx = 0;
|
|
230
|
+
vi.spyOn(llmClient, 'callLlm').mockImplementation(async () => {
|
|
231
|
+
callIdx++;
|
|
232
|
+
if (callIdx === 1) {
|
|
233
|
+
return {
|
|
234
|
+
text: '',
|
|
235
|
+
toolCalls: [
|
|
236
|
+
{ toolCallId: 'tc-1', toolName: 'read-file', input: { path: 'src/index.ts' } },
|
|
237
|
+
],
|
|
238
|
+
tokensIn: 50,
|
|
239
|
+
tokensOut: 30,
|
|
240
|
+
durationMs: 400,
|
|
241
|
+
model: 'claude-sonnet-4-20250514',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
text: 'Done after tool use',
|
|
246
|
+
toolCalls: [],
|
|
247
|
+
tokensIn: 40,
|
|
248
|
+
tokensOut: 20,
|
|
249
|
+
durationMs: 400,
|
|
250
|
+
model: 'claude-sonnet-4-20250514',
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
const persona = makePersona();
|
|
254
|
+
const task = {
|
|
255
|
+
mode: 'simple',
|
|
256
|
+
trigger: ['manual'],
|
|
257
|
+
skill: 'feature-implementation',
|
|
258
|
+
produces: 'code-changes',
|
|
259
|
+
};
|
|
260
|
+
const provider = mockProvider();
|
|
261
|
+
const result = await executeSimpleTask(persona, task, emptyContext, mockModel, provider, sampleCrewTools);
|
|
262
|
+
expect(result.content).toBe('Done after tool use');
|
|
263
|
+
expect(callIdx).toBe(2);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
describe('T-01-003c: error handling', () => {
|
|
267
|
+
beforeEach(() => {
|
|
268
|
+
vi.restoreAllMocks();
|
|
269
|
+
});
|
|
270
|
+
it('wraps provider errors as LlmCallError', async () => {
|
|
271
|
+
const { callLlm } = await import('./llm-client.js');
|
|
272
|
+
const failingProvider = {
|
|
273
|
+
generateText: vi.fn().mockRejectedValue(new Error('API rate limited')),
|
|
274
|
+
};
|
|
275
|
+
await expect(callLlm({
|
|
276
|
+
provider: failingProvider,
|
|
277
|
+
model: mockModel,
|
|
278
|
+
system: 'test',
|
|
279
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
280
|
+
})).rejects.toMatchObject({ code: 'LLM_CALL' });
|
|
281
|
+
});
|
|
282
|
+
it('LlmCallError includes model name in message', async () => {
|
|
283
|
+
const { callLlm } = await import('./llm-client.js');
|
|
284
|
+
const failingProvider = {
|
|
285
|
+
generateText: vi.fn().mockRejectedValue(new Error('timeout')),
|
|
286
|
+
};
|
|
287
|
+
try {
|
|
288
|
+
await callLlm({
|
|
289
|
+
provider: failingProvider,
|
|
290
|
+
model: mockModel,
|
|
291
|
+
system: 'test',
|
|
292
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
293
|
+
});
|
|
294
|
+
expect.fail('Expected callLlm to throw');
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const error = err;
|
|
298
|
+
expect(error.code).toBe('LLM_CALL');
|
|
299
|
+
expect(error.message).toContain('claude-sonnet-4-20250514');
|
|
300
|
+
expect(error.message).toContain('timeout');
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
it('measures duration on failure', async () => {
|
|
304
|
+
const { callLlm } = await import('./llm-client.js');
|
|
305
|
+
const failingProvider = {
|
|
306
|
+
generateText: vi.fn().mockRejectedValue(new Error('fail')),
|
|
307
|
+
};
|
|
308
|
+
try {
|
|
309
|
+
await callLlm({
|
|
310
|
+
provider: failingProvider,
|
|
311
|
+
model: mockModel,
|
|
312
|
+
system: 'test',
|
|
313
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
314
|
+
});
|
|
315
|
+
expect.fail('Expected callLlm to throw');
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
const error = err;
|
|
319
|
+
expect(error.message).toMatch(/failed after \d+ms/);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
it('passes maxRetries through to provider', async () => {
|
|
323
|
+
const { callLlm } = await import('./llm-client.js');
|
|
324
|
+
const provider = mockProvider();
|
|
325
|
+
await callLlm({
|
|
326
|
+
provider,
|
|
327
|
+
model: mockModel,
|
|
328
|
+
system: 'test',
|
|
329
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
330
|
+
maxRetries: 7,
|
|
331
|
+
});
|
|
332
|
+
expect(provider.generateText).toHaveBeenCalledWith(expect.objectContaining({ maxRetries: 7 }));
|
|
333
|
+
});
|
|
334
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { executeSimpleTask } from './simple.js';
|
|
2
|
+
export type { EngineResult } from './simple.js';
|
|
3
|
+
export { executeCompositeTask } from './composite.js';
|
|
4
|
+
export type { CompositeResult, CheckpointRecord, SubAgentRecord } from './composite.js';
|
|
5
|
+
export { callLlm } from './llm-client.js';
|
|
6
|
+
export type { LlmCallOptions, LlmCallResult } from './llm-client.js';
|
|
7
|
+
export { filterTools } from './tool-filter.js';
|
|
8
|
+
export { executeWithToolDispatch, toSchemaOnlyToolSet, filterCrewTools } from './tool-dispatch.js';
|
|
9
|
+
export type { ToolDispatchOptions, ToolDispatchResult } from './tool-dispatch.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnG,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { executeSimpleTask } from './simple.js';
|
|
2
|
+
export { executeCompositeTask } from './composite.js';
|
|
3
|
+
export { callLlm } from './llm-client.js';
|
|
4
|
+
export { filterTools } from './tool-filter.js';
|
|
5
|
+
export { executeWithToolDispatch, toSchemaOnlyToolSet, filterCrewTools } from './tool-dispatch.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LlmProvider, LlmMessage, ToolCallRecord, ToolSet } from '../types/index.js';
|
|
2
|
+
import type { ResolvedModel } from '../config/model-resolver.js';
|
|
3
|
+
export interface LlmCallOptions {
|
|
4
|
+
provider: LlmProvider;
|
|
5
|
+
model: ResolvedModel;
|
|
6
|
+
system: string;
|
|
7
|
+
messages: LlmMessage[];
|
|
8
|
+
tools?: ToolSet;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface LlmCallResult {
|
|
12
|
+
text: string;
|
|
13
|
+
toolCalls: ToolCallRecord[];
|
|
14
|
+
tokensIn: number;
|
|
15
|
+
tokensOut: number;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
model: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Execute an LLM call through a Crew-defined LlmProvider.
|
|
21
|
+
*
|
|
22
|
+
* The provider is created by a provider package (e.g., @helmiq/anthropic-sdk)
|
|
23
|
+
* and injected by the caller. The runtime never imports SDK packages
|
|
24
|
+
* directly. See ADR-0009.
|
|
25
|
+
*/
|
|
26
|
+
export declare function callLlm(options: LlmCallOptions): Promise<LlmCallResult>;
|
|
27
|
+
//# sourceMappingURL=llm-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-client.d.ts","sourceRoot":"","sources":["../../src/engine/llm-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAaD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAkC7E"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LlmCallError } from '../common/errors.js';
|
|
2
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
3
|
+
function getMaxRetries() {
|
|
4
|
+
const env = process.env['CREW_LLM_MAX_RETRIES'];
|
|
5
|
+
if (env) {
|
|
6
|
+
const parsed = parseInt(env, 10);
|
|
7
|
+
if (!Number.isNaN(parsed) && parsed >= 0)
|
|
8
|
+
return parsed;
|
|
9
|
+
}
|
|
10
|
+
return DEFAULT_MAX_RETRIES;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Execute an LLM call through a Crew-defined LlmProvider.
|
|
14
|
+
*
|
|
15
|
+
* The provider is created by a provider package (e.g., @helmiq/anthropic-sdk)
|
|
16
|
+
* and injected by the caller. The runtime never imports SDK packages
|
|
17
|
+
* directly. See ADR-0009.
|
|
18
|
+
*/
|
|
19
|
+
export async function callLlm(options) {
|
|
20
|
+
const { provider, model, system, messages, tools } = options;
|
|
21
|
+
const maxRetries = options.maxRetries ?? getMaxRetries();
|
|
22
|
+
const startMs = Date.now();
|
|
23
|
+
try {
|
|
24
|
+
const result = await provider.generateText({
|
|
25
|
+
model: model.concreteModel,
|
|
26
|
+
system,
|
|
27
|
+
messages,
|
|
28
|
+
tools,
|
|
29
|
+
maxRetries,
|
|
30
|
+
});
|
|
31
|
+
const durationMs = Date.now() - startMs;
|
|
32
|
+
return {
|
|
33
|
+
text: result.text,
|
|
34
|
+
toolCalls: result.toolCalls,
|
|
35
|
+
tokensIn: result.tokensIn,
|
|
36
|
+
tokensOut: result.tokensOut,
|
|
37
|
+
durationMs,
|
|
38
|
+
model: model.concreteModel,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const durationMs = Date.now() - startMs;
|
|
43
|
+
const message = err instanceof Error ? err.message : 'Unknown LLM call error';
|
|
44
|
+
throw new LlmCallError(`LLM call failed after ${durationMs}ms (model: ${model.concreteModel}): ${message}`, { cause: err });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PersonaSpec, SimpleTask, LlmProvider, ToolCallRecord } from '../types/index.js';
|
|
2
|
+
import type { CrewToolSet, ToolExecutionContext } from '../types/tool-runtime.js';
|
|
3
|
+
import type { ResolvedModel } from '../config/model-resolver.js';
|
|
4
|
+
import type { AssembledContext } from '../context/assembler.js';
|
|
5
|
+
import type { ResolvedSkills } from '../config/resolver.js';
|
|
6
|
+
export interface EngineResult {
|
|
7
|
+
content: string;
|
|
8
|
+
toolCalls: ToolCallRecord[];
|
|
9
|
+
tokensIn: number;
|
|
10
|
+
tokensOut: number;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
model: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Execute a simple (single LLM call with tool dispatch) persona task.
|
|
16
|
+
*
|
|
17
|
+
* Per ADR-0014, uses executeWithToolDispatch for consistency with
|
|
18
|
+
* composite tasks. Sub-agents with no tools get an empty CrewToolSet.
|
|
19
|
+
*/
|
|
20
|
+
export declare function executeSimpleTask(persona: PersonaSpec, task: SimpleTask, context: AssembledContext, model: ResolvedModel, provider: LlmProvider, availableTools: CrewToolSet, skills?: ResolvedSkills, toolContext?: ToolExecutionContext): Promise<EngineResult>;
|
|
21
|
+
//# sourceMappingURL=simple.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple.d.ts","sourceRoot":"","sources":["../../src/engine/simple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC9F,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAElF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAuCD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,WAAW,EACrB,cAAc,EAAE,WAAW,EAC3B,MAAM,GAAE,cAAmB,EAC3B,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC,YAAY,CAAC,CA4BvB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createNullToolContext } from '../types/tool-runtime.js';
|
|
2
|
+
import { filterCrewTools } from './tool-dispatch.js';
|
|
3
|
+
import { executeWithToolDispatch } from './tool-dispatch.js';
|
|
4
|
+
function buildSystemPrompt(persona) {
|
|
5
|
+
const { identity } = persona.persona;
|
|
6
|
+
return `You are a ${identity.role}.\n\nFollow your prompt instructions precisely. Produce the requested artifact.`;
|
|
7
|
+
}
|
|
8
|
+
function buildUserPrompt(task, context, skills) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
parts.push(`Task: ${task.produces}`);
|
|
11
|
+
const skillContent = skills[task.skill];
|
|
12
|
+
if (skillContent) {
|
|
13
|
+
parts.push(`\nInstructions:\n${skillContent}`);
|
|
14
|
+
}
|
|
15
|
+
if (context.blocks.length > 0) {
|
|
16
|
+
parts.push('\n--- Context ---');
|
|
17
|
+
for (const block of context.blocks) {
|
|
18
|
+
const label = block.source.type + (block.source.path ? ` (${block.source.path})` : '');
|
|
19
|
+
parts.push(`\n[${label}]\n${block.content}`);
|
|
20
|
+
}
|
|
21
|
+
parts.push('\n--- End Context ---');
|
|
22
|
+
}
|
|
23
|
+
if (context.gaps.length > 0) {
|
|
24
|
+
parts.push(`\nNote: The following context artifacts were not found: ${context.gaps.join(', ')}. Do not invent their content.`);
|
|
25
|
+
}
|
|
26
|
+
return parts.join('\n');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Execute a simple (single LLM call with tool dispatch) persona task.
|
|
30
|
+
*
|
|
31
|
+
* Per ADR-0014, uses executeWithToolDispatch for consistency with
|
|
32
|
+
* composite tasks. Sub-agents with no tools get an empty CrewToolSet.
|
|
33
|
+
*/
|
|
34
|
+
export async function executeSimpleTask(persona, task, context, model, provider, availableTools, skills = {}, toolContext) {
|
|
35
|
+
const permittedSet = new Set(persona.persona.tools.permitted);
|
|
36
|
+
const deniedSet = new Set(persona.persona.tools.denied);
|
|
37
|
+
const filteredTools = filterCrewTools(availableTools, permittedSet, deniedSet, task.tools);
|
|
38
|
+
const system = buildSystemPrompt(persona);
|
|
39
|
+
const userPrompt = buildUserPrompt(task, context, skills);
|
|
40
|
+
const dispatchResult = await executeWithToolDispatch({
|
|
41
|
+
callOptions: {
|
|
42
|
+
provider,
|
|
43
|
+
model,
|
|
44
|
+
system,
|
|
45
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
46
|
+
},
|
|
47
|
+
tools: filteredTools,
|
|
48
|
+
context: toolContext ?? createNullToolContext(persona.persona.name, task.produces),
|
|
49
|
+
maxRounds: 8,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
content: dispatchResult.text,
|
|
53
|
+
toolCalls: [],
|
|
54
|
+
tokensIn: dispatchResult.totalTokensIn,
|
|
55
|
+
tokensOut: dispatchResult.totalTokensOut,
|
|
56
|
+
durationMs: dispatchResult.totalDurationMs,
|
|
57
|
+
model: dispatchResult.model,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-managed tool-call dispatch loop per ADR-0010.
|
|
3
|
+
*
|
|
4
|
+
* Calls the provider with schema-only tools, intercepts ToolCallRecord
|
|
5
|
+
* results, dispatches each to the corresponding CrewTool execute function,
|
|
6
|
+
* appends tool-result messages, and repeats until the LLM produces a
|
|
7
|
+
* final text response or the max round limit is reached.
|
|
8
|
+
*/
|
|
9
|
+
import type { ToolSet } from '../types/index.js';
|
|
10
|
+
import type { CrewToolSet, ToolExecutionContext, ToolExecutionRecord } from '../types/tool-runtime.js';
|
|
11
|
+
import type { LlmCallOptions } from './llm-client.js';
|
|
12
|
+
export interface ToolDispatchOptions {
|
|
13
|
+
callOptions: Omit<LlmCallOptions, 'tools'>;
|
|
14
|
+
tools: CrewToolSet;
|
|
15
|
+
context: ToolExecutionContext;
|
|
16
|
+
maxRounds?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ToolDispatchResult {
|
|
19
|
+
text: string;
|
|
20
|
+
toolExecutions: ToolExecutionRecord[];
|
|
21
|
+
totalTokensIn: number;
|
|
22
|
+
totalTokensOut: number;
|
|
23
|
+
totalDurationMs: number;
|
|
24
|
+
rounds: number;
|
|
25
|
+
model: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function toSchemaOnlyToolSet(tools: CrewToolSet): ToolSet;
|
|
28
|
+
/**
|
|
29
|
+
* Filter available CrewTools by persona permissions and sub-agent restrictions.
|
|
30
|
+
*
|
|
31
|
+
* Supports tool group expansion: persona specs may use category names
|
|
32
|
+
* (e.g. "write-code", "git") which are expanded to individual tool names
|
|
33
|
+
* before matching against the registry.
|
|
34
|
+
*/
|
|
35
|
+
export declare function filterCrewTools(available: CrewToolSet, permitted: Set<string>, denied: Set<string>, subAgentTools?: string[]): CrewToolSet;
|
|
36
|
+
export declare function executeWithToolDispatch(options: ToolDispatchOptions): Promise<ToolDispatchResult>;
|
|
37
|
+
//# sourceMappingURL=tool-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-dispatch.d.ts","sourceRoot":"","sources":["../../src/engine/tool-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAA8B,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IAC3C,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAS/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,EACnB,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,WAAW,CAab;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA+D7B"}
|