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,374 @@
|
|
|
1
|
+
import { roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
2
|
+
import { getContactSnapshot, resolveContactView } from '../../config/contact-book.js';
|
|
3
|
+
import { mutateContactBook } from '../../config/contact-book-store.js';
|
|
4
|
+
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
5
|
+
/**
|
|
6
|
+
* Connect Menu — 联系人与关系管理协议
|
|
7
|
+
*
|
|
8
|
+
* 定位:role menu 之上的**薄委派层**。
|
|
9
|
+
* - 角色分配 / admin(assignments、resource=assignment/admin)→ 委派给 role-menu,
|
|
10
|
+
* 复用其 agent 级互斥锁、CAS revision、owner/admin 安全护栏与审计链,绝不自行写 relation 文件。
|
|
11
|
+
* - 群组列表(view=groups)→ 委派给 role-menu 的 view=targets(数据源是 AUN live 目录),
|
|
12
|
+
* 群组不落 contact.json(contact book 只接受个人 AID,group 会被拒绝)。
|
|
13
|
+
* - 联系人(view=contacts / contact,add / block / unblock)→ 直接读写 Contact Book v2,
|
|
14
|
+
* contact.json 是联系人列表的唯一事实来源。
|
|
15
|
+
*/
|
|
16
|
+
const MENU_NAME = 'connect';
|
|
17
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
18
|
+
const MAX_PAGE_SIZE = 200;
|
|
19
|
+
/**
|
|
20
|
+
* Map a connect menu request to its authorization operation id.
|
|
21
|
+
* Read views/options → connect.read; contact writes → connect.write;
|
|
22
|
+
* role assignment/admin delegate to the role.* operations role-menu enforces.
|
|
23
|
+
*/
|
|
24
|
+
export function connectMenuOperation(kind, args, value) {
|
|
25
|
+
if (kind === 'options' || kind === 'query')
|
|
26
|
+
return 'connect.read';
|
|
27
|
+
if (kind === 'action')
|
|
28
|
+
return 'connect.write';
|
|
29
|
+
const resource = String(args?.resource ?? '');
|
|
30
|
+
if (resource === 'admin')
|
|
31
|
+
return 'connect.admin';
|
|
32
|
+
const decoded = decodeUpdateValue(value);
|
|
33
|
+
if (decoded === 'admin')
|
|
34
|
+
return 'connect.admin';
|
|
35
|
+
return decoded === null || decoded === false || decoded === 'revoke'
|
|
36
|
+
? 'connect.revoke'
|
|
37
|
+
: 'connect.assign';
|
|
38
|
+
}
|
|
39
|
+
// ── Menu Entry ──
|
|
40
|
+
export async function handleConnectMenu(req, context) {
|
|
41
|
+
const { self } = context;
|
|
42
|
+
if (!self)
|
|
43
|
+
return fail('INVALID_ARGUMENT', 'selfAid is required for connect menu');
|
|
44
|
+
if (req.args?.self !== self) {
|
|
45
|
+
return fail(req.args && Object.prototype.hasOwnProperty.call(req.args, 'self') ? 'PERMISSION_DENIED' : 'INVALID_ARGUMENT', req.args && Object.prototype.hasOwnProperty.call(req.args, 'self')
|
|
46
|
+
? 'args.self does not match the receiving Agent'
|
|
47
|
+
: 'args.self is required for connect menu');
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
if (req.subtype === 'options')
|
|
51
|
+
return await handleOptions(self);
|
|
52
|
+
if (req.subtype === 'query')
|
|
53
|
+
return await handleQuery(req, self, context);
|
|
54
|
+
if (req.subtype === 'update')
|
|
55
|
+
return await handleUpdate(req, context);
|
|
56
|
+
if (req.subtype === 'action')
|
|
57
|
+
return await handleAction(req, self, context);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return fail(errorCode(error), errorMessage(error), errorData(error));
|
|
61
|
+
}
|
|
62
|
+
return fail('INVALID_ARGUMENT', `Unknown subtype: ${req.subtype}`);
|
|
63
|
+
}
|
|
64
|
+
// ── Options ──
|
|
65
|
+
async function handleOptions(self) {
|
|
66
|
+
const snapshot = getContactSnapshot(self);
|
|
67
|
+
const contactCount = snapshot.contacts.size;
|
|
68
|
+
const blockedCount = snapshot.blockedIndex.size;
|
|
69
|
+
return ok({
|
|
70
|
+
options: [
|
|
71
|
+
{
|
|
72
|
+
option: 'contacts',
|
|
73
|
+
label: '联系人列表',
|
|
74
|
+
description: `查看所有联系人(${contactCount} 人,${blockedCount} 人已拉黑)`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
option: 'groups',
|
|
78
|
+
label: '群组列表',
|
|
79
|
+
description: '查看所在群组',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// ── Query ──
|
|
85
|
+
async function handleQuery(req, self, context) {
|
|
86
|
+
const args = req.args ?? {};
|
|
87
|
+
const view = String(args.view ?? '');
|
|
88
|
+
if (view === 'contacts')
|
|
89
|
+
return queryContacts(args, self);
|
|
90
|
+
if (view === 'contact')
|
|
91
|
+
return queryContact(req, self);
|
|
92
|
+
// Groups and role assignments are delegated to role-menu (authoritative source).
|
|
93
|
+
// Role *definitions* are out of scope here — use `menu name=role` for those.
|
|
94
|
+
if (view === 'groups') {
|
|
95
|
+
const data = await roleMenuQuery(context, { ...args, view: 'targets', targetType: 'group' });
|
|
96
|
+
return ok({
|
|
97
|
+
self,
|
|
98
|
+
view: 'groups',
|
|
99
|
+
items: (data.items ?? []).map((item) => ({
|
|
100
|
+
groupId: item.groupId,
|
|
101
|
+
peerKey: item.peerKey,
|
|
102
|
+
displayName: item.displayName,
|
|
103
|
+
status: item.status,
|
|
104
|
+
memberCount: item.memberCount,
|
|
105
|
+
roleId: item.explicitDefaultRole ?? null,
|
|
106
|
+
effectiveRoleId: item.baselineRole ?? null,
|
|
107
|
+
effectiveRoleSource: item.baselineRoleSource ?? null,
|
|
108
|
+
relationRevision: item.relationRevision,
|
|
109
|
+
})),
|
|
110
|
+
nextCursor: data.nextCursor ?? null,
|
|
111
|
+
total: data.total,
|
|
112
|
+
dataSource: data.dataSource,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (view === 'assignments') {
|
|
116
|
+
const data = await roleMenuQuery(context, { ...args, view: 'assignments', includeDefaults: false });
|
|
117
|
+
return ok({
|
|
118
|
+
self,
|
|
119
|
+
view: 'assignments',
|
|
120
|
+
...(args.roleId === undefined ? {} : { roleId: args.roleId }),
|
|
121
|
+
policyRevision: data.policyRevision,
|
|
122
|
+
items: (data.items ?? []).map(normalizeAssignmentItem),
|
|
123
|
+
nextCursor: data.nextCursor ?? null,
|
|
124
|
+
dataSource: { ...data.dataSource, kind: 'relation-config' },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return fail('INVALID_ARGUMENT', `Unknown view: ${String(view)}`);
|
|
128
|
+
}
|
|
129
|
+
function queryContacts(args, self) {
|
|
130
|
+
const snapshot = getContactSnapshot(self);
|
|
131
|
+
const page = paginate([...snapshot.contacts.keys()].sort(), args, self, primaryId => primaryId);
|
|
132
|
+
const items = page.items.map(primaryId => contactItem(self, primaryId));
|
|
133
|
+
return ok({
|
|
134
|
+
self,
|
|
135
|
+
view: 'contacts',
|
|
136
|
+
items,
|
|
137
|
+
contactRevision: snapshot.contactRevision,
|
|
138
|
+
nextCursor: page.nextCursor,
|
|
139
|
+
dataSource: { kind: 'contact-book', authoritative: true, complete: true },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function queryContact(req, self) {
|
|
143
|
+
const primaryId = String(req.args?.primaryId ?? '').trim();
|
|
144
|
+
if (!primaryId)
|
|
145
|
+
return fail('INVALID_ARGUMENT', 'primaryId is required');
|
|
146
|
+
const snapshot = getContactSnapshot(self);
|
|
147
|
+
if (!snapshot.contacts.has(primaryId))
|
|
148
|
+
return fail('NOT_FOUND', `Contact was not found: ${primaryId}`);
|
|
149
|
+
return ok({ self, view: 'contact', ...contactItem(self, primaryId), contactRevision: snapshot.contactRevision });
|
|
150
|
+
}
|
|
151
|
+
function contactItem(self, primaryId) {
|
|
152
|
+
const view = resolveContactView(self, primaryId);
|
|
153
|
+
return {
|
|
154
|
+
primaryId,
|
|
155
|
+
displayName: view.displayName ?? null,
|
|
156
|
+
status: view.status,
|
|
157
|
+
blocked: view.blocked,
|
|
158
|
+
isOwner: view.isOwner,
|
|
159
|
+
aliases: view.aliases,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function normalizeAssignmentItem(item) {
|
|
163
|
+
const peerKey = item?.target?.peerKey;
|
|
164
|
+
if (typeof peerKey !== 'string')
|
|
165
|
+
return item;
|
|
166
|
+
try {
|
|
167
|
+
const { channelType, channelId } = parsePeerKey(peerKey);
|
|
168
|
+
if (channelType === 'aun')
|
|
169
|
+
return item;
|
|
170
|
+
return {
|
|
171
|
+
...item,
|
|
172
|
+
target: {
|
|
173
|
+
kind: item.target.kind,
|
|
174
|
+
channelType,
|
|
175
|
+
channelId,
|
|
176
|
+
peerKey,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return item;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ── Update (delegated to role-menu) ──
|
|
185
|
+
async function handleUpdate(req, context) {
|
|
186
|
+
const args = req.args ?? {};
|
|
187
|
+
const resource = args.resource;
|
|
188
|
+
if (resource !== 'assignment' && resource !== 'admin') {
|
|
189
|
+
return fail('INVALID_ARGUMENT', `Unknown resource: ${String(resource)}`);
|
|
190
|
+
}
|
|
191
|
+
if (req.value === undefined || !req.value.trim()) {
|
|
192
|
+
return fail('INVALID_ARGUMENT', 'value is required');
|
|
193
|
+
}
|
|
194
|
+
validateWritableTarget(args.target);
|
|
195
|
+
const value = roleMenuUpdateValue(resource, req.value);
|
|
196
|
+
const data = await roleMenuUpdate(context, args, value);
|
|
197
|
+
return ok(data);
|
|
198
|
+
}
|
|
199
|
+
// ── Action (Contact Book v2) ──
|
|
200
|
+
async function handleAction(req, self, context) {
|
|
201
|
+
const action = req.action;
|
|
202
|
+
if (action === 'add')
|
|
203
|
+
return actionAdd(req, self, context);
|
|
204
|
+
if (action === 'block')
|
|
205
|
+
return actionSetStatus(req, self, context, true);
|
|
206
|
+
if (action === 'unblock')
|
|
207
|
+
return actionSetStatus(req, self, context, false);
|
|
208
|
+
return fail('INVALID_ARGUMENT', `Unknown action: ${String(action)}`);
|
|
209
|
+
}
|
|
210
|
+
async function actionAdd(req, self, context) {
|
|
211
|
+
const args = req.args ?? {};
|
|
212
|
+
const primaryId = String(args.primaryId ?? '').trim();
|
|
213
|
+
if (!primaryId)
|
|
214
|
+
return fail('INVALID_ARGUMENT', 'primaryId is required');
|
|
215
|
+
const expectedContactRevision = requireContactRevision(args);
|
|
216
|
+
const hasDisplayName = Object.prototype.hasOwnProperty.call(args, 'displayName');
|
|
217
|
+
if (hasDisplayName && args.displayName !== null && typeof args.displayName !== 'string') {
|
|
218
|
+
return fail('INVALID_ARGUMENT', 'displayName must be a string or null');
|
|
219
|
+
}
|
|
220
|
+
const current = getContactSnapshot(self).contacts.get(primaryId);
|
|
221
|
+
const displayName = hasDisplayName ? args.displayName : current?.displayName ?? null;
|
|
222
|
+
const result = await mutateContactBook({
|
|
223
|
+
selfAid: self,
|
|
224
|
+
actor: context.actorAid ?? 'connect-menu',
|
|
225
|
+
expectedContactRevision,
|
|
226
|
+
mutation: { type: 'set-display-name', primaryId, displayName },
|
|
227
|
+
});
|
|
228
|
+
const storedDisplayName = result.contact.contacts[primaryId]?.displayName ?? null;
|
|
229
|
+
return ok({
|
|
230
|
+
self,
|
|
231
|
+
action: 'add',
|
|
232
|
+
primaryId,
|
|
233
|
+
displayName: storedDisplayName,
|
|
234
|
+
changed: result.changed,
|
|
235
|
+
contactRevision: result.contactRevision,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
async function actionSetStatus(req, self, context, blocked) {
|
|
239
|
+
const args = req.args ?? {};
|
|
240
|
+
const primaryId = String(args.primaryId ?? '').trim();
|
|
241
|
+
if (!primaryId)
|
|
242
|
+
return fail('INVALID_ARGUMENT', 'primaryId is required');
|
|
243
|
+
const expectedContactRevision = requireContactRevision(args);
|
|
244
|
+
const result = await mutateContactBook({
|
|
245
|
+
selfAid: self,
|
|
246
|
+
actor: context.actorAid ?? 'connect-menu',
|
|
247
|
+
expectedContactRevision,
|
|
248
|
+
mutation: { type: 'set-status', primaryId, status: blocked ? 'blocked' : 'active' },
|
|
249
|
+
});
|
|
250
|
+
return ok({
|
|
251
|
+
self,
|
|
252
|
+
action: blocked ? 'block' : 'unblock',
|
|
253
|
+
primaryId,
|
|
254
|
+
changed: result.changed,
|
|
255
|
+
contactRevision: result.contactRevision,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// ── Helpers ──
|
|
259
|
+
function ok(data) {
|
|
260
|
+
return { type: 'menu.response', id: '', name: MENU_NAME, data };
|
|
261
|
+
}
|
|
262
|
+
function fail(code, message, data) {
|
|
263
|
+
return {
|
|
264
|
+
type: 'menu.response',
|
|
265
|
+
id: '',
|
|
266
|
+
name: MENU_NAME,
|
|
267
|
+
error: { code, message, ...(data === undefined ? {} : { data }) },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function errorCode(error) {
|
|
271
|
+
const code = error?.code;
|
|
272
|
+
if (code === 'INVALID_AID')
|
|
273
|
+
return 'INVALID_ARGUMENT';
|
|
274
|
+
if (code === 'OWNER_PROTECTED')
|
|
275
|
+
return 'NOT_ALLOWED';
|
|
276
|
+
if (code === 'ALIAS_CONFLICT')
|
|
277
|
+
return 'CONFLICT';
|
|
278
|
+
if (code === 'INVALID_CONTACT_BOOK')
|
|
279
|
+
return 'INTERNAL_ERROR';
|
|
280
|
+
return typeof code === 'string' ? code : 'INTERNAL_ERROR';
|
|
281
|
+
}
|
|
282
|
+
function errorMessage(error) {
|
|
283
|
+
if (error instanceof Error)
|
|
284
|
+
return error.message;
|
|
285
|
+
const message = error?.message;
|
|
286
|
+
return typeof message === 'string' ? message : String(error);
|
|
287
|
+
}
|
|
288
|
+
function errorData(error) {
|
|
289
|
+
return error && typeof error === 'object' ? error.data : undefined;
|
|
290
|
+
}
|
|
291
|
+
function requireContactRevision(args) {
|
|
292
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'expectedContactRevision')) {
|
|
293
|
+
throw connectError('INVALID_ARGUMENT', 'expectedContactRevision is required');
|
|
294
|
+
}
|
|
295
|
+
if (typeof args.expectedContactRevision !== 'string' || !args.expectedContactRevision) {
|
|
296
|
+
throw connectError('INVALID_ARGUMENT', 'expectedContactRevision must be a non-empty string');
|
|
297
|
+
}
|
|
298
|
+
return args.expectedContactRevision;
|
|
299
|
+
}
|
|
300
|
+
function decodeUpdateValue(value) {
|
|
301
|
+
const text = String(value ?? '').trim();
|
|
302
|
+
if (!text)
|
|
303
|
+
return undefined;
|
|
304
|
+
try {
|
|
305
|
+
const decoded = JSON.parse(text);
|
|
306
|
+
if (decoded === null || typeof decoded === 'string' || typeof decoded === 'boolean')
|
|
307
|
+
return decoded;
|
|
308
|
+
}
|
|
309
|
+
catch { }
|
|
310
|
+
return text;
|
|
311
|
+
}
|
|
312
|
+
function roleMenuUpdateValue(resource, value) {
|
|
313
|
+
const decoded = decodeUpdateValue(value);
|
|
314
|
+
if (resource === 'assignment') {
|
|
315
|
+
if (decoded !== null && typeof decoded !== 'string') {
|
|
316
|
+
throw connectError('INVALID_ARGUMENT', 'assignment value must be a role ID or null');
|
|
317
|
+
}
|
|
318
|
+
return JSON.stringify(decoded);
|
|
319
|
+
}
|
|
320
|
+
if (![true, false, null, 'grant', 'revoke'].includes(decoded)) {
|
|
321
|
+
throw connectError('INVALID_ARGUMENT', 'admin value must be grant or revoke');
|
|
322
|
+
}
|
|
323
|
+
return JSON.stringify(decoded);
|
|
324
|
+
}
|
|
325
|
+
function validateWritableTarget(target) {
|
|
326
|
+
if (!target || typeof target !== 'object' || Array.isArray(target))
|
|
327
|
+
return;
|
|
328
|
+
const value = target;
|
|
329
|
+
if (typeof value.channelType === 'string' && value.channelType !== 'aun') {
|
|
330
|
+
throw connectError('NOT_SUPPORTED', 'Native non-AUN assignment writes are not supported in Phase 1');
|
|
331
|
+
}
|
|
332
|
+
const kind = value.kind;
|
|
333
|
+
const logicalId = kind === 'private' ? value.peerAid : kind === 'group' ? value.groupId : undefined;
|
|
334
|
+
if (value.peerKey === undefined || typeof logicalId !== 'string')
|
|
335
|
+
return;
|
|
336
|
+
const expected = formatPeerKey('aun', logicalId);
|
|
337
|
+
if (value.peerKey !== expected) {
|
|
338
|
+
throw connectError('INVALID_ARGUMENT', 'target.peerKey does not match the logical target', {
|
|
339
|
+
expectedPeerKey: expected,
|
|
340
|
+
peerKey: value.peerKey,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function paginate(items, args, self, key) {
|
|
345
|
+
const requestedLimit = Number(args.limit ?? DEFAULT_PAGE_SIZE);
|
|
346
|
+
const limit = Number.isInteger(requestedLimit) && requestedLimit > 0
|
|
347
|
+
? Math.min(requestedLimit, MAX_PAGE_SIZE)
|
|
348
|
+
: DEFAULT_PAGE_SIZE;
|
|
349
|
+
let start = 0;
|
|
350
|
+
if (args.cursor !== undefined && args.cursor !== null) {
|
|
351
|
+
try {
|
|
352
|
+
const decoded = JSON.parse(Buffer.from(String(args.cursor), 'base64url').toString('utf8'));
|
|
353
|
+
if (!decoded || typeof decoded.after !== 'string')
|
|
354
|
+
throw new Error();
|
|
355
|
+
start = items.findIndex(item => key(item) > decoded.after);
|
|
356
|
+
if (start < 0)
|
|
357
|
+
start = items.length;
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
throw connectError('INVALID_ARGUMENT', 'cursor is invalid', { self, field: 'cursor' });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const page = items.slice(start, start + limit);
|
|
364
|
+
const hasMore = start + page.length < items.length;
|
|
365
|
+
return {
|
|
366
|
+
items: page,
|
|
367
|
+
nextCursor: hasMore && page.length
|
|
368
|
+
? Buffer.from(JSON.stringify({ after: key(page[page.length - 1]) }), 'utf8').toString('base64url')
|
|
369
|
+
: null,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function connectError(code, message, data) {
|
|
373
|
+
return Object.assign(new Error(message), { code, ...(data === undefined ? {} : { data }) });
|
|
374
|
+
}
|