evolcore 0.0.1 → 0.0.3
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 +64 -0
- package/LICENSE +21 -0
- package/MIGRATION-0.5.0.md +378 -0
- package/README.md +318 -14
- package/ROLE_ACCESS_CONTROL.md +174 -0
- package/assets/.env.template +4 -0
- package/bin/ec-safe-output.js +161 -0
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent.js +163 -0
- package/dist/agents/claude-runner.js +2565 -0
- package/dist/agents/codex-app-server-client.js +448 -0
- package/dist/agents/codex-runner.js +2682 -0
- package/dist/agents/gemini-runner.js +666 -0
- package/dist/agents/runner-types.js +75 -0
- package/dist/aun/aid/agentmd.js +216 -0
- package/dist/aun/aid/client.js +132 -0
- package/dist/aun/aid/control-aid.js +91 -0
- package/dist/aun/aid/identity.js +518 -0
- package/dist/aun/aid/index.js +4 -0
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/group-identity.js +10 -0
- package/dist/aun/msg/group-index.js +6 -0
- package/dist/aun/msg/group.js +1231 -0
- package/dist/aun/msg/history.js +123 -0
- package/dist/aun/msg/index.js +5 -0
- package/dist/aun/msg/p2p.js +393 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +137 -0
- package/dist/aun/outbox.js +168 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +25 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/service-proxy.js +225 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +68 -0
- package/dist/channels/aun.js +4164 -0
- package/dist/channels/contact-bind-code.js +134 -0
- package/dist/channels/daemon.js +422 -0
- package/dist/channels/dingtalk.js +1479 -0
- package/dist/channels/feishu.js +1865 -0
- package/dist/channels/qqbot.js +409 -0
- package/dist/channels/wechat.js +817 -0
- package/dist/channels/wecom-card.js +101 -0
- package/dist/channels/wecom-onboarding.js +82 -0
- package/dist/channels/wecom-state.js +191 -0
- package/dist/channels/wecom.js +1157 -0
- package/dist/cli/agent-command.js +642 -0
- package/dist/cli/agent.js +1059 -0
- package/dist/cli/aun-commands.js +2003 -0
- package/dist/cli/bench.js +1228 -0
- package/dist/cli/cli-argv.js +66 -0
- package/dist/cli/code-stats.js +329 -0
- package/dist/cli/command-log.js +82 -0
- package/dist/cli/config-selector.js +69 -0
- package/dist/cli/config.js +261 -0
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +62 -0
- package/dist/cli/daemon-commands.js +2750 -0
- package/dist/cli/fs-command.js +1447 -0
- package/dist/cli/handoff-command.js +302 -0
- package/dist/cli/help.js +35 -0
- package/dist/cli/index.js +374 -0
- package/dist/cli/init-channel.js +1372 -0
- package/dist/cli/init.js +590 -0
- package/dist/cli/link-rules.js +240 -0
- package/dist/cli/model.js +591 -0
- package/dist/cli/net-check.js +723 -0
- package/dist/cli/queue-command.js +150 -0
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +344 -0
- package/dist/cli/restart-monitor.js +480 -0
- package/dist/cli/stats.js +609 -0
- package/dist/cli/task-context.js +80 -0
- package/dist/cli/trigger-command.js +545 -0
- package/dist/cli/version.js +93 -0
- package/dist/cli/watch-logs.js +33 -0
- package/dist/cli/watch-msg.js +673 -0
- package/dist/config/boot-log.js +266 -0
- package/dist/config/builtin-role-templates.js +42 -0
- package/dist/config/builtin-roles.js +91 -0
- package/dist/config/config-batch-get.js +11 -0
- package/dist/config/config-field-policy.js +261 -0
- package/dist/config/config-manager.js +1120 -0
- package/dist/config/config-operation-service.js +384 -0
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-book-store.js +454 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +224 -0
- package/dist/config/contact-operation-service.js +110 -0
- package/dist/config/gateway-config.js +858 -0
- package/dist/config/lifecycle.js +17 -0
- package/dist/config/mention-mode.js +27 -0
- package/dist/config/merge.js +161 -0
- package/dist/config/owner-policy.js +4 -0
- package/dist/config/peer-role-resolver.js +218 -0
- package/dist/config/resolved-config-op.js +483 -0
- package/dist/config/role-config-v4-startup.js +32 -0
- package/dist/config/role-config-v5-startup.js +27 -0
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-schema.js +105 -0
- package/dist/config/role-service.js +156 -0
- package/dist/config/role-store.js +215 -0
- package/dist/config/roles.js +64 -0
- package/dist/config/schema-registry.js +154 -0
- package/dist/config/snapshot.js +598 -0
- package/dist/config-store.js +501 -0
- package/dist/core/auth/agent-delegation.js +111 -0
- package/dist/core/auth/auth-gateway.js +166 -0
- package/dist/core/auth/authenticated-actor.js +6 -0
- package/dist/core/auth/authorization-audit.js +119 -0
- package/dist/core/auth/operation-authorizer.js +720 -0
- package/dist/core/auth/operation-catalog.js +731 -0
- package/dist/core/baseagent-loader.js +54 -0
- package/dist/core/bootstrap-service.js +175 -0
- package/dist/core/capability/capability-manager.js +316 -0
- package/dist/core/capability/providers/claude-capability-provider.js +176 -0
- package/dist/core/capability/providers/codex-capability-provider.js +148 -0
- package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
- package/dist/core/capability/types.js +27 -0
- package/dist/core/causation/audit.js +103 -0
- package/dist/core/causation/aun-association.js +111 -0
- package/dist/core/causation/context.js +93 -0
- package/dist/core/causation/index.js +4 -0
- package/dist/core/causation/types.js +2 -0
- package/dist/core/channel-loader.js +277 -0
- package/dist/core/command/agent-control.js +616 -0
- package/dist/core/command/cli-intent-parser.js +225 -0
- package/dist/core/command/command-handler.js +1733 -0
- package/dist/core/command/connect-menu.js +374 -0
- package/dist/core/command/menu-handler.js +3452 -0
- package/dist/core/command/menu-protocol.js +247 -0
- package/dist/core/command/role-menu.js +1623 -0
- package/dist/core/command/slash-gate.js +148 -0
- package/dist/core/command/slash-handler.js +3066 -0
- package/dist/core/daemon-file-cache.js +222 -0
- package/dist/core/event-bus.js +32 -0
- package/dist/core/event-catalog.js +810 -0
- package/dist/core/evolagent-registry.js +545 -0
- package/dist/core/evolagent.js +342 -0
- package/dist/core/handoff/dispatcher.js +229 -0
- package/dist/core/handoff/mutex.js +46 -0
- package/dist/core/handoff/runtime.js +324 -0
- package/dist/core/handoff/store.js +537 -0
- package/dist/core/handoff/types.js +12 -0
- package/dist/core/inference/text-inference.js +173 -0
- package/dist/core/interaction-registration.js +10 -0
- package/dist/core/interaction-router.js +278 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/im-renderer.js +659 -0
- package/dist/core/message/items-formatter.js +76 -0
- package/dist/core/message/logical-queue-bridge.js +123 -0
- package/dist/core/message/message-bridge.js +874 -0
- package/dist/core/message/message-cache.js +56 -0
- package/dist/core/message/message-log.js +339 -0
- package/dist/core/message/message-processor.js +4 -0
- package/dist/core/message/message-queue.js +1446 -0
- package/dist/core/message/message-utils.js +76 -0
- package/dist/core/message/peer-mode.js +105 -0
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +33 -0
- package/dist/core/message/response-engine.js +4020 -0
- package/dist/core/message/response-snapshot.js +83 -0
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +139 -0
- package/dist/core/message/stream-idle-monitor.js +124 -0
- package/dist/core/model/config-scope.js +162 -0
- package/dist/core/model/field-scope.js +78 -0
- package/dist/core/model/model-catalog.js +227 -0
- package/dist/core/model/model-diagnostics.js +182 -0
- package/dist/core/model/model-permission.js +90 -0
- package/dist/core/permission/approval-gateway.js +1017 -0
- package/dist/core/permission/ec-command-parser.js +347 -0
- package/dist/core/permission/execution-sandbox.js +16 -0
- package/dist/core/permission/index.js +6 -0
- package/dist/core/permission/mode.js +24 -0
- package/dist/core/permission/sandbox-runtime.js +265 -0
- package/dist/core/permission/tool-policy.js +1019 -0
- package/dist/core/permission/unix-socket-policy.js +99 -0
- package/dist/core/protected-paths.js +332 -0
- package/dist/core/relation/peer-identity.js +222 -0
- package/dist/core/relation/peer-key.js +1 -0
- package/dist/core/role/runtime-policy.js +141 -0
- package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
- package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
- package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
- package/dist/core/session/session-file-adapter.js +7 -0
- package/dist/core/session/session-file-health.js +45 -0
- package/dist/core/session/session-fs-store.js +273 -0
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +1643 -0
- package/dist/core/session/session-mapper.js +100 -0
- package/dist/core/session/session-renew.js +314 -0
- package/dist/core/session/session-title.js +128 -0
- package/dist/core/session/session-turn-coordinator.js +205 -0
- package/dist/core/session/session-turns.js +67 -0
- package/dist/core/system-channels.js +29 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/group-rules-sync.js +345 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/kit-renderer.js +359 -0
- package/dist/eck/manifest-engine.js +446 -0
- package/dist/eck/message-renderer.js +199 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +2926 -4
- package/dist/ipc.js +777 -0
- package/dist/paths.js +262 -0
- package/dist/product.js +18 -0
- package/dist/response-system/context-builder.js +71 -0
- package/dist/response-system/coordinator.js +117 -0
- package/dist/response-system/decision-executor.js +86 -0
- package/dist/response-system/engines/v1/index.js +21 -0
- package/dist/response-system/engines/v1/interactive-flow.js +27 -0
- package/dist/response-system/engines/v1/proactive-flow.js +137 -0
- package/dist/response-system/engines/v1/types.js +1 -0
- package/dist/response-system/extensions.js +41 -0
- package/dist/response-system/index.js +6 -0
- package/dist/response-system/modes/index.js +7 -0
- package/dist/response-system/modes/single-session/index.js +72 -0
- package/dist/response-system/queues/fifo-queue.js +44 -0
- package/dist/response-system/queues/index.js +6 -0
- package/dist/response-system/queues/lifo-queue.js +42 -0
- package/dist/response-system/queues/priority-queue.js +63 -0
- package/dist/response-system/registry.js +97 -0
- package/dist/response-system/resolver.js +37 -0
- package/dist/response-system/selector.js +23 -0
- package/dist/response-system/types.js +7 -0
- package/dist/stats/billing.js +163 -0
- package/dist/stats/budget.js +93 -0
- package/dist/stats/db.js +403 -0
- package/dist/stats/eck-vars.js +89 -0
- package/dist/stats/index.js +11 -0
- package/dist/stats/normalizer.js +80 -0
- package/dist/stats/price-resolver.js +138 -0
- package/dist/stats/query.js +763 -0
- package/dist/stats/role-budget.js +168 -0
- package/dist/stats/writer.js +151 -0
- package/dist/trigger/anomaly-store.js +258 -0
- package/dist/trigger/audit.js +152 -0
- package/dist/trigger/event-source.js +119 -0
- package/dist/trigger/feedback.js +685 -0
- package/dist/trigger/history.js +290 -0
- package/dist/trigger/manager.js +294 -0
- package/dist/trigger/parser.js +595 -0
- package/dist/trigger/patch.js +155 -0
- package/dist/trigger/scheduler.js +1602 -0
- package/dist/trigger/script-executor.js +155 -0
- package/dist/trigger/state.js +145 -0
- package/dist/trigger/types.js +1 -0
- package/dist/trigger/validation.js +634 -0
- package/dist/types.js +12 -0
- package/dist/utils/aid-bind.js +313 -0
- package/dist/utils/atomic-write.js +95 -0
- package/dist/utils/avatar-upload.js +123 -0
- package/dist/utils/cross-platform.js +297 -0
- package/dist/utils/ecweb-utils.js +73 -0
- package/dist/utils/error-dict.json +153 -0
- package/dist/utils/error-utils.js +349 -0
- package/dist/utils/instance-registry.js +444 -0
- package/dist/utils/locale.js +21 -0
- package/dist/utils/log-writer.js +270 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/markdown-to-plain-text.js +20 -0
- package/dist/utils/media-cache.js +274 -0
- package/dist/utils/model-prices.jsonl +20 -0
- package/dist/utils/npm-ops.js +210 -0
- package/dist/utils/process-introspect.js +133 -0
- package/dist/utils/process-tree-stats.js +271 -0
- package/dist/utils/project-path.js +74 -0
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stats.js +410 -0
- package/dist/utils/system-memory.js +62 -0
- package/dist/utils/tool-summary.js +284 -0
- package/dist/utils/welcome.js +268 -0
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +66 -0
- package/kits/docs/aun/CHEATSHEET.md +19 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/aun.md +65 -0
- package/kits/docs/channels/feishu.md +56 -0
- package/kits/docs/context-assembly.md +366 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolcore/INDEX.md +68 -0
- package/kits/docs/evolcore/agent.md +77 -0
- package/kits/docs/evolcore/aid.md +52 -0
- package/kits/docs/evolcore/config.md +149 -0
- package/kits/docs/evolcore/contact.md +57 -0
- package/kits/docs/evolcore/ctl.md +46 -0
- package/kits/docs/evolcore/event.md +216 -0
- package/kits/docs/evolcore/fs-architecture.md +1215 -0
- package/kits/docs/evolcore/fs.md +110 -0
- package/kits/docs/evolcore/group-fs.md +17 -0
- package/kits/docs/evolcore/group-rules.md +226 -0
- package/kits/docs/evolcore/group.md +150 -0
- package/kits/docs/evolcore/model.md +50 -0
- package/kits/docs/evolcore/msg.md +136 -0
- package/kits/docs/evolcore/response.md +75 -0
- package/kits/docs/evolcore/rpc.md +37 -0
- package/kits/docs/evolcore/self-summary.md +29 -0
- package/kits/docs/evolcore/stats.md +83 -0
- package/kits/docs/evolcore/storage.md +50 -0
- package/kits/docs/evolcore/trigger.md +539 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +23 -0
- package/kits/docs/identity/identity-tools.md +26 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/docs/prompt-loading-architecture.md +266 -0
- package/kits/docs/venues/aun-group.md +45 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +25 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.auxiliary.json +43 -0
- package/kits/eck_manifest.json +203 -0
- package/kits/eck_message_manifest.json +63 -0
- package/kits/migrations/README-role-config-v4.md +32 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
- package/kits/migrations/migrate-role-config-v4.mjs +623 -0
- package/kits/migrations/migrate-role-config-v5.mjs +346 -0
- package/kits/migrations/rename-config-file.mjs +99 -0
- package/kits/rules/01-overview.md +142 -0
- package/kits/rules/02-navigation.md +76 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +59 -0
- package/kits/rules/05-venue.md +44 -0
- package/kits/rules/06-channel.md +59 -0
- package/kits/schemas/_meta.json +32 -0
- package/kits/schemas/agent-config.schema.1.json +177 -0
- package/kits/schemas/agent-config.schema.2.json +239 -0
- package/kits/schemas/agent-config.schema.3.json +119 -0
- package/kits/schemas/agent-config.schema.4.json +208 -0
- package/kits/schemas/agent-config.schema.5.json +326 -0
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/contact-book.schema.1.json +36 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/daemon.schema.1.json +90 -0
- package/kits/schemas/defaults.schema.1.json +81 -0
- package/kits/schemas/menu-exec-schema-commands.md +208 -0
- package/kits/schemas/migrations/README.md +28 -0
- package/kits/schemas/relation-config.schema.1.json +158 -0
- package/kits/schemas/relation-config.schema.2.json +73 -0
- package/kits/schemas/relation-config.schema.3.json +50 -0
- package/kits/schemas/relation-config.schema.4.json +47 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/role-config.schema.1.json +201 -0
- package/kits/schemas/role-registry.schema.1.json +35 -0
- package/kits/schemas/single-session.schema.1.json +31 -0
- package/kits/templates/bootstrap-welcome.md +15 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
- package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/roles/admin.json +9 -0
- package/kits/templates/roles/member.json +41 -0
- package/kits/templates/roles/owner.json +9 -0
- package/kits/templates/roles/visitor.json +40 -0
- package/kits/templates/system-fragments/baseagent.md +14 -0
- package/kits/templates/system-fragments/bootstrap.md +16 -0
- package/kits/templates/system-fragments/channel.md +48 -0
- package/kits/templates/system-fragments/commands.md +28 -0
- package/kits/templates/system-fragments/identity.md +11 -0
- package/kits/templates/system-fragments/relation.md +19 -0
- package/kits/templates/system-fragments/session.md +53 -0
- package/kits/templates/system-fragments/venue.md +31 -0
- package/package.json +50 -15
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,1231 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { TextDecoder } from 'util';
|
|
5
|
+
import { createShortConnection } from '../rpc/index.js';
|
|
6
|
+
import { getAidStore, loadClient, SLOT } from '../aid/store.js';
|
|
7
|
+
import { uploadFileAndBuildPayload } from './upload.js';
|
|
8
|
+
import { daemonTaskSendIpcTimeoutMs } from './p2p.js';
|
|
9
|
+
import { checkGroupIndex, getGroupIndex } from './group-index.js';
|
|
10
|
+
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from '../../core/message/message-log.js';
|
|
11
|
+
import { chatDirPath } from '../../core/session/session-fs-store.js';
|
|
12
|
+
import { readBestTaskRuntimeContext } from '../../cli/task-context.js';
|
|
13
|
+
import { ipcQuery } from '../../ipc.js';
|
|
14
|
+
import { resolvePaths } from '../../paths.js';
|
|
15
|
+
import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
|
|
16
|
+
function buildGroupPayload(body) {
|
|
17
|
+
return body.mode === 'text' ? { type: 'text', text: body.text } : { ...body.payload };
|
|
18
|
+
}
|
|
19
|
+
function groupMessageLogContent(body) {
|
|
20
|
+
return body.mode === 'text' ? body.text
|
|
21
|
+
: body.mode === 'file' ? `[file] ${body.filePath}`
|
|
22
|
+
: '[payload]';
|
|
23
|
+
}
|
|
24
|
+
function isCurrentGroupRuntime(args, runtimeContext) {
|
|
25
|
+
return runtimeContext?.channel === 'aun'
|
|
26
|
+
&& runtimeContext.chatType === 'group'
|
|
27
|
+
&& runtimeContext.selfAid === args.from
|
|
28
|
+
&& runtimeContext.channelId === args.groupId;
|
|
29
|
+
}
|
|
30
|
+
function groupThreadId(args, payload, runtimeContext) {
|
|
31
|
+
const payloadThread = typeof payload?.thread_id === 'string' && payload.thread_id.trim()
|
|
32
|
+
? payload.thread_id.trim()
|
|
33
|
+
: undefined;
|
|
34
|
+
return payloadThread || args.thread || (isCurrentGroupRuntime(args, runtimeContext) ? runtimeContext?.threadId : undefined);
|
|
35
|
+
}
|
|
36
|
+
function applyGroupRoutingPayloadFields(payload, args, runtimeContext) {
|
|
37
|
+
const threadId = groupThreadId(args, payload, runtimeContext);
|
|
38
|
+
if (threadId && typeof payload.thread_id !== 'string')
|
|
39
|
+
payload.thread_id = threadId;
|
|
40
|
+
}
|
|
41
|
+
function groupSendLogMetadata(args, payload, runtimeContext) {
|
|
42
|
+
const classified = payload ? classifyAunPayloadForLog(payload) : undefined;
|
|
43
|
+
return {
|
|
44
|
+
content: classified?.content ?? groupMessageLogContent(args.body),
|
|
45
|
+
sessionId: isCurrentGroupRuntime(args, runtimeContext) ? runtimeContext?.sessionId : undefined,
|
|
46
|
+
threadId: groupThreadId(args, payload, runtimeContext) ?? null,
|
|
47
|
+
msgType: classified?.msgType,
|
|
48
|
+
payloadType: classified?.payloadType,
|
|
49
|
+
payloadSummary: classified?.payloadSummary,
|
|
50
|
+
source: (runtimeContext ? 'msg' : 'cli'),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function appendGroupSendOutboundLog(args, messageId, payload, runtimeContext) {
|
|
54
|
+
const log = groupSendLogMetadata(args, payload, runtimeContext);
|
|
55
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', args.groupId, args.from);
|
|
56
|
+
fs.mkdirSync(chatDir, { recursive: true });
|
|
57
|
+
appendMessageLog(chatDir, buildOutboundEntry({
|
|
58
|
+
from: args.from,
|
|
59
|
+
to: args.groupId,
|
|
60
|
+
sessionId: log.sessionId,
|
|
61
|
+
threadId: log.threadId,
|
|
62
|
+
chatType: 'group',
|
|
63
|
+
groupId: args.groupId,
|
|
64
|
+
msgId: messageId,
|
|
65
|
+
content: log.content,
|
|
66
|
+
encrypt: args.encrypt === true,
|
|
67
|
+
msgType: log.msgType,
|
|
68
|
+
payloadType: log.payloadType,
|
|
69
|
+
payloadSummary: log.payloadSummary,
|
|
70
|
+
source: log.source,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
async function tryDaemonGroupSend(args, payload, runtimeContext, file) {
|
|
74
|
+
const delegatedTask = !!process.env.EVOLCORE_SESSION_ID
|
|
75
|
+
|| !!process.env[AGENT_DELEGATION_TOKEN_ENV];
|
|
76
|
+
if (!runtimeContext) {
|
|
77
|
+
return delegatedTask
|
|
78
|
+
? { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task runtime context is unavailable' }
|
|
79
|
+
: null;
|
|
80
|
+
}
|
|
81
|
+
if (!runtimeContext.selfAid || runtimeContext.selfAid !== args.from) {
|
|
82
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task sender does not match the delegated agent' };
|
|
83
|
+
}
|
|
84
|
+
if (!runtimeContext.sessionId || !runtimeContext.messageId) {
|
|
85
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task runtime context is incomplete' };
|
|
86
|
+
}
|
|
87
|
+
if (args.slotId || args.aunPath) {
|
|
88
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task sends cannot override the daemon AUN connection' };
|
|
89
|
+
}
|
|
90
|
+
const thread = groupThreadId(args, payload, runtimeContext);
|
|
91
|
+
const response = await ipcQuery(resolvePaths().socket, {
|
|
92
|
+
type: 'aun-msg-send',
|
|
93
|
+
aid: args.from,
|
|
94
|
+
to: args.groupId,
|
|
95
|
+
scope: 'group',
|
|
96
|
+
...(payload ? { payload } : { file }),
|
|
97
|
+
mentions: args.mentions,
|
|
98
|
+
encrypt: args.encrypt === true,
|
|
99
|
+
thread,
|
|
100
|
+
returnPolicy: args.returnPolicy,
|
|
101
|
+
originSessionId: runtimeContext.sessionId,
|
|
102
|
+
originMessageId: runtimeContext.messageId,
|
|
103
|
+
delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
|
|
104
|
+
log: groupSendLogMetadata(args, payload, runtimeContext),
|
|
105
|
+
}, daemonTaskSendIpcTimeoutMs(!!file));
|
|
106
|
+
return response ?? {
|
|
107
|
+
ok: false,
|
|
108
|
+
code: 'AUN_DAEMON_UNAVAILABLE',
|
|
109
|
+
error: 'EvolCore daemon did not complete the delegated AUN group send',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function daemonGroupSendResult(args, payload, runtimeContext, result) {
|
|
113
|
+
if (!result.ok) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: result.error || 'daemon AUN group send failed',
|
|
117
|
+
code: result.code,
|
|
118
|
+
handoff_id: result.handoff_id,
|
|
119
|
+
target_session_id: result.target_session_id,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (result.handoff_id) {
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
group_id: result.group_id || args.groupId,
|
|
126
|
+
message_id: result.message_id,
|
|
127
|
+
handoff_id: result.handoff_id,
|
|
128
|
+
target_session_id: result.target_session_id,
|
|
129
|
+
status: result.status,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (!result.group_id || !result.message) {
|
|
133
|
+
return { ok: false, error: 'daemon AUN group send returned no group message' };
|
|
134
|
+
}
|
|
135
|
+
const messageId = result.message_id
|
|
136
|
+
?? (typeof result.message.message_id === 'string' ? result.message.message_id : undefined);
|
|
137
|
+
if (messageId && !result.log_written) {
|
|
138
|
+
appendGroupSendOutboundLog(args, messageId, payload, runtimeContext);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
ok: true,
|
|
142
|
+
group_id: result.group_id || args.groupId,
|
|
143
|
+
message: result.message,
|
|
144
|
+
event: result.event,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export async function groupSend(args) {
|
|
148
|
+
const runtimeContext = await readBestTaskRuntimeContext();
|
|
149
|
+
if (args.body.mode !== 'file') {
|
|
150
|
+
const payload = buildGroupPayload(args.body);
|
|
151
|
+
applyGroupRoutingPayloadFields(payload, args, runtimeContext);
|
|
152
|
+
const daemonResult = await tryDaemonGroupSend(args, payload, runtimeContext);
|
|
153
|
+
if (daemonResult)
|
|
154
|
+
return daemonGroupSendResult(args, payload, runtimeContext, daemonResult);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const daemonResult = await tryDaemonGroupSend(args, undefined, runtimeContext, {
|
|
158
|
+
filePath: path.resolve(args.body.filePath),
|
|
159
|
+
as: args.body.as,
|
|
160
|
+
contentType: args.body.contentType,
|
|
161
|
+
text: args.body.text,
|
|
162
|
+
transcript: args.body.transcript,
|
|
163
|
+
});
|
|
164
|
+
if (daemonResult) {
|
|
165
|
+
const payload = { type: args.body.as || 'file' };
|
|
166
|
+
applyGroupRoutingPayloadFields(payload, args, runtimeContext);
|
|
167
|
+
return daemonGroupSendResult(args, payload, runtimeContext, daemonResult);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
171
|
+
try {
|
|
172
|
+
let payload;
|
|
173
|
+
switch (args.body.mode) {
|
|
174
|
+
case 'text':
|
|
175
|
+
payload = { type: 'text', text: args.body.text };
|
|
176
|
+
break;
|
|
177
|
+
case 'payload':
|
|
178
|
+
payload = args.body.payload;
|
|
179
|
+
break;
|
|
180
|
+
case 'file': {
|
|
181
|
+
const built = await uploadFileAndBuildPayload(conn, args.from, args.body.filePath, {
|
|
182
|
+
as: args.body.as,
|
|
183
|
+
contentType: args.body.contentType,
|
|
184
|
+
text: args.body.text,
|
|
185
|
+
transcript: args.body.transcript,
|
|
186
|
+
onProgress: args.onUploadProgress,
|
|
187
|
+
});
|
|
188
|
+
payload = built.payload;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (args.mentions && args.mentions.length > 0) {
|
|
193
|
+
payload.mentions = args.mentions;
|
|
194
|
+
}
|
|
195
|
+
applyGroupRoutingPayloadFields(payload, args, runtimeContext);
|
|
196
|
+
const sendParams = { group_id: args.groupId, payload };
|
|
197
|
+
sendParams.encrypt = args.encrypt === true;
|
|
198
|
+
const result = await conn.call('group.send', sendParams);
|
|
199
|
+
const messageId = typeof result?.message?.message_id === 'string' ? result.message.message_id : undefined;
|
|
200
|
+
if (messageId)
|
|
201
|
+
appendGroupSendOutboundLog(args, messageId, payload, runtimeContext);
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
group_id: result?.group_id ?? args.groupId,
|
|
205
|
+
message: result?.message,
|
|
206
|
+
event: result?.event,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
return formatRpcError(e);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
await conn.close();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export async function groupPull(args) {
|
|
217
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
218
|
+
try {
|
|
219
|
+
const params = { group_id: args.groupId };
|
|
220
|
+
if (args.afterSeq !== undefined)
|
|
221
|
+
params.after_message_seq = args.afterSeq;
|
|
222
|
+
if (args.limit !== undefined)
|
|
223
|
+
params.limit = args.limit;
|
|
224
|
+
const result = await conn.call('group.pull', params);
|
|
225
|
+
return {
|
|
226
|
+
ok: true,
|
|
227
|
+
group_id: result?.group_id ?? args.groupId,
|
|
228
|
+
messages: result?.messages ?? [],
|
|
229
|
+
latest_message_seq: result?.latest_message_seq ?? 0,
|
|
230
|
+
has_more: !!result?.has_more,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
return formatRpcError(e);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await conn.close();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export async function groupAck(args) {
|
|
241
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
242
|
+
try {
|
|
243
|
+
const result = await conn.call('group.ack', { group_id: args.groupId, msg_seq: args.seq });
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
group_id: result?.group_id ?? args.groupId,
|
|
247
|
+
ack_seq: result?.cursor ?? result?.ack_seq ?? args.seq,
|
|
248
|
+
latest_message_seq: result?.latest_message_seq,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
return formatRpcError(e);
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
await conn.close();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export async function groupCreate(args) {
|
|
259
|
+
const store = await getAidStore({ slotId: args.slotId ?? SLOT.cli, aunPath: args.aunPath });
|
|
260
|
+
const conn = await loadClient(store, args.from);
|
|
261
|
+
try {
|
|
262
|
+
await conn.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
263
|
+
const params = { name: args.name };
|
|
264
|
+
if (args.groupId)
|
|
265
|
+
params.group_id = args.groupId;
|
|
266
|
+
const groupName = groupNameFromGroupId(args.groupId);
|
|
267
|
+
if (groupName)
|
|
268
|
+
params.group_name = groupName;
|
|
269
|
+
if (args.visibility)
|
|
270
|
+
params.visibility = args.visibility;
|
|
271
|
+
if (args.description)
|
|
272
|
+
params.description = args.description;
|
|
273
|
+
if (args.joinMode)
|
|
274
|
+
params.join_mode = args.joinMode;
|
|
275
|
+
const result = await conn.createGroup(params);
|
|
276
|
+
return { ok: true, group: result?.group ?? result };
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
return formatRpcError(e);
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
await conn.close();
|
|
283
|
+
try {
|
|
284
|
+
store.close();
|
|
285
|
+
}
|
|
286
|
+
catch { }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export async function groupInfo(args) {
|
|
290
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
291
|
+
try {
|
|
292
|
+
const params = { group_id: args.groupId };
|
|
293
|
+
if (args.required?.length)
|
|
294
|
+
params.required = args.required;
|
|
295
|
+
const result = await conn.call('group.get_info', params);
|
|
296
|
+
const raw = result?.group ?? result;
|
|
297
|
+
const found = result?.found !== false && !!raw && !!(raw.group_aid ?? raw.group_id);
|
|
298
|
+
return { ok: true, found, ...(found ? { group: normalizeGroupInfo(raw) } : {}) };
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
return formatRpcError(e);
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
await conn.close();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
export async function groupList(args) {
|
|
308
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
309
|
+
try {
|
|
310
|
+
const params = {};
|
|
311
|
+
if (args.size !== undefined)
|
|
312
|
+
params.size = args.size;
|
|
313
|
+
if (args.page !== undefined)
|
|
314
|
+
params.page = args.page;
|
|
315
|
+
if (args.cursor !== undefined)
|
|
316
|
+
params.cursor = args.cursor;
|
|
317
|
+
const result = await conn.call('group.list_my', params);
|
|
318
|
+
const items = (result?.items ?? []).map(normalizeGroupInfo);
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
items,
|
|
322
|
+
total: result?.total ?? 0,
|
|
323
|
+
page: result?.page ?? args.page ?? 1,
|
|
324
|
+
size: result?.size ?? args.size ?? items.length,
|
|
325
|
+
nextCursor: result?.next_cursor ?? result?.nextCursor ?? result?.cursor ?? null,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
return formatRpcError(e);
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
await conn.close();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export async function groupUpdate(args) {
|
|
336
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
337
|
+
try {
|
|
338
|
+
const params = { group_id: args.groupId };
|
|
339
|
+
if (args.name !== undefined)
|
|
340
|
+
params.name = args.name;
|
|
341
|
+
if (args.description !== undefined)
|
|
342
|
+
params.description = args.description;
|
|
343
|
+
const result = await conn.call('group.update', params);
|
|
344
|
+
return { ok: true, group: result?.group };
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
return formatRpcError(e);
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
await conn.close();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
export async function groupDissolve(args) {
|
|
354
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
355
|
+
try {
|
|
356
|
+
const result = await conn.call('group.dissolve', { group_id: args.groupId });
|
|
357
|
+
return {
|
|
358
|
+
ok: true,
|
|
359
|
+
group_id: result?.group_id ?? args.groupId,
|
|
360
|
+
status: result?.status ?? 'dissolved',
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
return formatRpcError(e);
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
await conn.close();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export async function groupSuspend(args) {
|
|
371
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
372
|
+
try {
|
|
373
|
+
const result = await conn.call('group.suspend', { group_id: args.groupId });
|
|
374
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
375
|
+
}
|
|
376
|
+
catch (e) {
|
|
377
|
+
return formatRpcError(e);
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
await conn.close();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
export async function groupResume(args) {
|
|
384
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
385
|
+
try {
|
|
386
|
+
const result = await conn.call('group.resume', { group_id: args.groupId });
|
|
387
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
388
|
+
}
|
|
389
|
+
catch (e) {
|
|
390
|
+
return formatRpcError(e);
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
await conn.close();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
export async function groupJoin(args) {
|
|
397
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
398
|
+
try {
|
|
399
|
+
const params = { group_id: args.groupId };
|
|
400
|
+
if (args.message)
|
|
401
|
+
params.message = args.message;
|
|
402
|
+
if (args.answer)
|
|
403
|
+
params.answer = args.answer;
|
|
404
|
+
const result = await conn.call('group.request_join', params);
|
|
405
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
406
|
+
}
|
|
407
|
+
catch (e) {
|
|
408
|
+
return formatRpcError(e);
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
await conn.close();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
export async function groupLeave(args) {
|
|
415
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
416
|
+
try {
|
|
417
|
+
const result = await conn.call('group.leave', { group_id: args.groupId });
|
|
418
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
419
|
+
}
|
|
420
|
+
catch (e) {
|
|
421
|
+
return formatRpcError(e);
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
await conn.close();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
export async function groupInvite(args) {
|
|
428
|
+
if (args.members.length === 0) {
|
|
429
|
+
return { ok: false, error: '至少需要一个成员 AID' };
|
|
430
|
+
}
|
|
431
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
432
|
+
const added = [];
|
|
433
|
+
const failed = [];
|
|
434
|
+
try {
|
|
435
|
+
for (const memberAid of args.members) {
|
|
436
|
+
try {
|
|
437
|
+
await conn.call('group.add_member', { group_id: args.groupId, aid: memberAid });
|
|
438
|
+
added.push(memberAid);
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
failed.push({ aid: memberAid, error: String(e?.message ?? e) });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return { ok: true, group_id: args.groupId, added, failed };
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
await conn.close();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
export async function groupKick(args) {
|
|
451
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
452
|
+
try {
|
|
453
|
+
const result = await conn.call('group.kick', { group_id: args.groupId, aid: args.memberAid });
|
|
454
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
return formatRpcError(e);
|
|
458
|
+
}
|
|
459
|
+
finally {
|
|
460
|
+
await conn.close();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
export async function groupMembers(args) {
|
|
464
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
465
|
+
try {
|
|
466
|
+
const params = { group_id: args.groupId };
|
|
467
|
+
if (args.page !== undefined)
|
|
468
|
+
params.page = args.page;
|
|
469
|
+
if (args.size !== undefined)
|
|
470
|
+
params.size = args.size;
|
|
471
|
+
const result = await conn.call('group.get_members', params);
|
|
472
|
+
return {
|
|
473
|
+
ok: true,
|
|
474
|
+
members: result?.members ?? [],
|
|
475
|
+
total: result?.total ?? 0,
|
|
476
|
+
page: result?.page ?? 1,
|
|
477
|
+
size: result?.size ?? 50,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
catch (e) {
|
|
481
|
+
return formatRpcError(e);
|
|
482
|
+
}
|
|
483
|
+
finally {
|
|
484
|
+
await conn.close();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
export async function groupOnline(args) {
|
|
488
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
489
|
+
try {
|
|
490
|
+
const result = await conn.call('group.get_online_members', { group_id: args.groupId });
|
|
491
|
+
return {
|
|
492
|
+
ok: true,
|
|
493
|
+
group_id: result?.group_id ?? args.groupId,
|
|
494
|
+
members: result?.members ?? [],
|
|
495
|
+
online_count: result?.online_count ?? 0,
|
|
496
|
+
total: result?.total ?? 0,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
return formatRpcError(e);
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
await conn.close();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
export async function groupSetRole(args) {
|
|
507
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
508
|
+
try {
|
|
509
|
+
const result = await conn.call('group.set_role', {
|
|
510
|
+
group_id: args.groupId,
|
|
511
|
+
aid: args.memberAid,
|
|
512
|
+
role: args.role,
|
|
513
|
+
});
|
|
514
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
515
|
+
}
|
|
516
|
+
catch (e) {
|
|
517
|
+
return formatRpcError(e);
|
|
518
|
+
}
|
|
519
|
+
finally {
|
|
520
|
+
await conn.close();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
export async function groupTransferOwner(args) {
|
|
524
|
+
const store = await getAidStore({ slotId: args.slotId ?? SLOT.cli, aunPath: args.aunPath });
|
|
525
|
+
const conn = await loadClient(store, args.from);
|
|
526
|
+
try {
|
|
527
|
+
await conn.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
528
|
+
const start = await conn.startGroupTransfer({
|
|
529
|
+
group_id: args.groupId,
|
|
530
|
+
new_owner: args.newOwner,
|
|
531
|
+
}, { aidStore: store });
|
|
532
|
+
if (!ownerTransferNeedsCompletion(start)) {
|
|
533
|
+
return {
|
|
534
|
+
ok: true,
|
|
535
|
+
group_id: args.groupId,
|
|
536
|
+
data: { status: 'completed', auto_completed: false, start: normalizeRecord(start) },
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const newOwnerConn = await loadClient(store, args.newOwner);
|
|
541
|
+
try {
|
|
542
|
+
await newOwnerConn.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
543
|
+
const complete = await newOwnerConn.completeGroupTransfer({
|
|
544
|
+
group_id: args.groupId,
|
|
545
|
+
}, { aidStore: store });
|
|
546
|
+
return {
|
|
547
|
+
ok: true,
|
|
548
|
+
group_id: args.groupId,
|
|
549
|
+
data: {
|
|
550
|
+
status: 'completed',
|
|
551
|
+
auto_completed: true,
|
|
552
|
+
start: normalizeRecord(start),
|
|
553
|
+
complete: normalizeRecord(complete),
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
await newOwnerConn.close();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch (completeError) {
|
|
562
|
+
return {
|
|
563
|
+
ok: true,
|
|
564
|
+
group_id: args.groupId,
|
|
565
|
+
data: {
|
|
566
|
+
status: 'pending_rekey',
|
|
567
|
+
auto_completed: false,
|
|
568
|
+
start: normalizeRecord(start),
|
|
569
|
+
complete_error: errorMessage(completeError),
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
catch (e) {
|
|
575
|
+
return formatRpcError(e);
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
await conn.close();
|
|
579
|
+
try {
|
|
580
|
+
store.close();
|
|
581
|
+
}
|
|
582
|
+
catch { }
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
export async function groupBan(args) {
|
|
586
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
587
|
+
try {
|
|
588
|
+
const params = { group_id: args.groupId, aid: args.memberAid };
|
|
589
|
+
if (args.durationSeconds !== undefined)
|
|
590
|
+
params.duration_seconds = args.durationSeconds;
|
|
591
|
+
const result = await conn.call('group.ban', params);
|
|
592
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
593
|
+
}
|
|
594
|
+
catch (e) {
|
|
595
|
+
return formatRpcError(e);
|
|
596
|
+
}
|
|
597
|
+
finally {
|
|
598
|
+
await conn.close();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
export async function groupUnban(args) {
|
|
602
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
603
|
+
try {
|
|
604
|
+
const result = await conn.call('group.unban', { group_id: args.groupId, aid: args.memberAid });
|
|
605
|
+
return { ok: true, group_id: args.groupId, data: result };
|
|
606
|
+
}
|
|
607
|
+
catch (e) {
|
|
608
|
+
return formatRpcError(e);
|
|
609
|
+
}
|
|
610
|
+
finally {
|
|
611
|
+
await conn.close();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
export async function groupBanlist(args) {
|
|
615
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
616
|
+
try {
|
|
617
|
+
const result = await conn.call('group.get_banlist', { group_id: args.groupId });
|
|
618
|
+
return {
|
|
619
|
+
ok: true,
|
|
620
|
+
group_id: result?.group_id ?? args.groupId,
|
|
621
|
+
items: result?.items ?? [],
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
catch (e) {
|
|
625
|
+
return formatRpcError(e);
|
|
626
|
+
}
|
|
627
|
+
finally {
|
|
628
|
+
await conn.close();
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
export async function groupRules(args) {
|
|
632
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
633
|
+
try {
|
|
634
|
+
const result = await conn.call('group.get_rules', { group_id: args.groupId });
|
|
635
|
+
return {
|
|
636
|
+
ok: true,
|
|
637
|
+
group_id: result?.group_id ?? args.groupId,
|
|
638
|
+
rules: result?.rules ?? result ?? {},
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
catch (e) {
|
|
642
|
+
if (e?.code === -32000 && String(e?.message ?? '').toLowerCase().includes('rules not found')) {
|
|
643
|
+
return { ok: true, group_id: args.groupId, rules: {} };
|
|
644
|
+
}
|
|
645
|
+
return formatRpcError(e);
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
await conn.close();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
export async function groupUpdateRules(args) {
|
|
652
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
653
|
+
try {
|
|
654
|
+
const params = { group_id: args.groupId };
|
|
655
|
+
if (args.mode !== undefined)
|
|
656
|
+
params.mode = args.mode;
|
|
657
|
+
if (args.question !== undefined)
|
|
658
|
+
params.question = args.question;
|
|
659
|
+
if (args.autoApprovePatterns !== undefined)
|
|
660
|
+
params.auto_approve_patterns = args.autoApprovePatterns;
|
|
661
|
+
if (args.maxPending !== undefined)
|
|
662
|
+
params.max_pending = args.maxPending;
|
|
663
|
+
const result = await conn.call('group.update_rules', params);
|
|
664
|
+
return {
|
|
665
|
+
ok: true,
|
|
666
|
+
group_id: result?.group_id ?? args.groupId,
|
|
667
|
+
rules: result?.rules ?? result ?? {},
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
catch (e) {
|
|
671
|
+
return formatRpcError(e);
|
|
672
|
+
}
|
|
673
|
+
finally {
|
|
674
|
+
await conn.close();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const GROUP_RULES_FILE_PATH = '/rules.md';
|
|
678
|
+
const MAX_GROUP_RULES_BYTES = 4 * 1024;
|
|
679
|
+
const MTIME_TOLERANCE_MS = 1;
|
|
680
|
+
export async function groupRulesFileGet(args) {
|
|
681
|
+
return withAunClient(args.from, args, async (client) => {
|
|
682
|
+
const resolved = await resolvePublishedRulesFile(client, args.groupId, true);
|
|
683
|
+
if (resolved.status !== 'ok') {
|
|
684
|
+
return {
|
|
685
|
+
ok: true,
|
|
686
|
+
group_id: args.groupId,
|
|
687
|
+
status: resolved.status,
|
|
688
|
+
path: GROUP_RULES_FILE_PATH,
|
|
689
|
+
metadata: resolved.metadata,
|
|
690
|
+
remote: resolved.remote,
|
|
691
|
+
error: resolved.error,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
let bytes;
|
|
695
|
+
try {
|
|
696
|
+
bytes = await readGroupRulesBytes(client, args.groupId);
|
|
697
|
+
}
|
|
698
|
+
catch (e) {
|
|
699
|
+
return {
|
|
700
|
+
ok: true,
|
|
701
|
+
group_id: args.groupId,
|
|
702
|
+
status: rulesFileStatusFromError(e),
|
|
703
|
+
path: GROUP_RULES_FILE_PATH,
|
|
704
|
+
metadata: resolved.metadata,
|
|
705
|
+
remote: resolved.remote,
|
|
706
|
+
error: errorMessage(e),
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
if (bytes.byteLength > MAX_GROUP_RULES_BYTES) {
|
|
710
|
+
return {
|
|
711
|
+
ok: true,
|
|
712
|
+
group_id: args.groupId,
|
|
713
|
+
status: 'too_large',
|
|
714
|
+
path: GROUP_RULES_FILE_PATH,
|
|
715
|
+
metadata: resolved.metadata,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
let content;
|
|
719
|
+
try {
|
|
720
|
+
content = decodeRulesText(bytes);
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
return {
|
|
724
|
+
ok: true,
|
|
725
|
+
group_id: args.groupId,
|
|
726
|
+
status: 'unreadable',
|
|
727
|
+
path: GROUP_RULES_FILE_PATH,
|
|
728
|
+
metadata: resolved.metadata,
|
|
729
|
+
remote: resolved.remote,
|
|
730
|
+
error: `规则文件不是有效 UTF-8: ${errorMessage(e)}`,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
ok: true,
|
|
735
|
+
group_id: args.groupId,
|
|
736
|
+
status: 'ok',
|
|
737
|
+
path: GROUP_RULES_FILE_PATH,
|
|
738
|
+
metadata: resolved.metadata,
|
|
739
|
+
content,
|
|
740
|
+
};
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
export async function groupRulesFileSet(args) {
|
|
744
|
+
return withAunClient(args.from, args, async (client) => {
|
|
745
|
+
assertLocalRulesFile(args.filePath);
|
|
746
|
+
const upload = await client.group.fs.cp(args.filePath, `${args.groupId}:${GROUP_RULES_FILE_PATH}`, {
|
|
747
|
+
force: true,
|
|
748
|
+
overwrite: true,
|
|
749
|
+
parents: true,
|
|
750
|
+
contentType: 'text/markdown; charset=utf-8',
|
|
751
|
+
});
|
|
752
|
+
const published = await publishCurrentRulesFile(client, args.groupId, args.from);
|
|
753
|
+
return { ...published, upload };
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
export async function groupRulesFilePublish(args) {
|
|
757
|
+
return withAunClient(args.from, args, async (client) => {
|
|
758
|
+
return publishCurrentRulesFile(client, args.groupId, args.from);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
// ==================== Internal ====================
|
|
762
|
+
async function withAunClient(from, opts, fn) {
|
|
763
|
+
const store = await getAidStore({ slotId: opts.slotId ?? SLOT.cli, aunPath: opts.aunPath });
|
|
764
|
+
let client;
|
|
765
|
+
try {
|
|
766
|
+
client = await loadClient(store, from);
|
|
767
|
+
await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
768
|
+
return await fn(client);
|
|
769
|
+
}
|
|
770
|
+
catch (e) {
|
|
771
|
+
return formatRpcError(e);
|
|
772
|
+
}
|
|
773
|
+
finally {
|
|
774
|
+
if (client) {
|
|
775
|
+
try {
|
|
776
|
+
await client.close();
|
|
777
|
+
}
|
|
778
|
+
catch { /* ignore */ }
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
store.close();
|
|
782
|
+
}
|
|
783
|
+
catch { /* ignore */ }
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
async function resolvePublishedRulesFile(client, groupId, forcePull) {
|
|
787
|
+
let content;
|
|
788
|
+
if (forcePull) {
|
|
789
|
+
try {
|
|
790
|
+
const index = await getGroupIndex(client, groupId);
|
|
791
|
+
const settings = isRecord(index.settings) ? index.settings : {};
|
|
792
|
+
content = settings['rules.content'];
|
|
793
|
+
}
|
|
794
|
+
catch (e) {
|
|
795
|
+
return {
|
|
796
|
+
status: rulesFileStatusFromError(e),
|
|
797
|
+
error: errorMessage(e),
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
try {
|
|
803
|
+
const result = await client.group.getRules({ group_id: groupId });
|
|
804
|
+
const rules = isRecord(result.rules) ? result.rules : {};
|
|
805
|
+
content = rules.content;
|
|
806
|
+
}
|
|
807
|
+
catch (e) {
|
|
808
|
+
return {
|
|
809
|
+
status: rulesFileStatusFromError(e),
|
|
810
|
+
error: errorMessage(e),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (content === undefined || content === null || content === '') {
|
|
815
|
+
return { status: 'missing' };
|
|
816
|
+
}
|
|
817
|
+
let metadata;
|
|
818
|
+
try {
|
|
819
|
+
metadata = parseRulesFileMetadata(content);
|
|
820
|
+
}
|
|
821
|
+
catch (e) {
|
|
822
|
+
return {
|
|
823
|
+
status: statusFromTaggedError(e) ?? 'invalid_metadata',
|
|
824
|
+
error: errorMessage(e),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
if (metadata.size > MAX_GROUP_RULES_BYTES) {
|
|
828
|
+
return { status: 'too_large', metadata };
|
|
829
|
+
}
|
|
830
|
+
let remote;
|
|
831
|
+
try {
|
|
832
|
+
remote = metadataFromRulesStat(await client.group.fs.stat(`${groupId}:${GROUP_RULES_FILE_PATH}`));
|
|
833
|
+
}
|
|
834
|
+
catch (e) {
|
|
835
|
+
return {
|
|
836
|
+
status: rulesFileStatusFromError(e),
|
|
837
|
+
metadata,
|
|
838
|
+
error: errorMessage(e),
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
if (!remote.size || remote.mtimeMs === undefined) {
|
|
842
|
+
return {
|
|
843
|
+
status: 'unreadable',
|
|
844
|
+
metadata,
|
|
845
|
+
remote,
|
|
846
|
+
error: `远端 ${GROUP_RULES_FILE_PATH} 缺少 size 或 mtimeMs`,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (!metadataMatches(metadata, remote)) {
|
|
850
|
+
return { status: 'file_mismatch', metadata, remote };
|
|
851
|
+
}
|
|
852
|
+
return { status: 'ok', metadata, remote };
|
|
853
|
+
}
|
|
854
|
+
async function publishCurrentRulesFile(client, groupId, actorAid) {
|
|
855
|
+
let metadata;
|
|
856
|
+
try {
|
|
857
|
+
metadata = metadataFromRulesStat(await client.group.fs.stat(`${groupId}:${GROUP_RULES_FILE_PATH}`));
|
|
858
|
+
}
|
|
859
|
+
catch (e) {
|
|
860
|
+
return {
|
|
861
|
+
ok: true,
|
|
862
|
+
group_id: groupId,
|
|
863
|
+
status: rulesFileStatusFromError(e),
|
|
864
|
+
path: GROUP_RULES_FILE_PATH,
|
|
865
|
+
error: errorMessage(e),
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
if (!metadata.size || metadata.mtimeMs === undefined) {
|
|
869
|
+
return {
|
|
870
|
+
ok: true,
|
|
871
|
+
group_id: groupId,
|
|
872
|
+
status: 'unreadable',
|
|
873
|
+
path: GROUP_RULES_FILE_PATH,
|
|
874
|
+
remote: metadata,
|
|
875
|
+
error: `远端 ${GROUP_RULES_FILE_PATH} 缺少 size 或 mtimeMs`,
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
const rulesMetadata = {
|
|
879
|
+
path: GROUP_RULES_FILE_PATH,
|
|
880
|
+
size: metadata.size,
|
|
881
|
+
mtimeMs: metadata.mtimeMs,
|
|
882
|
+
};
|
|
883
|
+
if (rulesMetadata.size > MAX_GROUP_RULES_BYTES) {
|
|
884
|
+
return {
|
|
885
|
+
ok: true,
|
|
886
|
+
group_id: groupId,
|
|
887
|
+
status: 'too_large',
|
|
888
|
+
path: GROUP_RULES_FILE_PATH,
|
|
889
|
+
metadata: rulesMetadata,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const publish = await client.group.updateRules({
|
|
893
|
+
group_id: groupId,
|
|
894
|
+
content: JSON.stringify(rulesMetadata),
|
|
895
|
+
attachments: [],
|
|
896
|
+
});
|
|
897
|
+
let after;
|
|
898
|
+
try {
|
|
899
|
+
after = metadataFromRulesStat(await client.group.fs.stat(`${groupId}:${GROUP_RULES_FILE_PATH}`));
|
|
900
|
+
}
|
|
901
|
+
catch (e) {
|
|
902
|
+
return {
|
|
903
|
+
ok: true,
|
|
904
|
+
group_id: groupId,
|
|
905
|
+
status: rulesFileStatusFromError(e),
|
|
906
|
+
path: GROUP_RULES_FILE_PATH,
|
|
907
|
+
metadata: rulesMetadata,
|
|
908
|
+
publish,
|
|
909
|
+
error: errorMessage(e),
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
if (!metadataMatches(rulesMetadata, after)) {
|
|
913
|
+
return {
|
|
914
|
+
ok: true,
|
|
915
|
+
group_id: groupId,
|
|
916
|
+
status: 'file_mismatch',
|
|
917
|
+
path: GROUP_RULES_FILE_PATH,
|
|
918
|
+
metadata: rulesMetadata,
|
|
919
|
+
remote: after,
|
|
920
|
+
publish,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
const groupIndexEtag = groupIndexEtagFromUpdate(publish, groupId) ?? await currentGroupIndexEtag(client, groupId);
|
|
924
|
+
const notice = await sendGroupRulesUpdatedNotice(client, {
|
|
925
|
+
groupId,
|
|
926
|
+
actorAid,
|
|
927
|
+
metadata: rulesMetadata,
|
|
928
|
+
groupIndexEtag,
|
|
929
|
+
});
|
|
930
|
+
return {
|
|
931
|
+
ok: true,
|
|
932
|
+
group_id: groupId,
|
|
933
|
+
status: 'ok',
|
|
934
|
+
path: GROUP_RULES_FILE_PATH,
|
|
935
|
+
metadata: rulesMetadata,
|
|
936
|
+
publish,
|
|
937
|
+
group_index_etag: groupIndexEtag,
|
|
938
|
+
notice,
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
async function sendGroupRulesUpdatedNotice(client, args) {
|
|
942
|
+
const payload = {
|
|
943
|
+
type: 'notice',
|
|
944
|
+
subtype: 'group.rules.updated',
|
|
945
|
+
text: `群规则已发布:${args.groupId}:${GROUP_RULES_FILE_PATH}\n操作者:${args.actorAid}`,
|
|
946
|
+
actor_aid: args.actorAid,
|
|
947
|
+
group_id: args.groupId,
|
|
948
|
+
path: GROUP_RULES_FILE_PATH,
|
|
949
|
+
group_index_etag: args.groupIndexEtag,
|
|
950
|
+
size: args.metadata.size,
|
|
951
|
+
mtimeMs: args.metadata.mtimeMs,
|
|
952
|
+
};
|
|
953
|
+
try {
|
|
954
|
+
const result = await client.group.send({
|
|
955
|
+
group_id: args.groupId,
|
|
956
|
+
payload,
|
|
957
|
+
encrypt: false,
|
|
958
|
+
});
|
|
959
|
+
const message = isRecord(result.message) ? result.message : {};
|
|
960
|
+
const messageId = typeof result.message_id === 'string'
|
|
961
|
+
? result.message_id
|
|
962
|
+
: typeof message.message_id === 'string'
|
|
963
|
+
? message.message_id
|
|
964
|
+
: undefined;
|
|
965
|
+
return { ok: true, message_id: messageId };
|
|
966
|
+
}
|
|
967
|
+
catch (e) {
|
|
968
|
+
return { ok: false, error: String(e?.message || e) };
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
function assertLocalRulesFile(filePath) {
|
|
972
|
+
const st = fs.statSync(filePath);
|
|
973
|
+
if (!st.isFile()) {
|
|
974
|
+
throw new Error(`规则文件不是普通文件: ${filePath}`);
|
|
975
|
+
}
|
|
976
|
+
if (st.size > MAX_GROUP_RULES_BYTES) {
|
|
977
|
+
throw new Error(`规则文件过大: ${st.size} bytes > ${MAX_GROUP_RULES_BYTES}`);
|
|
978
|
+
}
|
|
979
|
+
const bytes = fs.readFileSync(filePath);
|
|
980
|
+
try {
|
|
981
|
+
decodeRulesText(bytes);
|
|
982
|
+
}
|
|
983
|
+
catch (e) {
|
|
984
|
+
throw new Error(`规则文件不是有效 UTF-8: ${errorMessage(e)}`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
async function readGroupRulesBytes(client, groupId) {
|
|
988
|
+
const remoteRef = `${groupId}:${GROUP_RULES_FILE_PATH}`;
|
|
989
|
+
const ticket = await client.call('group.fs.create_download_ticket', { path: remoteRef });
|
|
990
|
+
if (!isRecord(ticket)) {
|
|
991
|
+
throw new Error('group.fs.create_download_ticket returned invalid response');
|
|
992
|
+
}
|
|
993
|
+
const downloadUrl = stringField(ticket, ['download_url', 'url']);
|
|
994
|
+
if (!downloadUrl) {
|
|
995
|
+
throw new Error('group.fs.create_download_ticket did not return download_url');
|
|
996
|
+
}
|
|
997
|
+
const bytes = await client.group.fs.lowlevel.httpGet(downloadUrl, bearerHeaders(client));
|
|
998
|
+
const expectedSha = stringField(ticket, ['sha256']);
|
|
999
|
+
if (expectedSha && sha256Hex(bytes).toLowerCase() !== expectedSha.toLowerCase()) {
|
|
1000
|
+
throw new Error('download hash verification failed');
|
|
1001
|
+
}
|
|
1002
|
+
return bytes;
|
|
1003
|
+
}
|
|
1004
|
+
function decodeRulesText(bytes) {
|
|
1005
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
1006
|
+
}
|
|
1007
|
+
function parseRulesFileMetadata(value) {
|
|
1008
|
+
let parsed;
|
|
1009
|
+
if (typeof value === 'string') {
|
|
1010
|
+
parsed = JSON.parse(value);
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
parsed = value;
|
|
1014
|
+
}
|
|
1015
|
+
if (!isRecord(parsed)) {
|
|
1016
|
+
throw statusError('invalid_metadata', 'rules.content metadata must be an object');
|
|
1017
|
+
}
|
|
1018
|
+
if (parsed.path !== GROUP_RULES_FILE_PATH) {
|
|
1019
|
+
throw statusError('invalid_metadata', `rules.content.path must be ${GROUP_RULES_FILE_PATH}`);
|
|
1020
|
+
}
|
|
1021
|
+
const size = numberField(parsed, ['size']);
|
|
1022
|
+
const mtimeMs = mtimeMsField(parsed);
|
|
1023
|
+
if (size === undefined || !Number.isInteger(size) || size <= 0) {
|
|
1024
|
+
throw statusError('invalid_metadata', 'rules.content.size must be a positive integer');
|
|
1025
|
+
}
|
|
1026
|
+
if (mtimeMs === undefined || !Number.isFinite(mtimeMs) || mtimeMs <= 0) {
|
|
1027
|
+
throw statusError('invalid_metadata', 'rules.content.mtimeMs must be a valid millisecond timestamp');
|
|
1028
|
+
}
|
|
1029
|
+
return { path: GROUP_RULES_FILE_PATH, size, mtimeMs };
|
|
1030
|
+
}
|
|
1031
|
+
function metadataFromRulesStat(value) {
|
|
1032
|
+
if (!isRecord(value))
|
|
1033
|
+
return { path: GROUP_RULES_FILE_PATH };
|
|
1034
|
+
return {
|
|
1035
|
+
path: GROUP_RULES_FILE_PATH,
|
|
1036
|
+
size: numberField(value, ['size', 'sizeBytes', 'size_bytes', 'bytes']),
|
|
1037
|
+
mtimeMs: mtimeMsField(value),
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function metadataMatches(expected, actual) {
|
|
1041
|
+
return actual.size === expected.size
|
|
1042
|
+
&& actual.mtimeMs !== undefined
|
|
1043
|
+
&& Math.abs(actual.mtimeMs - expected.mtimeMs) <= MTIME_TOLERANCE_MS;
|
|
1044
|
+
}
|
|
1045
|
+
function groupIndexEtagFromUpdate(result, groupId) {
|
|
1046
|
+
const meta = isRecord(result._meta) ? result._meta : {};
|
|
1047
|
+
const groupIndexes = isRecord(meta.group_indexes) ? meta.group_indexes : {};
|
|
1048
|
+
const direct = isRecord(groupIndexes[groupId]) ? groupIndexes[groupId] : undefined;
|
|
1049
|
+
if (typeof direct?.etag === 'string')
|
|
1050
|
+
return direct.etag;
|
|
1051
|
+
for (const value of Object.values(groupIndexes)) {
|
|
1052
|
+
if (isRecord(value) && typeof value.etag === 'string')
|
|
1053
|
+
return value.etag;
|
|
1054
|
+
}
|
|
1055
|
+
return undefined;
|
|
1056
|
+
}
|
|
1057
|
+
function sha256Hex(bytes) {
|
|
1058
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
1059
|
+
}
|
|
1060
|
+
function bearerHeaders(client) {
|
|
1061
|
+
const token = accessTokenFromClient(client);
|
|
1062
|
+
return token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
1063
|
+
}
|
|
1064
|
+
function accessTokenFromClient(client) {
|
|
1065
|
+
const anyClient = client;
|
|
1066
|
+
if (typeof anyClient.getAccessToken === 'function') {
|
|
1067
|
+
const token = stringValue(anyClient.getAccessToken());
|
|
1068
|
+
if (token)
|
|
1069
|
+
return token;
|
|
1070
|
+
}
|
|
1071
|
+
return stringValue(anyClient.accessToken)
|
|
1072
|
+
|| stringValue(anyClient.access_token)
|
|
1073
|
+
|| stringValue(anyClient._identity?.access_token)
|
|
1074
|
+
|| stringValue(anyClient._sessionParams?.access_token);
|
|
1075
|
+
}
|
|
1076
|
+
function stringField(obj, keys) {
|
|
1077
|
+
for (const key of keys) {
|
|
1078
|
+
const value = obj[key];
|
|
1079
|
+
if (typeof value === 'string' && value.trim())
|
|
1080
|
+
return value;
|
|
1081
|
+
}
|
|
1082
|
+
return undefined;
|
|
1083
|
+
}
|
|
1084
|
+
async function currentGroupIndexEtag(client, groupId) {
|
|
1085
|
+
try {
|
|
1086
|
+
const check = await checkGroupIndex(client, groupId);
|
|
1087
|
+
for (const key of ['remote_etag', 'local_etag', 'etag']) {
|
|
1088
|
+
const value = check[key];
|
|
1089
|
+
if (typeof value === 'string' && value)
|
|
1090
|
+
return value;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
// Fall through to a pulled index below.
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
const index = await getGroupIndex(client, groupId);
|
|
1098
|
+
const meta = isRecord(index.meta) ? index.meta : {};
|
|
1099
|
+
return typeof meta.etag === 'string' ? meta.etag : undefined;
|
|
1100
|
+
}
|
|
1101
|
+
catch {
|
|
1102
|
+
return undefined;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function statusError(status, message) {
|
|
1106
|
+
const error = new Error(message);
|
|
1107
|
+
error.groupRulesFileStatus = status;
|
|
1108
|
+
return error;
|
|
1109
|
+
}
|
|
1110
|
+
function statusFromTaggedError(error) {
|
|
1111
|
+
if (isRecord(error) && typeof error.groupRulesFileStatus === 'string') {
|
|
1112
|
+
const status = error.groupRulesFileStatus;
|
|
1113
|
+
if (isGroupRulesFileStatus(status))
|
|
1114
|
+
return status;
|
|
1115
|
+
}
|
|
1116
|
+
return undefined;
|
|
1117
|
+
}
|
|
1118
|
+
function rulesFileStatusFromError(error) {
|
|
1119
|
+
const tagged = statusFromTaggedError(error);
|
|
1120
|
+
if (tagged)
|
|
1121
|
+
return tagged;
|
|
1122
|
+
const haystack = errorHaystack(error);
|
|
1123
|
+
if (/(forbidden|unauthorized|permission|denied|no_permission|permission_denied|无权限|权限|拒绝|\b401\b|\b403\b)/i.test(haystack)) {
|
|
1124
|
+
return 'forbidden';
|
|
1125
|
+
}
|
|
1126
|
+
if (/(not[_ -]?found|notfound|no such|enoent|missing|不存在|\b404\b|rules not found)/i.test(haystack)) {
|
|
1127
|
+
return 'missing';
|
|
1128
|
+
}
|
|
1129
|
+
if (/(signature|verify|verification|signed_by|body_hash|invalid signature|验签|签名|json|metadata)/i.test(haystack)) {
|
|
1130
|
+
return 'invalid_metadata';
|
|
1131
|
+
}
|
|
1132
|
+
if (/(timeout|temporar|unavailable|econn|network|socket|reset|下载|读取)/i.test(haystack)) {
|
|
1133
|
+
return 'unreadable';
|
|
1134
|
+
}
|
|
1135
|
+
return 'error';
|
|
1136
|
+
}
|
|
1137
|
+
function isGroupRulesFileStatus(value) {
|
|
1138
|
+
return value === 'ok'
|
|
1139
|
+
|| value === 'missing'
|
|
1140
|
+
|| value === 'forbidden'
|
|
1141
|
+
|| value === 'invalid_metadata'
|
|
1142
|
+
|| value === 'file_mismatch'
|
|
1143
|
+
|| value === 'too_large'
|
|
1144
|
+
|| value === 'unreadable'
|
|
1145
|
+
|| value === 'error';
|
|
1146
|
+
}
|
|
1147
|
+
function errorHaystack(error) {
|
|
1148
|
+
if (!isRecord(error))
|
|
1149
|
+
return errorMessage(error);
|
|
1150
|
+
return [
|
|
1151
|
+
error.name,
|
|
1152
|
+
error.message,
|
|
1153
|
+
error.code,
|
|
1154
|
+
error.status,
|
|
1155
|
+
error.statusCode,
|
|
1156
|
+
error.error,
|
|
1157
|
+
].map(value => value === undefined ? '' : String(value)).join(' ');
|
|
1158
|
+
}
|
|
1159
|
+
function numberField(obj, keys) {
|
|
1160
|
+
for (const key of keys) {
|
|
1161
|
+
const value = obj[key];
|
|
1162
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
1163
|
+
return value;
|
|
1164
|
+
if (typeof value === 'string' && value.trim() && Number.isFinite(Number(value)))
|
|
1165
|
+
return Number(value);
|
|
1166
|
+
}
|
|
1167
|
+
return undefined;
|
|
1168
|
+
}
|
|
1169
|
+
function stringValue(value) {
|
|
1170
|
+
return value === undefined || value === null ? '' : String(value).trim();
|
|
1171
|
+
}
|
|
1172
|
+
function mtimeMsField(obj) {
|
|
1173
|
+
const direct = numberField(obj, ['mtimeMs', 'mtime_ms', 'updatedAtMs', 'updated_at_ms']);
|
|
1174
|
+
if (direct !== undefined)
|
|
1175
|
+
return direct;
|
|
1176
|
+
const seconds = numberField(obj, ['mtime', 'updated_at', 'updatedAt', 'last_modified', 'modified_at']);
|
|
1177
|
+
if (seconds !== undefined)
|
|
1178
|
+
return seconds > 10_000_000_000 ? seconds : seconds * 1000;
|
|
1179
|
+
for (const key of ['modifiedAt', 'modified_at', 'mtime_iso']) {
|
|
1180
|
+
const value = obj[key];
|
|
1181
|
+
if (typeof value === 'string') {
|
|
1182
|
+
const parsed = Date.parse(value);
|
|
1183
|
+
if (Number.isFinite(parsed))
|
|
1184
|
+
return parsed;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return undefined;
|
|
1188
|
+
}
|
|
1189
|
+
function isRecord(value) {
|
|
1190
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1191
|
+
}
|
|
1192
|
+
function normalizeGroupInfo(value) {
|
|
1193
|
+
const groupId = String(value?.group_aid ?? value?.group_id ?? '');
|
|
1194
|
+
return {
|
|
1195
|
+
...value,
|
|
1196
|
+
group_id: groupId,
|
|
1197
|
+
...(value?.group_aid ? { group_aid: String(value.group_aid) } : {}),
|
|
1198
|
+
name: String(value?.name ?? groupId),
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function formatRpcError(e) {
|
|
1202
|
+
if (e?.code !== undefined && e?.message !== undefined) {
|
|
1203
|
+
return { ok: false, error: String(e.message), code: e.code };
|
|
1204
|
+
}
|
|
1205
|
+
return { ok: false, error: String(e?.message ?? e) };
|
|
1206
|
+
}
|
|
1207
|
+
function normalizeRecord(value) {
|
|
1208
|
+
return value && typeof value === 'object' ? value : { value };
|
|
1209
|
+
}
|
|
1210
|
+
function ownerTransferNeedsCompletion(value) {
|
|
1211
|
+
if (!value || typeof value !== 'object')
|
|
1212
|
+
return false;
|
|
1213
|
+
const data = value;
|
|
1214
|
+
const status = String(data.status ?? data.transfer_status ?? '').toLowerCase();
|
|
1215
|
+
return status === 'pending_rekey' || status === 'requires_ca_rekey' || data.requires_ca_rekey === true;
|
|
1216
|
+
}
|
|
1217
|
+
function errorMessage(e) {
|
|
1218
|
+
if (e && typeof e === 'object' && 'message' in e)
|
|
1219
|
+
return String(e.message);
|
|
1220
|
+
return String(e);
|
|
1221
|
+
}
|
|
1222
|
+
function groupNameFromGroupId(groupId) {
|
|
1223
|
+
if (!groupId)
|
|
1224
|
+
return undefined;
|
|
1225
|
+
const raw = String(groupId).trim().toLowerCase();
|
|
1226
|
+
const first = raw.split(/[.@/]/, 1)[0] ?? '';
|
|
1227
|
+
const slug = first.startsWith('g-') ? first.slice(2) : first;
|
|
1228
|
+
if (/^[a-z0-9][a-z0-9_-]{3,63}$/.test(slug))
|
|
1229
|
+
return slug;
|
|
1230
|
+
return undefined;
|
|
1231
|
+
}
|