evolclaw 3.2.0 → 3.3.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/CHANGELOG.md +17 -0
- package/README.md +1 -2
- package/dist/agents/{resolve.js → baseagent.js} +34 -5
- package/dist/agents/claude-runner.js +120 -27
- package/dist/agents/codex-app-server-client.js +364 -0
- package/dist/agents/codex-runner.js +1069 -141
- package/dist/agents/gemini-runner.js +2 -2
- package/dist/agents/runner-types.js +28 -0
- package/dist/aun/aid/store.js +1 -1
- package/dist/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +406 -293
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +97 -150
- package/dist/channels/qqbot.js +75 -138
- package/dist/channels/wechat.js +75 -136
- package/dist/channels/wecom.js +75 -138
- package/dist/cli/agent.js +8 -5
- package/dist/cli/index.js +177 -44
- package/dist/cli/init.js +33 -6
- package/dist/cli/model.js +1 -1
- package/dist/cli/stats.js +558 -0
- package/dist/cli/version.js +87 -0
- package/dist/cli/watch-msg.js +5 -2
- package/dist/config-store.js +12 -6
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +473 -114
- package/dist/core/evolagent-registry.js +1 -0
- package/dist/core/evolagent.js +1 -1
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +63 -1
- package/dist/core/message/im-renderer.js +35 -13
- package/dist/core/message/items-formatter.js +9 -1
- package/dist/core/message/message-bridge.js +49 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +295 -35
- package/dist/core/message/message-queue.js +2 -2
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +56 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-scope.js +2 -2
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
- package/dist/core/session/session-manager.js +27 -13
- package/dist/core/session/session-title.js +26 -0
- package/dist/core/stats/billing.js +151 -0
- package/dist/core/stats/budget.js +93 -0
- package/dist/core/stats/db.js +314 -0
- package/dist/core/stats/eck-vars.js +84 -0
- package/dist/core/stats/index.js +10 -0
- package/dist/core/stats/normalizer.js +78 -0
- package/dist/core/stats/query.js +760 -0
- package/dist/core/stats/writer.js +115 -0
- package/dist/core/trigger/manager.js +34 -0
- package/dist/core/trigger/parser.js +9 -3
- package/dist/core/trigger/scheduler.js +20 -17
- package/dist/{agents → eck}/manifest-engine.js +20 -1
- package/dist/{agents → eck}/message-renderer.js +24 -1
- package/dist/index.js +130 -8
- package/dist/ipc.js +17 -1
- package/dist/utils/cross-platform.js +23 -5
- package/dist/utils/ecweb-pair.js +20 -0
- package/dist/utils/stats.js +14 -0
- package/kits/docs/evolclaw/INDEX.md +3 -1
- package/kits/docs/evolclaw/fs-architecture.md +1215 -0
- package/kits/docs/evolclaw/fs.md +131 -0
- package/kits/docs/evolclaw/group-fs.md +209 -0
- package/kits/docs/evolclaw/stats.md +70 -0
- package/kits/docs/venues/aun-group.md +29 -6
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +30 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/system-fragments/response-depth.md +16 -0
- package/package.json +4 -4
- package/dist/agents/baseagent-normalize.js +0 -19
- package/dist/core/relation/peer-key.js +0 -16
- package/dist/evolclaw-config.js +0 -11
- package/dist/utils/channel-helpers.js +0 -46
- /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
- /package/dist/{agents → eck}/kit-renderer.js +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
export class CodexAppServerClient {
|
|
5
|
+
options;
|
|
6
|
+
proc = null;
|
|
7
|
+
pending = new Map();
|
|
8
|
+
notificationHandlers = new Set();
|
|
9
|
+
nextId = 1;
|
|
10
|
+
initialized = false;
|
|
11
|
+
stderrBuffer = [];
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
onNotification(handler) {
|
|
16
|
+
this.notificationHandlers.add(handler);
|
|
17
|
+
return () => this.notificationHandlers.delete(handler);
|
|
18
|
+
}
|
|
19
|
+
async threadStart(projectPath, options) {
|
|
20
|
+
const effort = options?.effort ?? this.options.effort;
|
|
21
|
+
return this.request('thread/start', {
|
|
22
|
+
cwd: projectPath,
|
|
23
|
+
model: options?.model ?? this.options.model ?? null,
|
|
24
|
+
approvalPolicy: options?.approvalPolicy ?? null,
|
|
25
|
+
approvalsReviewer: options?.approvalsReviewer ?? this.options.approvalsReviewer ?? null,
|
|
26
|
+
sandbox: options?.sandbox ?? null,
|
|
27
|
+
config: this.buildThreadConfig(effort, options?.config),
|
|
28
|
+
...(options?.baseInstructions ? { baseInstructions: options.baseInstructions } : {}),
|
|
29
|
+
...(options?.developerInstructions ? { developerInstructions: options.developerInstructions } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async threadResume(threadId, projectPath, options) {
|
|
33
|
+
const effort = options?.effort ?? this.options.effort;
|
|
34
|
+
return this.request('thread/resume', {
|
|
35
|
+
threadId,
|
|
36
|
+
cwd: projectPath,
|
|
37
|
+
model: options?.model ?? this.options.model ?? null,
|
|
38
|
+
approvalPolicy: options?.approvalPolicy ?? null,
|
|
39
|
+
approvalsReviewer: options?.approvalsReviewer ?? this.options.approvalsReviewer ?? null,
|
|
40
|
+
sandbox: options?.sandbox ?? null,
|
|
41
|
+
config: this.buildThreadConfig(effort, options?.config),
|
|
42
|
+
...(options?.baseInstructions ? { baseInstructions: options.baseInstructions } : {}),
|
|
43
|
+
...(options?.developerInstructions ? { developerInstructions: options.developerInstructions } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async turnStart(threadId, input, options) {
|
|
47
|
+
const sandboxPolicy = this.toTurnSandboxPolicy(options?.sandbox);
|
|
48
|
+
return this.request('turn/start', {
|
|
49
|
+
threadId,
|
|
50
|
+
input,
|
|
51
|
+
...(options?.cwd ? { cwd: options.cwd } : {}),
|
|
52
|
+
...(options?.model ? { model: options.model } : {}),
|
|
53
|
+
...(options?.approvalPolicy ? { approvalPolicy: options.approvalPolicy } : {}),
|
|
54
|
+
...(sandboxPolicy ? { sandboxPolicy } : {}),
|
|
55
|
+
...(options?.effort ? { effort: options.effort } : {}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
toTurnSandboxPolicy(sandbox) {
|
|
59
|
+
if (!sandbox)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (sandbox === 'danger-full-access')
|
|
62
|
+
return { type: 'dangerFullAccess' };
|
|
63
|
+
if (sandbox === 'read-only')
|
|
64
|
+
return { type: 'readOnly', networkAccess: true };
|
|
65
|
+
if (sandbox === 'workspace-write') {
|
|
66
|
+
return {
|
|
67
|
+
type: 'workspaceWrite',
|
|
68
|
+
writableRoots: [],
|
|
69
|
+
networkAccess: true,
|
|
70
|
+
excludeTmpdirEnvVar: false,
|
|
71
|
+
excludeSlashTmp: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
async threadRead(threadId, includeTurns = true) {
|
|
77
|
+
return this.request('thread/read', { threadId, includeTurns });
|
|
78
|
+
}
|
|
79
|
+
async threadRollback(threadId, numTurns) {
|
|
80
|
+
return this.request('thread/rollback', { threadId, numTurns });
|
|
81
|
+
}
|
|
82
|
+
async threadFork(threadId, projectPath, title, options) {
|
|
83
|
+
const effort = options?.effort ?? this.options.effort;
|
|
84
|
+
const response = await this.request('thread/fork', {
|
|
85
|
+
threadId,
|
|
86
|
+
cwd: projectPath,
|
|
87
|
+
model: options?.model ?? this.options.model ?? null,
|
|
88
|
+
approvalsReviewer: options?.approvalsReviewer ?? this.options.approvalsReviewer ?? null,
|
|
89
|
+
config: this.buildThreadConfig(effort, options?.config),
|
|
90
|
+
excludeTurns: false,
|
|
91
|
+
persistExtendedHistory: false,
|
|
92
|
+
});
|
|
93
|
+
const forkedThreadId = response.thread?.id;
|
|
94
|
+
if (title && forkedThreadId) {
|
|
95
|
+
await this.threadSetName(forkedThreadId, title).catch(error => {
|
|
96
|
+
logger.debug(`[CodexAppServer] thread/name/set failed after fork: ${error}`);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return response;
|
|
100
|
+
}
|
|
101
|
+
buildThreadConfig(effort, config) {
|
|
102
|
+
const merged = { ...(config ?? {}) };
|
|
103
|
+
if (effort)
|
|
104
|
+
merged.model_reasoning_effort = effort;
|
|
105
|
+
return Object.keys(merged).length > 0 ? merged : null;
|
|
106
|
+
}
|
|
107
|
+
async threadCompactStart(threadId) {
|
|
108
|
+
await this.request('thread/compact/start', { threadId });
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
async threadTurnsList(threadId, limit) {
|
|
112
|
+
return this.request('thread/turns/list', {
|
|
113
|
+
threadId,
|
|
114
|
+
...(limit ? { limit } : {}),
|
|
115
|
+
sortDirection: 'asc',
|
|
116
|
+
itemsView: 'full',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async threadTurnsItemsList(threadId, turnId, limit) {
|
|
120
|
+
return this.request('thread/turns/items/list', {
|
|
121
|
+
threadId,
|
|
122
|
+
turnId,
|
|
123
|
+
...(limit ? { limit } : {}),
|
|
124
|
+
sortDirection: 'asc',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async threadSetName(threadId, name) {
|
|
128
|
+
await this.request('thread/name/set', { threadId, name });
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
async threadMetadataUpdate(threadId, gitInfo) {
|
|
132
|
+
const params = { threadId };
|
|
133
|
+
if (gitInfo) {
|
|
134
|
+
params.gitInfo = Object.fromEntries(Object.entries(gitInfo).filter((entry) => entry[1] !== undefined));
|
|
135
|
+
}
|
|
136
|
+
await this.request('thread/metadata/update', params);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
async turnInterrupt(threadId, turnId) {
|
|
140
|
+
await this.request('turn/interrupt', { threadId, turnId });
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
async modelList(includeHidden = false) {
|
|
144
|
+
return this.request('model/list', { includeHidden });
|
|
145
|
+
}
|
|
146
|
+
async modelProviderCapabilitiesRead() {
|
|
147
|
+
return this.request('modelProvider/capabilities/read', {});
|
|
148
|
+
}
|
|
149
|
+
async close() {
|
|
150
|
+
const proc = this.proc;
|
|
151
|
+
this.proc = null;
|
|
152
|
+
this.initialized = false;
|
|
153
|
+
for (const pending of this.pending.values()) {
|
|
154
|
+
pending.reject(new Error('Codex app-server closed'));
|
|
155
|
+
}
|
|
156
|
+
this.pending.clear();
|
|
157
|
+
if (!proc)
|
|
158
|
+
return;
|
|
159
|
+
proc.stdin.end();
|
|
160
|
+
proc.kill('SIGTERM');
|
|
161
|
+
}
|
|
162
|
+
async ensureStarted() {
|
|
163
|
+
if (this.initialized)
|
|
164
|
+
return;
|
|
165
|
+
if (!this.proc)
|
|
166
|
+
this.startProcess();
|
|
167
|
+
await this.request('initialize', {
|
|
168
|
+
clientInfo: {
|
|
169
|
+
name: 'evolclaw_codex_app_server',
|
|
170
|
+
title: 'EvolClaw Codex App Server Client',
|
|
171
|
+
version: '1.0.0',
|
|
172
|
+
},
|
|
173
|
+
capabilities: { experimentalApi: true },
|
|
174
|
+
});
|
|
175
|
+
this.notify('initialized');
|
|
176
|
+
this.initialized = true;
|
|
177
|
+
}
|
|
178
|
+
startProcess() {
|
|
179
|
+
const env = { ...process.env, ...this.options.env };
|
|
180
|
+
env.CODEX_API_KEY = this.options.apiKey;
|
|
181
|
+
if (this.options.baseUrl) {
|
|
182
|
+
env.OPENAI_BASE_URL = this.options.baseUrl;
|
|
183
|
+
}
|
|
184
|
+
const args = this.buildProcessArgs();
|
|
185
|
+
this.proc = spawn('codex', args, {
|
|
186
|
+
cwd: this.options.cwd,
|
|
187
|
+
env,
|
|
188
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
189
|
+
});
|
|
190
|
+
this.proc.once('error', error => this.failAll(error));
|
|
191
|
+
this.proc.once('exit', (code, signal) => {
|
|
192
|
+
this.failAll(new Error(`Codex app-server exited: code=${code ?? 'null'} signal=${signal ?? 'null'}`));
|
|
193
|
+
});
|
|
194
|
+
const rl = readline.createInterface({ input: this.proc.stdout, crlfDelay: Infinity });
|
|
195
|
+
rl.on('line', line => this.handleLine(line));
|
|
196
|
+
this.proc.stderr.on('data', chunk => {
|
|
197
|
+
const text = String(chunk).trim();
|
|
198
|
+
if (!text)
|
|
199
|
+
return;
|
|
200
|
+
this.stderrBuffer.push(text);
|
|
201
|
+
if (this.stderrBuffer.length > 20)
|
|
202
|
+
this.stderrBuffer.shift();
|
|
203
|
+
logger.debug(`[CodexAppServer] ${text}`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
buildProcessArgs() {
|
|
207
|
+
const args = ['app-server', '--listen', 'stdio://'];
|
|
208
|
+
if (this.options.enableRequestUserInput !== false) {
|
|
209
|
+
args.push('--enable', 'default_mode_request_user_input');
|
|
210
|
+
}
|
|
211
|
+
if (this.options.baseUrl) {
|
|
212
|
+
args.push('--config', `openai_base_url=${JSON.stringify(this.options.baseUrl)}`);
|
|
213
|
+
}
|
|
214
|
+
if (this.options.effort) {
|
|
215
|
+
args.push('--config', `model_reasoning_effort=${JSON.stringify(this.options.effort)}`);
|
|
216
|
+
}
|
|
217
|
+
return args;
|
|
218
|
+
}
|
|
219
|
+
async request(method, params) {
|
|
220
|
+
await this.ensureReadyFor(method);
|
|
221
|
+
const proc = this.proc;
|
|
222
|
+
if (!proc)
|
|
223
|
+
throw new Error('Codex app-server is not running');
|
|
224
|
+
const id = String(this.nextId++);
|
|
225
|
+
const payload = { id, method };
|
|
226
|
+
if (params !== undefined && params !== null)
|
|
227
|
+
payload.params = params;
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
this.pending.set(id, { resolve, reject, method, params });
|
|
230
|
+
proc.stdin.write(`${JSON.stringify(payload)}\n`, error => {
|
|
231
|
+
if (!error)
|
|
232
|
+
return;
|
|
233
|
+
this.pending.delete(id);
|
|
234
|
+
reject(error);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
respond(id, result) {
|
|
239
|
+
if (!this.proc)
|
|
240
|
+
return;
|
|
241
|
+
const payload = JSON.stringify({ id, result }) + '\n';
|
|
242
|
+
this.proc.stdin.write(payload, error => {
|
|
243
|
+
if (error)
|
|
244
|
+
logger.warn(`[CodexAppServer] failed to write response id=${id}: ${error.message}`);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
respondError(id, error) {
|
|
248
|
+
if (!this.proc)
|
|
249
|
+
return;
|
|
250
|
+
const payload = JSON.stringify({ id, error: { message: error.message } }) + '\n';
|
|
251
|
+
this.proc.stdin.write(payload, writeError => {
|
|
252
|
+
if (writeError)
|
|
253
|
+
logger.warn(`[CodexAppServer] failed to write error response id=${id}: ${writeError.message}`);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
notify(method, params) {
|
|
257
|
+
if (!this.proc)
|
|
258
|
+
return;
|
|
259
|
+
const payload = { method };
|
|
260
|
+
if (params !== undefined && params !== null)
|
|
261
|
+
payload.params = params;
|
|
262
|
+
this.proc.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
263
|
+
}
|
|
264
|
+
async ensureReadyFor(method) {
|
|
265
|
+
if (method === 'initialize') {
|
|
266
|
+
if (!this.proc)
|
|
267
|
+
this.startProcess();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
await this.ensureStarted();
|
|
271
|
+
}
|
|
272
|
+
handleLine(line) {
|
|
273
|
+
let message;
|
|
274
|
+
try {
|
|
275
|
+
message = JSON.parse(line);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
logger.debug(`[CodexAppServer] Ignoring non-JSON line: ${line}`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const rawId = message.id;
|
|
282
|
+
if (typeof message.method === 'string') {
|
|
283
|
+
if (rawId === undefined) {
|
|
284
|
+
this.emitNotification({ method: message.method, params: message.params });
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
this.handleServerRequest({ id: rawId, method: message.method, params: message.params });
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (rawId === undefined)
|
|
292
|
+
return;
|
|
293
|
+
const messageId = String(rawId);
|
|
294
|
+
const pending = this.pending.get(messageId);
|
|
295
|
+
if (!pending)
|
|
296
|
+
return;
|
|
297
|
+
this.pending.delete(messageId);
|
|
298
|
+
if (message.error) {
|
|
299
|
+
const detail = typeof message.error?.message === 'string' ? message.error.message : JSON.stringify(message.error);
|
|
300
|
+
const stderr = this.stderrBuffer.length ? `\n${this.stderrBuffer.join('\n')}` : '';
|
|
301
|
+
pending.reject(new Error(`${pending.method} failed: ${detail}${stderr}`));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
pending.resolve(message.result ?? null);
|
|
305
|
+
}
|
|
306
|
+
handleServerRequest(request) {
|
|
307
|
+
const handler = this.options.onServerRequest;
|
|
308
|
+
logger.info(`[CodexAppServer] server request id=${request.id} method=${request.method}`);
|
|
309
|
+
if (!handler) {
|
|
310
|
+
logger.warn(`[CodexAppServer] unsupported server request id=${request.id} method=${request.method}`);
|
|
311
|
+
this.respondError(request.id, new Error('Unsupported Codex app-server request: ' + request.method));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
Promise.resolve(handler(request))
|
|
315
|
+
.then(result => {
|
|
316
|
+
logger.info(`[CodexAppServer] server response id=${request.id} method=${request.method} result=${JSON.stringify(result ?? null)}`);
|
|
317
|
+
this.respond(request.id, result ?? null);
|
|
318
|
+
})
|
|
319
|
+
.catch(error => {
|
|
320
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
321
|
+
logger.warn(`[CodexAppServer] server request failed id=${request.id} method=${request.method}: ${err.message}`);
|
|
322
|
+
this.respondError(request.id, err);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
emitNotification(notification) {
|
|
326
|
+
this.resolveSyntheticResponses(notification);
|
|
327
|
+
for (const handler of this.notificationHandlers) {
|
|
328
|
+
try {
|
|
329
|
+
handler(notification);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
logger.debug(`[CodexAppServer] notification handler failed: ${error}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
resolveSyntheticResponses(notification) {
|
|
337
|
+
if (notification.method !== 'thread/compacted')
|
|
338
|
+
return;
|
|
339
|
+
const threadId = typeof notification.params?.threadId === 'string'
|
|
340
|
+
? notification.params.threadId
|
|
341
|
+
: typeof notification.params?.thread_id === 'string'
|
|
342
|
+
? notification.params.thread_id
|
|
343
|
+
: undefined;
|
|
344
|
+
if (!threadId)
|
|
345
|
+
return;
|
|
346
|
+
for (const [id, pending] of this.pending.entries()) {
|
|
347
|
+
const pendingThreadId = typeof pending.params?.threadId === 'string' ? pending.params.threadId : undefined;
|
|
348
|
+
if (pending.method !== 'thread/compact/start' || pendingThreadId !== threadId)
|
|
349
|
+
continue;
|
|
350
|
+
this.pending.delete(id);
|
|
351
|
+
logger.info(`[CodexAppServer] resolved pending thread/compact/start id=${id} from thread/compacted thread=${threadId}`);
|
|
352
|
+
pending.resolve({});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
failAll(error) {
|
|
356
|
+
if (this.proc)
|
|
357
|
+
this.proc = null;
|
|
358
|
+
this.initialized = false;
|
|
359
|
+
for (const pending of this.pending.values()) {
|
|
360
|
+
pending.reject(error);
|
|
361
|
+
}
|
|
362
|
+
this.pending.clear();
|
|
363
|
+
}
|
|
364
|
+
}
|