wowok_agent 2.1.39 → 2.2.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/index.js +1237 -1
- package/dist/schema/call/allocation.js +24 -1
- package/dist/schema/call/arbitration.js +92 -1
- package/dist/schema/call/base.js +134 -1
- package/dist/schema/call/contact.js +37 -1
- package/dist/schema/call/demand.js +47 -1
- package/dist/schema/call/guard.js +58 -1
- package/dist/schema/call/handler.js +171 -1
- package/dist/schema/call/index.js +18 -1
- package/dist/schema/call/machine.js +152 -1
- package/dist/schema/call/order.js +34 -1
- package/dist/schema/call/payment.js +17 -1
- package/dist/schema/call/permission.js +105 -1
- package/dist/schema/call/personal.js +68 -1
- package/dist/schema/call/progress.js +26 -1
- package/dist/schema/call/proof.js +27 -1
- package/dist/schema/call/repository.js +76 -1
- package/dist/schema/call/reward.js +42 -1
- package/dist/schema/call/service.js +82 -1
- package/dist/schema/call/treasury.js +71 -1
- package/dist/schema/common/index.js +345 -1
- package/dist/schema/index.js +7 -1
- package/dist/schema/local/index.js +855 -1
- package/dist/schema/local/wip.js +187 -1
- package/dist/schema/messenger/index.d.ts +4 -4
- package/dist/schema/messenger/index.js +446 -1
- package/dist/schema/query/index.js +1265 -1
- package/dist/schema/utils/guard-parser.js +401 -1
- package/dist/schema/utils/guard-query-utils.js +22 -1
- package/dist/schema/utils/node-parser.js +353 -1
- package/dist/schema/utils/permission-index-utils.js +7 -1
- package/package.json +3 -5
|
@@ -1 +1,353 @@
|
|
|
1
|
-
import{NodeSchema}from'../call/machine.js';
|
|
1
|
+
import { NodeSchema } from '../call/machine.js';
|
|
2
|
+
import { MachineNodeSchema } from '../query/index.js';
|
|
3
|
+
import { writeFileSync } from 'fs';
|
|
4
|
+
function detectFormat(text) {
|
|
5
|
+
const trimmed = text.trim();
|
|
6
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
7
|
+
return 'json';
|
|
8
|
+
}
|
|
9
|
+
return 'markdown';
|
|
10
|
+
}
|
|
11
|
+
function extractJsonFromMarkdown(markdown) {
|
|
12
|
+
const codeBlockRegex = /```(?:json)?\s*\n([\s\S]*?)```/g;
|
|
13
|
+
let match;
|
|
14
|
+
let lastMatch = null;
|
|
15
|
+
while ((match = codeBlockRegex.exec(markdown)) !== null) {
|
|
16
|
+
const jsonContent = match[1].trim();
|
|
17
|
+
if (jsonContent.startsWith('[') || jsonContent.startsWith('{')) {
|
|
18
|
+
const beforeMatch = markdown.substring(0, match.index);
|
|
19
|
+
const lineOffset = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
20
|
+
lastMatch = { json: jsonContent, lineOffset };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lastMatch;
|
|
24
|
+
}
|
|
25
|
+
function parseMarkdownToNodeData(markdown) {
|
|
26
|
+
const errors = [];
|
|
27
|
+
const jsonMatch = extractJsonFromMarkdown(markdown);
|
|
28
|
+
if (jsonMatch) {
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(jsonMatch.json);
|
|
31
|
+
return { data, errors: [] };
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
errors.push({
|
|
35
|
+
message: `Failed to parse JSON from markdown: ${e.message}`,
|
|
36
|
+
path: '/',
|
|
37
|
+
});
|
|
38
|
+
return { data: null, errors };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const result = { op: 'add' };
|
|
42
|
+
const nodes = [];
|
|
43
|
+
const opMatch = markdown.match(/(?:^|\n)#+\s*(?:Operation|操作类型)[^\n]*\n+[-*]\s*op\s*:\s*(\w+)/i);
|
|
44
|
+
if (opMatch) {
|
|
45
|
+
result.op = opMatch[1].toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
const replaceMatch = markdown.match(/(?:^|\n)#+\s*(?:Options|选项)[^\n]*\n+[-*]\s*bReplace\s*:\s*(true|false)/i);
|
|
48
|
+
if (replaceMatch) {
|
|
49
|
+
result.bReplace = replaceMatch[1].toLowerCase() === 'true';
|
|
50
|
+
}
|
|
51
|
+
const nodeSections = markdown.split(/(?=^##\s*(?:Node|节点)[:\s])/m);
|
|
52
|
+
for (const section of nodeSections.slice(1)) {
|
|
53
|
+
const nodeNameMatch = section.match(/^##\s*(?:Node|节点)[:\s]+(.+?)(?:\n|$)/im);
|
|
54
|
+
if (!nodeNameMatch)
|
|
55
|
+
continue;
|
|
56
|
+
const nodeName = nodeNameMatch[1].trim();
|
|
57
|
+
const node = { name: nodeName, pairs: [] };
|
|
58
|
+
const pairSections = section.split(/(?=^###\s*(?:Pair|节点对|Previous Node)[:\s])/m);
|
|
59
|
+
for (const pairSection of pairSections.slice(1)) {
|
|
60
|
+
const prevNodeMatch = pairSection.match(/(?:^|\n)[-*]\s*prev_node\s*[::]\s*(.+?)(?:\n|$)/im) ||
|
|
61
|
+
pairSection.match(/^###\s*(?:Pair|节点对|Previous Node)[:\s]+(.+?)(?:\n|$)/im);
|
|
62
|
+
const thresholdMatch = pairSection.match(/(?:^|\n)[-*]\s*threshold\s*[::]\s*(\d+)/i);
|
|
63
|
+
if (!prevNodeMatch)
|
|
64
|
+
continue;
|
|
65
|
+
const pair = {
|
|
66
|
+
prev_node: prevNodeMatch[1].trim(),
|
|
67
|
+
threshold: thresholdMatch ? parseInt(thresholdMatch[1], 10) : 0,
|
|
68
|
+
forwards: []
|
|
69
|
+
};
|
|
70
|
+
const tableRows = pairSection.match(/\|[^\n]+\|/g);
|
|
71
|
+
if (tableRows && tableRows.length > 2) {
|
|
72
|
+
for (const row of tableRows.slice(2)) {
|
|
73
|
+
const cells = row.split('|').filter(c => c.trim()).map(c => c.trim());
|
|
74
|
+
if (cells.length >= 2) {
|
|
75
|
+
const forward = {
|
|
76
|
+
name: cells[0],
|
|
77
|
+
weight: parseInt(cells[1], 10) || 0
|
|
78
|
+
};
|
|
79
|
+
if (cells.length >= 3 && cells[2] && cells[2] !== '-') {
|
|
80
|
+
forward.namedOperator = cells[2];
|
|
81
|
+
}
|
|
82
|
+
if (cells.length >= 4 && cells[3] && cells[3] !== '-') {
|
|
83
|
+
forward.permissionIndex = parseInt(cells[3], 10);
|
|
84
|
+
}
|
|
85
|
+
if (cells.length >= 5 && cells[4] && cells[4] !== '-') {
|
|
86
|
+
forward.guard = cells[4];
|
|
87
|
+
}
|
|
88
|
+
pair.forwards.push(forward);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const forwardMatches = pairSection.matchAll(/(?:^|\n)[-*]\s*(?:forward)?\s*[::]?\s*\n?([\s\S]*?)(?=(?:\n[-*]\s*(?:forward|prev_node|threshold))|\n###|\n##|$)/gi);
|
|
93
|
+
for (const forwardMatch of forwardMatches) {
|
|
94
|
+
const forwardSection = forwardMatch[1];
|
|
95
|
+
const nameMatch = forwardSection.match(/\s*name\s*[::]\s*(.+?)(?:\n|$)/im);
|
|
96
|
+
const weightMatch = forwardSection.match(/\s*weight\s*[::]\s*(\d+)/i);
|
|
97
|
+
if (nameMatch && weightMatch) {
|
|
98
|
+
const forward = {
|
|
99
|
+
name: nameMatch[1].trim(),
|
|
100
|
+
weight: parseInt(weightMatch[1], 10)
|
|
101
|
+
};
|
|
102
|
+
const namedOperatorMatch = forwardSection.match(/\s*namedOperator\s*[::]\s*(.+?)(?:\n|$)/im);
|
|
103
|
+
const permissionIndexMatch = forwardSection.match(/\s*permissionIndex\s*[::]\s*(\d+)/i);
|
|
104
|
+
const guardMatch = forwardSection.match(/\s*guard\s*[::]\s*(.+?)(?:\n|$)/im);
|
|
105
|
+
if (namedOperatorMatch) {
|
|
106
|
+
forward.namedOperator = namedOperatorMatch[1].trim();
|
|
107
|
+
}
|
|
108
|
+
if (permissionIndexMatch) {
|
|
109
|
+
forward.permissionIndex = parseInt(permissionIndexMatch[1], 10);
|
|
110
|
+
}
|
|
111
|
+
if (guardMatch) {
|
|
112
|
+
forward.guard = guardMatch[1].trim();
|
|
113
|
+
}
|
|
114
|
+
pair.forwards.push(forward);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
node.pairs.push(pair);
|
|
118
|
+
}
|
|
119
|
+
nodes.push(node);
|
|
120
|
+
}
|
|
121
|
+
if (nodes.length > 0) {
|
|
122
|
+
result.nodes = nodes;
|
|
123
|
+
}
|
|
124
|
+
return { data: result, errors };
|
|
125
|
+
}
|
|
126
|
+
function formatZodPath(path) {
|
|
127
|
+
return '/' + path.map(p => String(p).replace(/~/g, '~0').replace(/\//g, '~1')).join('/');
|
|
128
|
+
}
|
|
129
|
+
function parseJsonWithErrors(text) {
|
|
130
|
+
const errors = [];
|
|
131
|
+
try {
|
|
132
|
+
const data = JSON.parse(text);
|
|
133
|
+
return { data, errors: [] };
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
const jsonError = e;
|
|
137
|
+
const lineMatch = jsonError.message.match(/position\s+(\d+)/);
|
|
138
|
+
let line = 1;
|
|
139
|
+
let column = 1;
|
|
140
|
+
if (lineMatch) {
|
|
141
|
+
const pos = parseInt(lineMatch[1], 10);
|
|
142
|
+
const lines = text.substring(0, pos).split('\n');
|
|
143
|
+
line = lines.length;
|
|
144
|
+
column = lines[lines.length - 1].length + 1;
|
|
145
|
+
}
|
|
146
|
+
errors.push({
|
|
147
|
+
message: `JSON parse error: ${jsonError.message}`,
|
|
148
|
+
path: '/',
|
|
149
|
+
line,
|
|
150
|
+
column,
|
|
151
|
+
});
|
|
152
|
+
return { data: null, errors };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function validateWithZod(schema, data, basePath = '') {
|
|
156
|
+
const errors = [];
|
|
157
|
+
const result = schema.safeParse(data);
|
|
158
|
+
if (result.success) {
|
|
159
|
+
return { success: true, data: result.data, errors: [] };
|
|
160
|
+
}
|
|
161
|
+
for (const issue of result.error.issues) {
|
|
162
|
+
const path = basePath + formatZodPath(issue.path);
|
|
163
|
+
errors.push({
|
|
164
|
+
message: issue.message,
|
|
165
|
+
path,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { success: false, errors };
|
|
169
|
+
}
|
|
170
|
+
export function parseNodeFromText(text) {
|
|
171
|
+
const trimmed = text.trim();
|
|
172
|
+
const format = detectFormat(trimmed);
|
|
173
|
+
let parsedData;
|
|
174
|
+
let errors = [];
|
|
175
|
+
if (format === 'markdown') {
|
|
176
|
+
const mdResult = parseMarkdownToNodeData(trimmed);
|
|
177
|
+
if (mdResult.errors.length > 0) {
|
|
178
|
+
return { success: false, errors: mdResult.errors };
|
|
179
|
+
}
|
|
180
|
+
parsedData = mdResult.data;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const jsonResult = parseJsonWithErrors(trimmed);
|
|
184
|
+
if (jsonResult.errors.length > 0) {
|
|
185
|
+
return { success: false, errors: jsonResult.errors };
|
|
186
|
+
}
|
|
187
|
+
parsedData = jsonResult.data;
|
|
188
|
+
}
|
|
189
|
+
const validation = validateWithZod(NodeSchema, parsedData);
|
|
190
|
+
return {
|
|
191
|
+
success: validation.success,
|
|
192
|
+
data: validation.data,
|
|
193
|
+
errors: validation.errors,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export function parseMachineNodesFromText(text) {
|
|
197
|
+
const trimmed = text.trim();
|
|
198
|
+
const format = detectFormat(trimmed);
|
|
199
|
+
let parsedData;
|
|
200
|
+
let errors = [];
|
|
201
|
+
if (format === 'markdown') {
|
|
202
|
+
const mdResult = parseMarkdownToNodeData(trimmed);
|
|
203
|
+
if (mdResult.errors.length > 0) {
|
|
204
|
+
return { success: false, errors: mdResult.errors };
|
|
205
|
+
}
|
|
206
|
+
if (typeof mdResult.data === 'object' && mdResult.data !== null && 'nodes' in mdResult.data) {
|
|
207
|
+
parsedData = mdResult.data.nodes;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
parsedData = mdResult.data;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const jsonResult = parseJsonWithErrors(trimmed);
|
|
215
|
+
if (jsonResult.errors.length > 0) {
|
|
216
|
+
return { success: false, errors: jsonResult.errors };
|
|
217
|
+
}
|
|
218
|
+
parsedData = jsonResult.data;
|
|
219
|
+
}
|
|
220
|
+
if (!Array.isArray(parsedData)) {
|
|
221
|
+
if (typeof parsedData === 'object' && parsedData !== null && 'op' in parsedData) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
errors: [{
|
|
225
|
+
message: 'Expected JSON array of nodes for json_or_markdown mode. Use schema mode for operations with "op" field.',
|
|
226
|
+
path: '/',
|
|
227
|
+
}]
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
errors: [{
|
|
233
|
+
message: 'Expected JSON array of nodes',
|
|
234
|
+
path: '/',
|
|
235
|
+
}]
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const nodes = [];
|
|
239
|
+
for (let i = 0; i < parsedData.length; i++) {
|
|
240
|
+
const nodeData = parsedData[i];
|
|
241
|
+
const validation = validateWithZod(MachineNodeSchema, nodeData, `/${i}`);
|
|
242
|
+
if (validation.success && validation.data) {
|
|
243
|
+
nodes.push(validation.data);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
errors.push(...validation.errors);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (errors.length > 0) {
|
|
250
|
+
return { success: false, errors };
|
|
251
|
+
}
|
|
252
|
+
return { success: true, data: nodes, errors: [] };
|
|
253
|
+
}
|
|
254
|
+
export function formatNodeErrors(errors) {
|
|
255
|
+
return errors.map(e => {
|
|
256
|
+
let msg = `Path: ${e.path}`;
|
|
257
|
+
if (e.line !== undefined && e.column !== undefined) {
|
|
258
|
+
msg += ` (line ${e.line}, column ${e.column})`;
|
|
259
|
+
}
|
|
260
|
+
msg += `\n Error: ${e.message}`;
|
|
261
|
+
return msg;
|
|
262
|
+
}).join('\n\n');
|
|
263
|
+
}
|
|
264
|
+
export function validateMachineNode(node) {
|
|
265
|
+
return validateWithZod(MachineNodeSchema, node);
|
|
266
|
+
}
|
|
267
|
+
export function validateMachineNodePair(pair) {
|
|
268
|
+
const result = MachineNodeSchema.shape.pairs.element.safeParse(pair);
|
|
269
|
+
if (result.success) {
|
|
270
|
+
return { success: true, errors: [] };
|
|
271
|
+
}
|
|
272
|
+
const errors = result.error.issues.map(issue => ({
|
|
273
|
+
message: issue.message,
|
|
274
|
+
path: formatZodPath(issue.path),
|
|
275
|
+
}));
|
|
276
|
+
return { success: false, errors };
|
|
277
|
+
}
|
|
278
|
+
export function validateMachineForward(forward) {
|
|
279
|
+
const result = MachineNodeSchema.shape.pairs.element.shape.forwards.element.safeParse(forward);
|
|
280
|
+
if (result.success) {
|
|
281
|
+
return { success: true, errors: [] };
|
|
282
|
+
}
|
|
283
|
+
const errors = result.error.issues.map(issue => ({
|
|
284
|
+
message: issue.message,
|
|
285
|
+
path: formatZodPath(issue.path),
|
|
286
|
+
}));
|
|
287
|
+
return { success: false, errors };
|
|
288
|
+
}
|
|
289
|
+
export function machineNodesToJson(nodes, options = {}) {
|
|
290
|
+
const { machineId, includeComments = true } = options;
|
|
291
|
+
let result = '';
|
|
292
|
+
if (includeComments && machineId) {
|
|
293
|
+
result += `// Machine ID: ${machineId}\n`;
|
|
294
|
+
}
|
|
295
|
+
result += JSON.stringify(nodes, null, 2);
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
export function machineNodesToMarkdown(nodes, options = {}) {
|
|
299
|
+
const { machineName, machineAddress, machineId, includeComments = true } = options;
|
|
300
|
+
let md = `# Machine Node Definition\n\n`;
|
|
301
|
+
if (machineName) {
|
|
302
|
+
md += `**Machine:** ${machineName}\n\n`;
|
|
303
|
+
}
|
|
304
|
+
if (machineAddress) {
|
|
305
|
+
md += `**Address:** ${machineAddress}\n\n`;
|
|
306
|
+
}
|
|
307
|
+
if (machineId) {
|
|
308
|
+
md += `**ID:** ${machineId}\n\n`;
|
|
309
|
+
}
|
|
310
|
+
md += `**Total Nodes:** ${nodes.length}\n\n`;
|
|
311
|
+
md += `---\n\n`;
|
|
312
|
+
for (const node of nodes) {
|
|
313
|
+
md += `## Node: ${node.name}\n\n`;
|
|
314
|
+
if (includeComments) {
|
|
315
|
+
md += `<!-- Node definition with pairs and forwards -->\n\n`;
|
|
316
|
+
}
|
|
317
|
+
for (const pair of node.pairs) {
|
|
318
|
+
md += `### Pair: ${pair.prev_node}\n\n`;
|
|
319
|
+
md += `- **prev_node**: ${pair.prev_node}\n`;
|
|
320
|
+
md += `- **threshold**: ${pair.threshold}\n\n`;
|
|
321
|
+
if (pair.forwards && pair.forwards.length > 0) {
|
|
322
|
+
md += `#### Forwards\n\n`;
|
|
323
|
+
md += `| name | weight | namedOperator | permissionIndex | guard |\n`;
|
|
324
|
+
md += `|------|--------|---------------|-----------------|-------|\n`;
|
|
325
|
+
for (const forward of pair.forwards) {
|
|
326
|
+
const namedOp = forward.namedOperator || '';
|
|
327
|
+
const permIdx = forward.permissionIndex?.toString() || '';
|
|
328
|
+
const guard = forward.guard?.guard || '';
|
|
329
|
+
md += `| ${forward.name} | ${forward.weight} | ${namedOp} | ${permIdx} | ${guard} |\n`;
|
|
330
|
+
}
|
|
331
|
+
md += `\n`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
md += `---\n\n`;
|
|
335
|
+
}
|
|
336
|
+
md += `## JSON Definition\n\n`;
|
|
337
|
+
if (includeComments) {
|
|
338
|
+
md += `<!-- You can also use the JSON format below -->\n\n`;
|
|
339
|
+
}
|
|
340
|
+
md += `\`\`\`json\n`;
|
|
341
|
+
if (includeComments && machineId) {
|
|
342
|
+
md += `// Machine ID: ${machineId}\n`;
|
|
343
|
+
}
|
|
344
|
+
md += JSON.stringify(nodes, null, 2);
|
|
345
|
+
md += `\n\`\`\`\n`;
|
|
346
|
+
return md;
|
|
347
|
+
}
|
|
348
|
+
export function saveMachineNodesToFile(nodes, filePath, format = 'json', options = {}) {
|
|
349
|
+
const content = format === 'json'
|
|
350
|
+
? machineNodesToJson(nodes, options)
|
|
351
|
+
: machineNodesToMarkdown(nodes, options);
|
|
352
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
353
|
+
}
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BuiltinPermissionIndex } from "wowok";
|
|
2
|
+
export const getValidBuiltinPermissionIndexes = () => {
|
|
3
|
+
return new Set(Object.values(BuiltinPermissionIndex).filter(value => typeof value === 'number'));
|
|
4
|
+
};
|
|
5
|
+
export const isValidPermissionIndex = (index) => {
|
|
6
|
+
return getValidBuiltinPermissionIndexes().has(index) || (index >= 1000 && index <= 65535);
|
|
7
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wowok_agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Making It Easy for AI Agents to Communicate, Collaborate, Trade, and Trust.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,19 +30,17 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
32
|
"lodash": "^4.18.1",
|
|
33
|
-
"wowok": "2.
|
|
33
|
+
"wowok": "2.2.0",
|
|
34
34
|
"zod": "^3.25.76"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.19.17",
|
|
38
|
-
"javascript-obfuscator": "^4.1.1",
|
|
39
38
|
"tsx": "^4.21.0",
|
|
40
39
|
"typescript": "^5.8.2"
|
|
41
40
|
},
|
|
42
41
|
"scripts": {
|
|
43
42
|
"clean": "rm -rf dist",
|
|
44
|
-
"build": "tsc
|
|
45
|
-
"obfuscate": "javascript-obfuscator dist --output dist --config obfuscator.json",
|
|
43
|
+
"build": "tsc",
|
|
46
44
|
"watch": "tsc --watch",
|
|
47
45
|
"start": "tsx src/index.ts"
|
|
48
46
|
}
|