evolcore 0.0.24 → 0.0.25
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 +578 -561
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +160 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +29 -29
- package/bin/install-codex-managed-hooks.mjs +555 -555
- package/dist/agents/claude-runner.js +4 -1
- package/dist/agents/codex-runner.js +7 -4
- package/dist/agents/ecagent-runner.js +14 -13
- package/dist/agents/gemini-runner.js +22 -2
- package/dist/aun/aid/agentmd.js +56 -4
- package/dist/aun/aid/index.js +1 -0
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/cli/agent-command.js +186 -87
- package/dist/cli/agent-create.js +42 -4
- package/dist/cli/agent.js +103 -1
- package/dist/cli/aun-commands.js +360 -294
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +20 -20
- package/dist/cli/contact.js +10 -10
- package/dist/cli/ctl-command.js +22 -22
- package/dist/cli/daemon-commands.js +15 -15
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +7 -7
- package/dist/cli/index.js +77 -77
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/menu-client.js +11 -11
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +30 -30
- package/dist/cli/stats.js +51 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +20 -3
- package/dist/config/resolved-config-op.js +39 -1
- package/dist/core/auth/operation-authorizer.js +62 -4
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +91 -16
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +41 -5
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +9 -9
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/message/response-engine.js +12 -6
- package/dist/core/permission/tool-policy.js +45 -6
- package/dist/index.js +209 -162
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/welcome.js +22 -22
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +365 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -69
- package/kits/docs/evolcore/agent-create.md +170 -156
- package/kits/docs/evolcore/agent.md +83 -73
- package/kits/docs/evolcore/aid.md +64 -62
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +195 -195
- package/kits/docs/evolcore/contact.md +68 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +34 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +165 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +270 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +25 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +195 -183
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +39 -37
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +96 -84
- package/kits/templates/roles/member.json +97 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +86 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +38 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +98 -98
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -2998,6 +2998,12 @@ export class ResponseEngine {
|
|
|
2998
2998
|
bootstrapAgentMdPath: agentMdPath(selfAid),
|
|
2999
2999
|
};
|
|
3000
3000
|
}
|
|
3001
|
+
if (selfAid && peerRole === 'owner' && effectivePermissionMode !== 'readonly') {
|
|
3002
|
+
modelOverride = {
|
|
3003
|
+
...(modelOverride || {}),
|
|
3004
|
+
personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'personal.md'),
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3001
3007
|
if (normalizedBaseagent.canonical === 'claude') {
|
|
3002
3008
|
modelOverride = {
|
|
3003
3009
|
...(modelOverride || {}),
|
|
@@ -4561,12 +4567,12 @@ export class ResponseEngine {
|
|
|
4561
4567
|
return undefined;
|
|
4562
4568
|
const shouldClose = !writerDb;
|
|
4563
4569
|
try {
|
|
4564
|
-
const row = db.prepare(`SELECT model, input_tokens, cache_creation_tokens, cache_read_tokens,
|
|
4565
|
-
context_tokens, max_tokens, auto_compact_tokens
|
|
4566
|
-
FROM model_calls
|
|
4567
|
-
WHERE session_id = ?
|
|
4568
|
-
AND agent_session_id = ?
|
|
4569
|
-
ORDER BY ts DESC, call_index DESC
|
|
4570
|
+
const row = db.prepare(`SELECT model, input_tokens, cache_creation_tokens, cache_read_tokens,
|
|
4571
|
+
context_tokens, max_tokens, auto_compact_tokens
|
|
4572
|
+
FROM model_calls
|
|
4573
|
+
WHERE session_id = ?
|
|
4574
|
+
AND agent_session_id = ?
|
|
4575
|
+
ORDER BY ts DESC, call_index DESC
|
|
4570
4576
|
LIMIT 1`).get(sessionId, agentSessionId);
|
|
4571
4577
|
if (!row)
|
|
4572
4578
|
return undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import { randomUUID } from 'crypto';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
6
|
import { resolveRoot } from '../../paths.js';
|
|
@@ -41,6 +42,14 @@ export function expandManagedTempPath(value, options = {}) {
|
|
|
41
42
|
}
|
|
42
43
|
const WRITE_INTENT_RE = /(?:^|[\s;&|])(?:mkdir|mktemp|touch|tee|cp|mv|install|chmod|chown|ln|rm)(?:[\s;&|]|$)|(?:^|[\s])(?:\d>>?|>>?)\s*/i;
|
|
43
44
|
const TEMP_TOKEN_RE = /(?:\$TMPDIR|\$\{TMPDIR\}|\/tmp(?:\/|$)|\/var\/tmp(?:\/|$)|\/dev\/shm(?:\/|$))[^\s'";&|<>)]*/g;
|
|
45
|
+
function windowsTempRoots(options) {
|
|
46
|
+
if (process.platform !== 'win32')
|
|
47
|
+
return [];
|
|
48
|
+
return [os.tmpdir(), options.env?.TEMP, options.env?.TMP, process.env.TEMP, process.env.TMP]
|
|
49
|
+
.filter((value) => typeof value === 'string' && path.isAbsolute(value))
|
|
50
|
+
.map(value => resolveProtectedCandidate(value))
|
|
51
|
+
.filter((value, index, values) => values.indexOf(value) === index);
|
|
52
|
+
}
|
|
44
53
|
/**
|
|
45
54
|
* Inspect shell write targets that look like temporary paths. Ordinary
|
|
46
55
|
* project writes are left to the existing workspace/H-class policy; any
|
|
@@ -52,19 +61,32 @@ export function checkManagedTempShellCommand(command, options = {}) {
|
|
|
52
61
|
const root = resolveManagedTempDir(options.env);
|
|
53
62
|
const normalized = substituteTmpDir(command, root);
|
|
54
63
|
const candidates = [...normalized.matchAll(TEMP_TOKEN_RE)].map(match => match[0].replace(/[.,:]+$/, ''));
|
|
55
|
-
|
|
64
|
+
// Windows has no portable `/tmp` spelling. Treat absolute paths below the
|
|
65
|
+
// host temporary roots as temporary operands too, so a sibling session
|
|
66
|
+
// directory cannot bypass the managed TMPDIR check.
|
|
67
|
+
if (process.platform === 'win32') {
|
|
68
|
+
const roots = windowsTempRoots(options);
|
|
69
|
+
const windowsCandidates = normalized.match(/[A-Za-z]:[\\/][^\s'";&|<>)]*/g) ?? [];
|
|
70
|
+
for (const candidate of windowsCandidates) {
|
|
71
|
+
const canonical = resolveProtectedCandidate(candidate.replace(/[.,:]+$/, ''), options.cwd);
|
|
72
|
+
if (roots.some(root => isSameOrDescendant(canonical, root)))
|
|
73
|
+
candidates.push(candidate.replace(/[.,:]+$/, ''));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
77
|
+
if (uniqueCandidates.length === 0)
|
|
56
78
|
return { kind: 'none' };
|
|
57
79
|
if (!root) {
|
|
58
|
-
return { kind: 'deny', paths:
|
|
80
|
+
return { kind: 'deny', paths: uniqueCandidates, reason: 'TMPDIR is unset or not an absolute managed path' };
|
|
59
81
|
}
|
|
60
|
-
const outside =
|
|
82
|
+
const outside = uniqueCandidates.filter(candidate => !isManagedTempPath(candidate, {
|
|
61
83
|
...options,
|
|
62
84
|
env: { ...(options.env ?? process.env), TMPDIR: root },
|
|
63
85
|
}));
|
|
64
86
|
if (outside.length > 0) {
|
|
65
87
|
return { kind: 'deny', paths: outside, reason: 'temporary write target is outside the session-managed TMPDIR' };
|
|
66
88
|
}
|
|
67
|
-
return { kind: 'allow', paths:
|
|
89
|
+
return { kind: 'allow', paths: uniqueCandidates };
|
|
68
90
|
}
|
|
69
91
|
/**
|
|
70
92
|
* Replace only already-validated temporary path operands before a second
|
|
@@ -2237,16 +2259,33 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2237
2259
|
&& managedAidArgv[2] === 'agentmd'
|
|
2238
2260
|
&& managedAidArgv[3] === 'put'
|
|
2239
2261
|
&& ownAid;
|
|
2262
|
+
const avatarFileIndex = managedAidArgv?.indexOf('--file') ?? -1;
|
|
2263
|
+
const avatarUrlIndex = managedAidArgv?.indexOf('--url') ?? -1;
|
|
2264
|
+
const avatarSourceIndex = avatarFileIndex >= 0 ? avatarFileIndex : avatarUrlIndex;
|
|
2265
|
+
const isSelfAvatarUpload = !!managedAidArgv
|
|
2266
|
+
&& (avatarSourceIndex === 4)
|
|
2267
|
+
&& managedAidArgv.length === 6
|
|
2268
|
+
&& managedAidArgv[0] === 'ec'
|
|
2269
|
+
&& managedAidArgv[1] === 'aid'
|
|
2270
|
+
&& managedAidArgv[2] === 'avatar'
|
|
2271
|
+
&& !!context.selfAid
|
|
2272
|
+
&& managedAidArgv[3]?.replace(/^@/, '') === context.selfAid.replace(/^@/, '')
|
|
2273
|
+
&& !!managedAidArgv[5]
|
|
2274
|
+
&& !managedAidArgv[5].startsWith('-');
|
|
2240
2275
|
const canPublishSelfAgentMd = isSelfAgentMdPublish
|
|
2241
2276
|
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
2242
|
-
const
|
|
2277
|
+
const canUploadSelfAvatar = isSelfAvatarUpload
|
|
2278
|
+
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
2279
|
+
const isAllowedManagedAidCommand = isManagedAidPublicRead || isSelfManagedAidShow || canPublishSelfAgentMd || canUploadSelfAvatar;
|
|
2243
2280
|
if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
|
|
2244
2281
|
return {
|
|
2245
2282
|
behavior: 'deny',
|
|
2246
2283
|
input,
|
|
2247
2284
|
message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
2248
2285
|
? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
|
|
2249
|
-
:
|
|
2286
|
+
: isSelfAvatarUpload && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
2287
|
+
? `🔒 ${context.permissionMode} 模式禁止上传头像;请切换到 request 或 bypass 后再执行 \`ec aid avatar <当前 self AID> (--file <path> 或 --url <https-url>)\`。`
|
|
2288
|
+
: '🔒 EvolCore 托管会话允许 `ec aid show <当前 self AID>`、`ec aid lookup <任意合法 AID>` 或 `ec aid agentmd get <任意合法 AID>`;`ec aid agentmd put` 及 `ec aid avatar` 还要求针对当前 self AID 且处于 request 或 bypass。可选后缀仅限 `--format json`。禁止列举本地身份、切换身份或对其它 AID 执行 show/put/avatar。',
|
|
2250
2289
|
policyCode: 'managed_ec_aid_scope_forbidden',
|
|
2251
2290
|
};
|
|
2252
2291
|
}
|
package/dist/index.js
CHANGED
|
@@ -333,153 +333,153 @@ function seedUpgradeCheckTrigger(manager, owner) {
|
|
|
333
333
|
// take the watchdog down with it.
|
|
334
334
|
const monitorEntry = platform.resolveCliEntry(packageRoot);
|
|
335
335
|
const stageParent = path.join(resolvePaths().root, 'data', 'monitor-stage');
|
|
336
|
-
fs.writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
337
|
-
const fs = require('node:fs');
|
|
338
|
-
const path = require('node:path');
|
|
339
|
-
const { existsSync, readFileSync } = fs;
|
|
340
|
-
const { spawn } = require('node:child_process');
|
|
341
|
-
|
|
342
|
-
const CLI_PATH = ${JSON.stringify(monitorEntry.entry)};
|
|
343
|
-
const CLI_ARGS = ${JSON.stringify(monitorEntry.args)};
|
|
344
|
-
const PACKAGE_ROOT = ${JSON.stringify(packageRoot)};
|
|
345
|
-
const STAGE_PARENT = ${JSON.stringify(stageParent)};
|
|
346
|
-
const PACKAGE_JSON = ${JSON.stringify(packageJsonPath)};
|
|
347
|
-
const PACKAGE_NAME = ${JSON.stringify(MAIN_PACKAGE_NAME)};
|
|
348
|
-
const DEV_MODE = ${devMode ? 'true' : 'false'};
|
|
349
|
-
|
|
350
|
-
function emit(outcome, text) {
|
|
351
|
-
process.stdout.write(JSON.stringify({ outcome, text, files: [] }));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function compareVersions(a, b) {
|
|
355
|
-
const pa = a.split('-')[0].split('.').map(Number);
|
|
356
|
-
const pb = b.split('-')[0].split('.').map(Number);
|
|
357
|
-
const count = Math.max(pa.length, pb.length);
|
|
358
|
-
for (let i = 0; i < count; i += 1) {
|
|
359
|
-
const av = Number.isFinite(pa[i]) ? pa[i] : 0;
|
|
360
|
-
const bv = Number.isFinite(pb[i]) ? pb[i] : 0;
|
|
361
|
-
if (av < bv) return -1;
|
|
362
|
-
if (av > bv) return 1;
|
|
363
|
-
}
|
|
364
|
-
return 0;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
async function latestVersion() {
|
|
368
|
-
const controller = new AbortController();
|
|
369
|
-
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
370
|
-
try {
|
|
371
|
-
const response = await fetch('https://registry.npmjs.org/' + encodeURIComponent(PACKAGE_NAME) + '/latest', {
|
|
372
|
-
signal: controller.signal,
|
|
373
|
-
headers: { Accept: 'application/json' },
|
|
374
|
-
});
|
|
375
|
-
if (!response.ok) return null;
|
|
376
|
-
const data = await response.json();
|
|
377
|
-
return typeof data.version === 'string' ? data.version : null;
|
|
378
|
-
} finally {
|
|
379
|
-
clearTimeout(timer);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async function main() {
|
|
384
|
-
if (DEV_MODE) {
|
|
385
|
-
emit('noop', 'upgrade check skipped in dev mode');
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
let localVersion;
|
|
390
|
-
try {
|
|
391
|
-
localVersion = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8')).version;
|
|
392
|
-
} catch {
|
|
393
|
-
emit('error', 'failed to read local ec version');
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
if (typeof localVersion !== 'string' || !localVersion) {
|
|
397
|
-
emit('error', 'failed to read local ec version');
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
let remoteVersion;
|
|
402
|
-
try {
|
|
403
|
-
remoteVersion = await latestVersion();
|
|
404
|
-
} catch {
|
|
405
|
-
remoteVersion = null;
|
|
406
|
-
}
|
|
407
|
-
if (!remoteVersion) {
|
|
408
|
-
emit('error', 'failed to check evolcore latest version');
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
if (compareVersions(localVersion, remoteVersion) >= 0) {
|
|
412
|
-
emit('noop', 'evolcore is already up to date (' + localVersion + '; latest ' + remoteVersion + ')');
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
if (!existsSync(CLI_PATH)) {
|
|
416
|
-
emit('error', 'restart-monitor entry not found: ' + CLI_PATH);
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Installed mode: copy the monitor out of the package before it upgrades that
|
|
421
|
-
// same package. Mirrors prepareMonitorStage(); kept inline because this file
|
|
422
|
-
// is generated standalone CJS. Any failure degrades to launching in place —
|
|
423
|
-
// losing upgrade isolation beats losing the upgrade.
|
|
424
|
-
let launchArgs = CLI_ARGS;
|
|
425
|
-
const launchEnv = { ...process.env };
|
|
426
|
-
if (!DEV_MODE && STAGE_PARENT) {
|
|
427
|
-
try {
|
|
428
|
-
const stageDir = path.join(STAGE_PARENT, 'upgrade-' + process.pid + '-' + Date.now());
|
|
429
|
-
fs.mkdirSync(stageDir, { recursive: true });
|
|
430
|
-
fs.cpSync(path.join(PACKAGE_ROOT, 'dist'), path.join(stageDir, 'dist'), { recursive: true });
|
|
431
|
-
if (existsSync(PACKAGE_JSON)) fs.copyFileSync(PACKAGE_JSON, path.join(stageDir, 'package.json'));
|
|
432
|
-
const modulesSource = path.join(PACKAGE_ROOT, 'node_modules');
|
|
433
|
-
if (existsSync(modulesSource)) {
|
|
434
|
-
const staged = path.join(stageDir, 'node_modules');
|
|
435
|
-
try {
|
|
436
|
-
// junction works unelevated on Windows; dir symlink elsewhere.
|
|
437
|
-
fs.symlinkSync(modulesSource, staged, process.platform === 'win32' ? 'junction' : 'dir');
|
|
438
|
-
fs.readdirSync(staged);
|
|
439
|
-
} catch (linkError) {
|
|
440
|
-
fs.rmSync(staged, { recursive: true, force: true });
|
|
441
|
-
fs.cpSync(modulesSource, staged, { recursive: true });
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
const stagedEntry = path.join(stageDir, 'dist', 'cli', 'index.js');
|
|
445
|
-
if (existsSync(stagedEntry)) {
|
|
446
|
-
launchArgs = [stagedEntry];
|
|
447
|
-
launchEnv.EVOLCORE_TARGET_PACKAGE_ROOT = PACKAGE_ROOT;
|
|
448
|
-
launchEnv.EVOLCORE_MONITOR_STAGE = stageDir;
|
|
449
|
-
}
|
|
450
|
-
} catch (stageError) {
|
|
451
|
-
// Fall through with the in-place entry.
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
const child = spawn(process.execPath, [...launchArgs, 'restart-monitor'], {
|
|
457
|
-
detached: true,
|
|
458
|
-
stdio: 'ignore',
|
|
459
|
-
windowsHide: process.platform === 'win32',
|
|
460
|
-
env: launchEnv,
|
|
461
|
-
});
|
|
462
|
-
child.unref();
|
|
463
|
-
const pid = child.pid;
|
|
464
|
-
// The daemon is still running here — the monitor is what stops it. So a
|
|
465
|
-
// monitor that dies on load costs the upgrade, not the service; still,
|
|
466
|
-
// report it accurately instead of claiming success.
|
|
467
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
468
|
-
let alive = false;
|
|
469
|
-
try { process.kill(pid, 0); alive = true; } catch (e) { alive = false; }
|
|
470
|
-
if (!alive) {
|
|
471
|
-
emit('error', 'restart-monitor exited during startup; upgrade to ' + remoteVersion + ' not applied');
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
emit('success', 'evolcore upgrade available: ' + localVersion + ' -> ' + remoteVersion + '; restart-monitor started');
|
|
475
|
-
} catch (error) {
|
|
476
|
-
emit('error', 'failed to start restart-monitor: ' + (error && error.message ? error.message : String(error)));
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
main().catch((error) => {
|
|
481
|
-
emit('error', 'upgrade check failed: ' + (error && error.message ? error.message : String(error)));
|
|
482
|
-
});
|
|
336
|
+
fs.writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
337
|
+
const fs = require('node:fs');
|
|
338
|
+
const path = require('node:path');
|
|
339
|
+
const { existsSync, readFileSync } = fs;
|
|
340
|
+
const { spawn } = require('node:child_process');
|
|
341
|
+
|
|
342
|
+
const CLI_PATH = ${JSON.stringify(monitorEntry.entry)};
|
|
343
|
+
const CLI_ARGS = ${JSON.stringify(monitorEntry.args)};
|
|
344
|
+
const PACKAGE_ROOT = ${JSON.stringify(packageRoot)};
|
|
345
|
+
const STAGE_PARENT = ${JSON.stringify(stageParent)};
|
|
346
|
+
const PACKAGE_JSON = ${JSON.stringify(packageJsonPath)};
|
|
347
|
+
const PACKAGE_NAME = ${JSON.stringify(MAIN_PACKAGE_NAME)};
|
|
348
|
+
const DEV_MODE = ${devMode ? 'true' : 'false'};
|
|
349
|
+
|
|
350
|
+
function emit(outcome, text) {
|
|
351
|
+
process.stdout.write(JSON.stringify({ outcome, text, files: [] }));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function compareVersions(a, b) {
|
|
355
|
+
const pa = a.split('-')[0].split('.').map(Number);
|
|
356
|
+
const pb = b.split('-')[0].split('.').map(Number);
|
|
357
|
+
const count = Math.max(pa.length, pb.length);
|
|
358
|
+
for (let i = 0; i < count; i += 1) {
|
|
359
|
+
const av = Number.isFinite(pa[i]) ? pa[i] : 0;
|
|
360
|
+
const bv = Number.isFinite(pb[i]) ? pb[i] : 0;
|
|
361
|
+
if (av < bv) return -1;
|
|
362
|
+
if (av > bv) return 1;
|
|
363
|
+
}
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function latestVersion() {
|
|
368
|
+
const controller = new AbortController();
|
|
369
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
370
|
+
try {
|
|
371
|
+
const response = await fetch('https://registry.npmjs.org/' + encodeURIComponent(PACKAGE_NAME) + '/latest', {
|
|
372
|
+
signal: controller.signal,
|
|
373
|
+
headers: { Accept: 'application/json' },
|
|
374
|
+
});
|
|
375
|
+
if (!response.ok) return null;
|
|
376
|
+
const data = await response.json();
|
|
377
|
+
return typeof data.version === 'string' ? data.version : null;
|
|
378
|
+
} finally {
|
|
379
|
+
clearTimeout(timer);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function main() {
|
|
384
|
+
if (DEV_MODE) {
|
|
385
|
+
emit('noop', 'upgrade check skipped in dev mode');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let localVersion;
|
|
390
|
+
try {
|
|
391
|
+
localVersion = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8')).version;
|
|
392
|
+
} catch {
|
|
393
|
+
emit('error', 'failed to read local ec version');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (typeof localVersion !== 'string' || !localVersion) {
|
|
397
|
+
emit('error', 'failed to read local ec version');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let remoteVersion;
|
|
402
|
+
try {
|
|
403
|
+
remoteVersion = await latestVersion();
|
|
404
|
+
} catch {
|
|
405
|
+
remoteVersion = null;
|
|
406
|
+
}
|
|
407
|
+
if (!remoteVersion) {
|
|
408
|
+
emit('error', 'failed to check evolcore latest version');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (compareVersions(localVersion, remoteVersion) >= 0) {
|
|
412
|
+
emit('noop', 'evolcore is already up to date (' + localVersion + '; latest ' + remoteVersion + ')');
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (!existsSync(CLI_PATH)) {
|
|
416
|
+
emit('error', 'restart-monitor entry not found: ' + CLI_PATH);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Installed mode: copy the monitor out of the package before it upgrades that
|
|
421
|
+
// same package. Mirrors prepareMonitorStage(); kept inline because this file
|
|
422
|
+
// is generated standalone CJS. Any failure degrades to launching in place —
|
|
423
|
+
// losing upgrade isolation beats losing the upgrade.
|
|
424
|
+
let launchArgs = CLI_ARGS;
|
|
425
|
+
const launchEnv = { ...process.env };
|
|
426
|
+
if (!DEV_MODE && STAGE_PARENT) {
|
|
427
|
+
try {
|
|
428
|
+
const stageDir = path.join(STAGE_PARENT, 'upgrade-' + process.pid + '-' + Date.now());
|
|
429
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
430
|
+
fs.cpSync(path.join(PACKAGE_ROOT, 'dist'), path.join(stageDir, 'dist'), { recursive: true });
|
|
431
|
+
if (existsSync(PACKAGE_JSON)) fs.copyFileSync(PACKAGE_JSON, path.join(stageDir, 'package.json'));
|
|
432
|
+
const modulesSource = path.join(PACKAGE_ROOT, 'node_modules');
|
|
433
|
+
if (existsSync(modulesSource)) {
|
|
434
|
+
const staged = path.join(stageDir, 'node_modules');
|
|
435
|
+
try {
|
|
436
|
+
// junction works unelevated on Windows; dir symlink elsewhere.
|
|
437
|
+
fs.symlinkSync(modulesSource, staged, process.platform === 'win32' ? 'junction' : 'dir');
|
|
438
|
+
fs.readdirSync(staged);
|
|
439
|
+
} catch (linkError) {
|
|
440
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
441
|
+
fs.cpSync(modulesSource, staged, { recursive: true });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const stagedEntry = path.join(stageDir, 'dist', 'cli', 'index.js');
|
|
445
|
+
if (existsSync(stagedEntry)) {
|
|
446
|
+
launchArgs = [stagedEntry];
|
|
447
|
+
launchEnv.EVOLCORE_TARGET_PACKAGE_ROOT = PACKAGE_ROOT;
|
|
448
|
+
launchEnv.EVOLCORE_MONITOR_STAGE = stageDir;
|
|
449
|
+
}
|
|
450
|
+
} catch (stageError) {
|
|
451
|
+
// Fall through with the in-place entry.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
const child = spawn(process.execPath, [...launchArgs, 'restart-monitor'], {
|
|
457
|
+
detached: true,
|
|
458
|
+
stdio: 'ignore',
|
|
459
|
+
windowsHide: process.platform === 'win32',
|
|
460
|
+
env: launchEnv,
|
|
461
|
+
});
|
|
462
|
+
child.unref();
|
|
463
|
+
const pid = child.pid;
|
|
464
|
+
// The daemon is still running here — the monitor is what stops it. So a
|
|
465
|
+
// monitor that dies on load costs the upgrade, not the service; still,
|
|
466
|
+
// report it accurately instead of claiming success.
|
|
467
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
468
|
+
let alive = false;
|
|
469
|
+
try { process.kill(pid, 0); alive = true; } catch (e) { alive = false; }
|
|
470
|
+
if (!alive) {
|
|
471
|
+
emit('error', 'restart-monitor exited during startup; upgrade to ' + remoteVersion + ' not applied');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
emit('success', 'evolcore upgrade available: ' + localVersion + ' -> ' + remoteVersion + '; restart-monitor started');
|
|
475
|
+
} catch (error) {
|
|
476
|
+
emit('error', 'failed to start restart-monitor: ' + (error && error.message ? error.message : String(error)));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
main().catch((error) => {
|
|
481
|
+
emit('error', 'upgrade check failed: ' + (error && error.message ? error.message : String(error)));
|
|
482
|
+
});
|
|
483
483
|
`);
|
|
484
484
|
// Remove the pre-Node script left by older installations so it cannot be
|
|
485
485
|
// selected by a stale trigger definition or run manually by accident.
|
|
@@ -3663,7 +3663,8 @@ async function main() {
|
|
|
3663
3663
|
});
|
|
3664
3664
|
if (params.targetAid
|
|
3665
3665
|
&& params.targetAid !== delegation.grant.selfAid
|
|
3666
|
-
&& subject.dataScope !== 'daemon'
|
|
3666
|
+
&& subject.dataScope !== 'daemon'
|
|
3667
|
+
&& !(operation === 'agent.show' && isManagementRole(subject.role))) {
|
|
3667
3668
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3668
3669
|
}
|
|
3669
3670
|
const decision = authorizeOperation({
|
|
@@ -3710,18 +3711,21 @@ async function main() {
|
|
|
3710
3711
|
const taskAid = delegation.grant.selfAid;
|
|
3711
3712
|
const targetAid = sub === 'list'
|
|
3712
3713
|
? undefined
|
|
3713
|
-
: sub === '
|
|
3714
|
-
?
|
|
3715
|
-
: sub === '
|
|
3716
|
-
? (argv[2] && !argv[2].startsWith('--') ? argv[2] :
|
|
3717
|
-
:
|
|
3714
|
+
: sub === 'persona'
|
|
3715
|
+
? taskAid
|
|
3716
|
+
: sub === 'show'
|
|
3717
|
+
? (argv[2] && !argv[2].startsWith('--') ? argv[2] : taskAid)
|
|
3718
|
+
: sub === 'reload'
|
|
3719
|
+
? (argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined)
|
|
3720
|
+
: (argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined);
|
|
3718
3721
|
const operation = sub === 'list' ? 'agent.list'
|
|
3719
3722
|
: sub === 'show' ? 'agent.show'
|
|
3720
|
-
: sub === '
|
|
3721
|
-
: sub === '
|
|
3722
|
-
: sub === '
|
|
3723
|
-
: sub === '
|
|
3724
|
-
: sub === '
|
|
3723
|
+
: sub === 'persona' ? 'agent.persona'
|
|
3724
|
+
: sub === 'new' ? 'agent.create'
|
|
3725
|
+
: sub === 'enable' ? 'agent.enable'
|
|
3726
|
+
: sub === 'disable' ? 'agent.disable'
|
|
3727
|
+
: sub === 'reload' ? 'agent.reload'
|
|
3728
|
+
: sub === 'delete' ? 'agent.delete' : undefined;
|
|
3725
3729
|
if (!operation)
|
|
3726
3730
|
return fail('INVALID_ARGS', `unsupported managed agent command: ${sub}`);
|
|
3727
3731
|
if (sub === 'new' && !argv.includes('--non-interactive')) {
|
|
@@ -3753,7 +3757,7 @@ async function main() {
|
|
|
3753
3757
|
subject,
|
|
3754
3758
|
intent: {
|
|
3755
3759
|
operation,
|
|
3756
|
-
scope: operation === 'agent.show'
|
|
3760
|
+
scope: operation === 'agent.show' || operation === 'agent.persona'
|
|
3757
3761
|
? 'agent'
|
|
3758
3762
|
: operation === 'agent.reload' && targetAid
|
|
3759
3763
|
? 'agent'
|
|
@@ -3809,16 +3813,50 @@ async function main() {
|
|
|
3809
3813
|
},
|
|
3810
3814
|
connection: agent.connection ?? null,
|
|
3811
3815
|
sessions: agent.sessions ?? { active: 0, last_activity: null },
|
|
3812
|
-
paths:
|
|
3813
|
-
|
|
3816
|
+
paths: subject.dataScope === 'daemon'
|
|
3817
|
+
? (agent.paths ?? { config: null, agent_md: null, project: null, data: null })
|
|
3818
|
+
: { config: null, agent_md: null, project: null, data: null },
|
|
3819
|
+
...(subject.dataScope === 'daemon' ? {} : { redacted: true }),
|
|
3814
3820
|
...(agent.createProgress ? { createProgress: agent.createProgress } : {}),
|
|
3815
3821
|
} };
|
|
3816
3822
|
}
|
|
3823
|
+
if (operation === 'agent.persona') {
|
|
3824
|
+
const rawPersonaArgs = argv.slice(2);
|
|
3825
|
+
const hasFormatSuffix = rawPersonaArgs.length >= 2
|
|
3826
|
+
&& rawPersonaArgs.at(-2) === '--format'
|
|
3827
|
+
&& rawPersonaArgs.at(-1) === 'json';
|
|
3828
|
+
const personaArgs = hasFormatSuffix ? rawPersonaArgs.slice(0, -2) : rawPersonaArgs;
|
|
3829
|
+
if (!hasFormatSuffix && rawPersonaArgs.includes('--format')) {
|
|
3830
|
+
return fail('INVALID_ARGS', 'persona 仅支持末尾的 --format json');
|
|
3831
|
+
}
|
|
3832
|
+
if (personaArgs.length === 2 && personaArgs[0] === '--file') {
|
|
3833
|
+
return fail('INVALID_ARGS', '托管 persona --file 必须由 CLI 读取后作为文本提交');
|
|
3834
|
+
}
|
|
3835
|
+
const wantsShow = personaArgs.length === 1 && personaArgs[0] === 'show';
|
|
3836
|
+
const text = personaArgs.length === 2 && personaArgs[0] === '--text'
|
|
3837
|
+
? personaArgs[1]
|
|
3838
|
+
: undefined;
|
|
3839
|
+
if (!wantsShow && !(personaArgs.length === 2 && personaArgs[0] === '--text')) {
|
|
3840
|
+
return fail('INVALID_ARGS', '用法: ec agent persona show | --text <text>');
|
|
3841
|
+
}
|
|
3842
|
+
if (text === undefined && !wantsShow)
|
|
3843
|
+
return fail('INVALID_ARGS', 'persona --text 缺少参数');
|
|
3844
|
+
const { agentPersona } = await import('./cli/agent.js');
|
|
3845
|
+
const result = await agentPersona(taskAid, wantsShow ? { trustedLocal: true } : { text, trustedLocal: true });
|
|
3846
|
+
if (!result.ok)
|
|
3847
|
+
return fail(result.code ?? 'INTERNAL', result.error);
|
|
3848
|
+
return { ok: true, result: {
|
|
3849
|
+
ok: true,
|
|
3850
|
+
aid: taskAid,
|
|
3851
|
+
content: result.content,
|
|
3852
|
+
...(result.saved ? { saved: true } : {}),
|
|
3853
|
+
} };
|
|
3854
|
+
}
|
|
3817
3855
|
if (operation === 'agent.create') {
|
|
3818
3856
|
const aid = argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined;
|
|
3819
3857
|
if (!aid)
|
|
3820
3858
|
return fail('INVALID_ARGS', 'ec agent new requires an AID');
|
|
3821
|
-
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan, parseAgentOwnerArgs } = await import('./cli/agent-create.js');
|
|
3859
|
+
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan, parseAgentOwnerArgs, parseAgentTags } = await import('./cli/agent-create.js');
|
|
3822
3860
|
let ownerOptions;
|
|
3823
3861
|
try {
|
|
3824
3862
|
ownerOptions = parseAgentOwnerArgs(argv);
|
|
@@ -3826,6 +3864,13 @@ async function main() {
|
|
|
3826
3864
|
catch (error) {
|
|
3827
3865
|
return fail('INVALID_ARGS', error instanceof Error ? error.message : String(error));
|
|
3828
3866
|
}
|
|
3867
|
+
let tags;
|
|
3868
|
+
try {
|
|
3869
|
+
tags = parseAgentTags(value('--tags'));
|
|
3870
|
+
}
|
|
3871
|
+
catch (error) {
|
|
3872
|
+
return fail('INVALID_ARGS', error instanceof Error ? error.message : String(error));
|
|
3873
|
+
}
|
|
3829
3874
|
const plan = resolveAgentCreatePlan({
|
|
3830
3875
|
aid,
|
|
3831
3876
|
project: value('--project'),
|
|
@@ -3834,6 +3879,7 @@ async function main() {
|
|
|
3834
3879
|
handoff: value('--handoff'),
|
|
3835
3880
|
name: value('--name'),
|
|
3836
3881
|
description: value('--description'),
|
|
3882
|
+
tags,
|
|
3837
3883
|
force: argv.includes('--force'),
|
|
3838
3884
|
});
|
|
3839
3885
|
const validationError = validateAgentCreatePlan(plan);
|
|
@@ -3890,6 +3936,7 @@ async function main() {
|
|
|
3890
3936
|
owners: plan.owners,
|
|
3891
3937
|
name: plan.name,
|
|
3892
3938
|
description: plan.description,
|
|
3939
|
+
tags: plan.tags,
|
|
3893
3940
|
force: argv.includes('--force'),
|
|
3894
3941
|
};
|
|
3895
3942
|
try {
|