evolcore 0.0.1 → 0.0.2
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 +813 -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/assets/brand/evolcore/README.md +19 -0
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +14 -0
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +10 -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 +2385 -0
- package/dist/agents/codex-app-server-client.js +448 -0
- package/dist/agents/codex-runner.js +2639 -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 +160 -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 +4147 -0
- package/dist/channels/daemon.js +422 -0
- package/dist/channels/dingtalk.js +649 -0
- package/dist/channels/feishu.js +1789 -0
- package/dist/channels/qqbot.js +409 -0
- package/dist/channels/wechat.js +817 -0
- package/dist/channels/wecom.js +565 -0
- package/dist/cli/agent-command.js +641 -0
- package/dist/cli/agent.js +1059 -0
- package/dist/cli/aun-commands.js +1948 -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/ctl-command.js +62 -0
- package/dist/cli/daemon-commands.js +2887 -0
- package/dist/cli/fs-command.js +1447 -0
- package/dist/cli/handoff-command.js +302 -0
- package/dist/cli/help.js +31 -0
- package/dist/cli/index.js +358 -0
- package/dist/cli/init-channel.js +1377 -0
- package/dist/cli/init.js +553 -0
- package/dist/cli/link-rules.js +240 -0
- package/dist/cli/model.js +590 -0
- package/dist/cli/net-check.js +723 -0
- package/dist/cli/queue-command.js +126 -0
- package/dist/cli/response.js +345 -0
- package/dist/cli/restart-monitor.js +456 -0
- package/dist/cli/stats.js +607 -0
- package/dist/cli/task-context.js +80 -0
- package/dist/cli/trigger-command.js +505 -0
- package/dist/cli/version.js +88 -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 +30 -0
- package/dist/config/builtin-roles.js +85 -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 +916 -0
- package/dist/config/config-operation-service.js +384 -0
- package/dist/config/contact-book.js +371 -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 +139 -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-schema.js +105 -0
- package/dist/config/role-service.js +159 -0
- package/dist/config/role-store.js +204 -0
- package/dist/config/roles.js +55 -0
- package/dist/config/schema-registry.js +154 -0
- package/dist/config/snapshot.js +598 -0
- package/dist/config-store.js +503 -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 +110 -0
- package/dist/core/auth/operation-authorizer.js +718 -0
- package/dist/core/auth/operation-catalog.js +675 -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 +1638 -0
- package/dist/core/command/menu-handler.js +3354 -0
- package/dist/core/command/menu-protocol.js +247 -0
- package/dist/core/command/role-menu.js +1524 -0
- package/dist/core/command/slash-gate.js +148 -0
- package/dist/core/command/slash-handler.js +3056 -0
- package/dist/core/daemon-file-cache.js +216 -0
- package/dist/core/event-bus.js +32 -0
- package/dist/core/event-catalog.js +740 -0
- package/dist/core/evolagent-registry.js +546 -0
- package/dist/core/evolagent.js +331 -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 +657 -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 +803 -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 +1436 -0
- package/dist/core/message/message-utils.js +70 -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 +3776 -0
- package/dist/core/message/response-snapshot.js +83 -0
- package/dist/core/message/stream-debouncer.js +130 -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 +987 -0
- package/dist/core/permission/unix-socket-policy.js +99 -0
- package/dist/core/protected-paths.js +330 -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 +232 -0
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +1587 -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 +2870 -4
- package/dist/ipc.js +748 -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 +151 -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 +291 -0
- package/dist/trigger/parser.js +520 -0
- package/dist/trigger/patch.js +148 -0
- package/dist/trigger/scheduler.js +1600 -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 +621 -0
- package/dist/types.js +12 -0
- package/dist/utils/aid-bind.js +299 -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 +437 -0
- package/dist/utils/locale.js +21 -0
- package/dist/utils/log-writer.js +224 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/markdown-to-plain-text.js +20 -0
- package/dist/utils/media-cache.js +271 -0
- package/dist/utils/model-prices.jsonl +17 -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/stats.js +410 -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 +65 -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 +66 -0
- package/kits/docs/evolcore/agent.md +69 -0
- package/kits/docs/evolcore/aid.md +49 -0
- package/kits/docs/evolcore/config.md +149 -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 +101 -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 +141 -0
- package/kits/docs/evolcore/model.md +47 -0
- package/kits/docs/evolcore/msg.md +130 -0
- package/kits/docs/evolcore/response.md +80 -0
- package/kits/docs/evolcore/rpc.md +35 -0
- package/kits/docs/evolcore/self-summary.md +29 -0
- package/kits/docs/evolcore/stats.md +70 -0
- package/kits/docs/evolcore/storage.md +49 -0
- package/kits/docs/evolcore/trigger.md +524 -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 +191 -0
- package/kits/eck_message_manifest.json +63 -0
- package/kits/migrations/README-role-config-v4.md +32 -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 +29 -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/contact-book.schema.1.json +36 -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/role-config.schema.1.json +200 -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 +8 -0
- package/kits/templates/roles/member.json +40 -0
- package/kits/templates/roles/owner.json +8 -0
- package/kits/templates/roles/visitor.json +39 -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 +26 -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,123 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { isValidAid } from '../aid/identity.js';
|
|
3
|
+
import { isTransientProtocolMessage, messageLogPath } from '../../core/message/message-log.js';
|
|
4
|
+
import { chatDirPath, readAllJsonlLines, readJsonFile, readLastJsonlLine } from '../../core/session/session-fs-store.js';
|
|
5
|
+
import { resolvePaths } from '../../paths.js';
|
|
6
|
+
const DEFAULT_LIMIT = 50;
|
|
7
|
+
export const MAX_MSG_HISTORY_LIMIT = 500;
|
|
8
|
+
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
9
|
+
export function msgHistory(args, options = {}) {
|
|
10
|
+
if (!isValidAid(args.from)) {
|
|
11
|
+
return error('INVALID_SELF_AID', `无效 self AID: ${args.from}`);
|
|
12
|
+
}
|
|
13
|
+
if (!isValidAid(args.target)) {
|
|
14
|
+
return error('INVALID_TARGET_AID', `无效 target AID: ${args.target}`);
|
|
15
|
+
}
|
|
16
|
+
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
17
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_MSG_HISTORY_LIMIT) {
|
|
18
|
+
return error('INVALID_LIMIT', `limit 必须是 1-${MAX_MSG_HISTORY_LIMIT} 的整数`);
|
|
19
|
+
}
|
|
20
|
+
const direction = args.direction ?? 'all';
|
|
21
|
+
if (direction !== 'in' && direction !== 'out' && direction !== 'all') {
|
|
22
|
+
return error('INVALID_DIRECTION', `direction 仅支持 in|out|all: ${String(args.direction)}`);
|
|
23
|
+
}
|
|
24
|
+
if (args.before !== undefined && (!Number.isFinite(args.before) || args.before < 0)) {
|
|
25
|
+
return error('INVALID_BEFORE', 'before 必须是有效时间');
|
|
26
|
+
}
|
|
27
|
+
if (args.after !== undefined && (!Number.isFinite(args.after) || args.after < 0)) {
|
|
28
|
+
return error('INVALID_AFTER', 'after 必须是有效时间');
|
|
29
|
+
}
|
|
30
|
+
if (args.before !== undefined && args.after !== undefined && args.after >= args.before) {
|
|
31
|
+
return error('INVALID_TIME_RANGE', 'after 必须早于 before');
|
|
32
|
+
}
|
|
33
|
+
const sessionsDir = options.sessionsDir ?? resolvePaths().sessionsDir;
|
|
34
|
+
const chatDir = chatDirPath(sessionsDir, 'aun', args.target, args.from);
|
|
35
|
+
const resolvedSession = resolveSessionFilter(chatDir, args.from, args.target, args.session ?? 'latest');
|
|
36
|
+
if (!resolvedSession.ok)
|
|
37
|
+
return resolvedSession;
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = readAllJsonlLines(messageLogPath(chatDir));
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
return error('HISTORY_READ_FAILED', cause instanceof Error ? cause.message : String(cause));
|
|
44
|
+
}
|
|
45
|
+
const messages = entries
|
|
46
|
+
.filter(entry => isMessageLogEntry(entry))
|
|
47
|
+
.filter(entry => !isTransientProtocolMessage(entry))
|
|
48
|
+
.filter(entry => resolvedSession.sessionId === undefined || entry.sessionId === resolvedSession.sessionId)
|
|
49
|
+
.filter(entry => direction === 'all' || entry.dir === direction)
|
|
50
|
+
.filter(entry => args.after === undefined || entry.ts > args.after)
|
|
51
|
+
.filter(entry => args.before === undefined || entry.ts < args.before)
|
|
52
|
+
.sort((left, right) => left.ts - right.ts)
|
|
53
|
+
.slice(-limit);
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
self_aid: args.from,
|
|
57
|
+
target_aid: args.target,
|
|
58
|
+
session_id: resolvedSession.sessionId,
|
|
59
|
+
count: messages.length,
|
|
60
|
+
messages,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function resolveSessionFilter(chatDir, selfAid, targetAid, requested) {
|
|
64
|
+
if (requested === 'all')
|
|
65
|
+
return { ok: true };
|
|
66
|
+
if (requested === 'latest') {
|
|
67
|
+
const active = readJsonFile(path.join(chatDir, 'active.json'));
|
|
68
|
+
if (!active?.id) {
|
|
69
|
+
return error('LATEST_SESSION_NOT_FOUND', '该 AID 对暂无最新会话');
|
|
70
|
+
}
|
|
71
|
+
if (!sessionBelongsToPair(active, selfAid, targetAid)) {
|
|
72
|
+
return error('SESSION_SCOPE_MISMATCH', 'active.json 不属于指定 AID 对');
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, sessionId: active.id };
|
|
75
|
+
}
|
|
76
|
+
if (!SESSION_ID_RE.test(requested)) {
|
|
77
|
+
return error('INVALID_SESSION_ID', `无效 session ID: ${requested}`);
|
|
78
|
+
}
|
|
79
|
+
const active = readJsonFile(path.join(chatDir, 'active.json'));
|
|
80
|
+
if (active?.id === requested) {
|
|
81
|
+
if (!sessionBelongsToPair(active, selfAid, targetAid)) {
|
|
82
|
+
return error('SESSION_SCOPE_MISMATCH', '指定会话不属于该 AID 对');
|
|
83
|
+
}
|
|
84
|
+
return { ok: true, sessionId: requested };
|
|
85
|
+
}
|
|
86
|
+
for (const sessionPath of [
|
|
87
|
+
path.join(chatDir, `${requested}.jsonl`),
|
|
88
|
+
path.join(chatDir, '_threads', `${requested}.jsonl`),
|
|
89
|
+
]) {
|
|
90
|
+
const session = readLastJsonlLine(sessionPath);
|
|
91
|
+
if (!session || session.id !== requested)
|
|
92
|
+
continue;
|
|
93
|
+
if (!sessionBelongsToPair(session, selfAid, targetAid)) {
|
|
94
|
+
return error('SESSION_SCOPE_MISMATCH', '指定会话不属于该 AID 对');
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, sessionId: requested };
|
|
97
|
+
}
|
|
98
|
+
return error('SESSION_NOT_FOUND', `在该 AID 对中找不到会话: ${requested}`);
|
|
99
|
+
}
|
|
100
|
+
function sessionBelongsToPair(session, selfAid, targetAid) {
|
|
101
|
+
if (session.channelType && session.channelType !== 'aun')
|
|
102
|
+
return false;
|
|
103
|
+
if (session.channelId && session.channelId !== targetAid)
|
|
104
|
+
return false;
|
|
105
|
+
const storedSelfAid = session.selfAID ?? session.selfId;
|
|
106
|
+
if (storedSelfAid && storedSelfAid !== selfAid)
|
|
107
|
+
return false;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
function isMessageLogEntry(value) {
|
|
111
|
+
if (!value || typeof value !== 'object')
|
|
112
|
+
return false;
|
|
113
|
+
const entry = value;
|
|
114
|
+
return Number.isFinite(entry.ts)
|
|
115
|
+
&& (entry.dir === 'in' || entry.dir === 'out')
|
|
116
|
+
&& typeof entry.from === 'string'
|
|
117
|
+
&& typeof entry.to === 'string'
|
|
118
|
+
&& typeof entry.content === 'string'
|
|
119
|
+
&& typeof entry.msgType === 'string';
|
|
120
|
+
}
|
|
121
|
+
function error(code, message) {
|
|
122
|
+
return { ok: false, code, error: message };
|
|
123
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { msgSend, msgPull, msgAck, msgRecall, msgOnline, } from './p2p.js';
|
|
2
|
+
export { msgHistory, MAX_MSG_HISTORY_LIMIT } from './history.js';
|
|
3
|
+
export { groupSend, groupPull, groupAck, groupCreate, groupInfo, groupList, groupUpdate, groupDissolve, groupSuspend, groupResume, groupJoin, groupLeave, groupInvite, groupKick, groupMembers, groupOnline, groupSetRole, groupTransferOwner, groupBan, groupUnban, groupBanlist, groupRules, groupUpdateRules, groupRulesFileGet, groupRulesFileSet, groupRulesFilePublish, } from './group.js';
|
|
4
|
+
export { uploadFileAndBuildPayload } from './upload.js';
|
|
5
|
+
export { inferPayloadType, isValidPayloadType } from './payload-type.js';
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createShortConnection } from '../rpc/index.js';
|
|
4
|
+
import { getAidStore, SLOT } from '../aid/store.js';
|
|
5
|
+
import { uploadFileAndBuildPayload } from './upload.js';
|
|
6
|
+
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from '../../core/message/message-log.js';
|
|
7
|
+
import { readBestTaskRuntimeContext, runtimeRefMessageIdForMsgSend } from '../../cli/task-context.js';
|
|
8
|
+
import { chatDirPath } from '../../core/session/session-fs-store.js';
|
|
9
|
+
import { resolvePaths } from '../../paths.js';
|
|
10
|
+
import { ipcQuery } from '../../ipc.js';
|
|
11
|
+
import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
|
|
12
|
+
const DEFAULT_TASK_EXECUTION_MS = 60 * 60 * 1000;
|
|
13
|
+
const DAEMON_SEND_TIMEOUT_GRACE_MS = 10_000;
|
|
14
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
15
|
+
export function daemonTaskSendIpcTimeoutMs(file = false) {
|
|
16
|
+
let executionMs = DEFAULT_TASK_EXECUTION_MS;
|
|
17
|
+
try {
|
|
18
|
+
const config = JSON.parse(fs.readFileSync(resolvePaths().daemonConfig, 'utf8'));
|
|
19
|
+
const seconds = config.idleMonitor?.maxExecutionTime;
|
|
20
|
+
if (typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0) {
|
|
21
|
+
executionMs = seconds * 1000;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
return Math.min(MAX_TIMER_MS, Math.max(file ? 120_000 : 5_000, executionMs + DAEMON_SEND_TIMEOUT_GRACE_MS));
|
|
26
|
+
}
|
|
27
|
+
function buildSimplePayload(body) {
|
|
28
|
+
switch (body.mode) {
|
|
29
|
+
case 'text':
|
|
30
|
+
return { type: 'text', text: body.text };
|
|
31
|
+
case 'payload':
|
|
32
|
+
return { ...body.payload };
|
|
33
|
+
case 'link': {
|
|
34
|
+
const payload = { type: 'link', url: body.url };
|
|
35
|
+
if (body.title)
|
|
36
|
+
payload.title = body.title;
|
|
37
|
+
if (body.description)
|
|
38
|
+
payload.description = body.description;
|
|
39
|
+
return payload;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function messageLogContent(body) {
|
|
44
|
+
return body.mode === 'text' ? body.text
|
|
45
|
+
: body.mode === 'link' ? `[link] ${body.url}`
|
|
46
|
+
: body.mode === 'file' ? `[file] ${body.filePath}`
|
|
47
|
+
: `[payload]`;
|
|
48
|
+
}
|
|
49
|
+
function applyRoutingPayloadFields(payload, args, runtimeContext, includeRuntimeRef = true) {
|
|
50
|
+
const threadId = msgSendThreadId(args, payload, runtimeContext);
|
|
51
|
+
if (threadId && !payload.thread_id)
|
|
52
|
+
payload.thread_id = threadId;
|
|
53
|
+
if (!includeRuntimeRef)
|
|
54
|
+
return;
|
|
55
|
+
const refMessageId = runtimeRefMessageIdForMsgSend({ from: args.from, runtime: runtimeContext });
|
|
56
|
+
if (refMessageId && !payload.ref_message_id)
|
|
57
|
+
payload.ref_message_id = refMessageId;
|
|
58
|
+
}
|
|
59
|
+
function msgSendThreadId(args, payload, runtimeContext) {
|
|
60
|
+
const payloadThreadId = typeof payload?.thread_id === 'string' && payload.thread_id.trim()
|
|
61
|
+
? payload.thread_id.trim()
|
|
62
|
+
: undefined;
|
|
63
|
+
const currentThreadId = runtimeContext?.channel === 'aun'
|
|
64
|
+
&& runtimeContext.chatType === 'private'
|
|
65
|
+
&& runtimeContext.selfAid === args.from
|
|
66
|
+
&& runtimeContext.channelId === args.to
|
|
67
|
+
? runtimeContext.threadId
|
|
68
|
+
: undefined;
|
|
69
|
+
return payloadThreadId || args.thread || currentThreadId;
|
|
70
|
+
}
|
|
71
|
+
function msgSendLogMetadata(args, payload, runtimeContext) {
|
|
72
|
+
const isInSession = !!process.env.EVOLCORE_SESSION_ID || !!runtimeContext;
|
|
73
|
+
const source = isInSession ? 'msg' : 'cli';
|
|
74
|
+
const classified = payload === undefined ? undefined : classifyAunPayloadForLog(payload);
|
|
75
|
+
const threadId = msgSendThreadId(args, payload && typeof payload === 'object' ? payload : undefined, runtimeContext);
|
|
76
|
+
const sessionId = runtimeContext?.channel === 'aun'
|
|
77
|
+
&& runtimeContext.chatType === 'private'
|
|
78
|
+
&& runtimeContext.selfAid === args.from
|
|
79
|
+
&& runtimeContext.peerId === args.to
|
|
80
|
+
? runtimeContext.sessionId
|
|
81
|
+
: undefined;
|
|
82
|
+
return {
|
|
83
|
+
content: classified?.content ?? messageLogContent(args.body),
|
|
84
|
+
sessionId,
|
|
85
|
+
threadId: threadId || null,
|
|
86
|
+
msgType: classified?.msgType,
|
|
87
|
+
payloadType: classified?.payloadType,
|
|
88
|
+
payloadSummary: classified?.payloadSummary,
|
|
89
|
+
source,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function tryDaemonMsgSend(args, payload, runtimeContext, file) {
|
|
93
|
+
const delegatedTask = !!process.env.EVOLCORE_SESSION_ID
|
|
94
|
+
|| !!process.env[AGENT_DELEGATION_TOKEN_ENV];
|
|
95
|
+
if (!runtimeContext) {
|
|
96
|
+
return delegatedTask
|
|
97
|
+
? { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task runtime context is unavailable' }
|
|
98
|
+
: null;
|
|
99
|
+
}
|
|
100
|
+
if (!runtimeContext.selfAid || runtimeContext.selfAid !== args.from) {
|
|
101
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task sender does not match the delegated agent' };
|
|
102
|
+
}
|
|
103
|
+
if (!runtimeContext.sessionId || !runtimeContext.messageId) {
|
|
104
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task runtime context is incomplete' };
|
|
105
|
+
}
|
|
106
|
+
if (args.slotId || args.aunPath) {
|
|
107
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task sends cannot override the daemon AUN connection' };
|
|
108
|
+
}
|
|
109
|
+
const thread = msgSendThreadId(args, payload, runtimeContext);
|
|
110
|
+
const response = await ipcQuery(resolvePaths().socket, {
|
|
111
|
+
type: 'aun-msg-send',
|
|
112
|
+
aid: args.from,
|
|
113
|
+
to: args.to,
|
|
114
|
+
scope: 'msg',
|
|
115
|
+
...(payload ? { payload } : { file }),
|
|
116
|
+
encrypt: args.encrypt === true,
|
|
117
|
+
thread,
|
|
118
|
+
returnPolicy: args.returnPolicy,
|
|
119
|
+
originSessionId: runtimeContext.sessionId,
|
|
120
|
+
originMessageId: runtimeContext.messageId,
|
|
121
|
+
delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
|
|
122
|
+
log: msgSendLogMetadata(args, payload, runtimeContext),
|
|
123
|
+
}, daemonTaskSendIpcTimeoutMs(!!file));
|
|
124
|
+
return response ?? {
|
|
125
|
+
ok: false,
|
|
126
|
+
code: 'AUN_DAEMON_UNAVAILABLE',
|
|
127
|
+
error: 'EvolCore daemon did not complete the delegated AUN send',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async function daemonMsgSendResult(args, payload, runtimeContext, daemonResult) {
|
|
131
|
+
if (!daemonResult.ok) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
error: daemonResult.error || 'daemon AUN message send failed',
|
|
135
|
+
code: daemonResult.code,
|
|
136
|
+
handoff_id: daemonResult.handoff_id,
|
|
137
|
+
target_session_id: daemonResult.target_session_id,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (!daemonResult.message_id && !daemonResult.handoff_id) {
|
|
141
|
+
return { ok: false, error: 'daemon AUN message send returned no message_id' };
|
|
142
|
+
}
|
|
143
|
+
const sent = {
|
|
144
|
+
ok: true,
|
|
145
|
+
message_id: daemonResult.message_id || daemonResult.handoff_id || '',
|
|
146
|
+
handoff_id: daemonResult.handoff_id,
|
|
147
|
+
target_session_id: daemonResult.target_session_id,
|
|
148
|
+
seq: daemonResult.seq,
|
|
149
|
+
timestamp: daemonResult.timestamp,
|
|
150
|
+
status: daemonResult.status,
|
|
151
|
+
delivery_mode: daemonResult.delivery_mode,
|
|
152
|
+
};
|
|
153
|
+
if (!daemonResult.handoff_id && !daemonResult.log_written) {
|
|
154
|
+
await appendMsgSendOutboundLog(args, sent, {
|
|
155
|
+
payload,
|
|
156
|
+
runtimeContext,
|
|
157
|
+
chatmode: daemonResult.chatmode,
|
|
158
|
+
encrypt: daemonResult.encrypt ?? args.encrypt === true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return sent;
|
|
162
|
+
}
|
|
163
|
+
async function appendMsgSendOutboundLog(args, result, opts) {
|
|
164
|
+
const sessionsDir = resolvePaths().sessionsDir;
|
|
165
|
+
const chatDir = chatDirPath(sessionsDir, 'aun', args.to, args.from);
|
|
166
|
+
const log = msgSendLogMetadata(args, opts.payload, opts.runtimeContext);
|
|
167
|
+
fs.mkdirSync(chatDir, { recursive: true });
|
|
168
|
+
appendMessageLog(chatDir, buildOutboundEntry({
|
|
169
|
+
from: args.from,
|
|
170
|
+
to: args.to,
|
|
171
|
+
sessionId: log.sessionId,
|
|
172
|
+
threadId: log.threadId,
|
|
173
|
+
chatType: 'private',
|
|
174
|
+
msgId: result.message_id,
|
|
175
|
+
content: log.content,
|
|
176
|
+
encrypt: opts.encrypt === true,
|
|
177
|
+
chatmode: opts.chatmode,
|
|
178
|
+
msgType: log.msgType,
|
|
179
|
+
payloadType: log.payloadType,
|
|
180
|
+
payloadSummary: log.payloadSummary,
|
|
181
|
+
source: log.source,
|
|
182
|
+
}));
|
|
183
|
+
// 通知 daemon 更新 stats(如果 daemon 在运行)
|
|
184
|
+
try {
|
|
185
|
+
await ipcQuery(resolvePaths().socket, {
|
|
186
|
+
type: 'aun-aid-stats-record-outbound',
|
|
187
|
+
aid: args.from,
|
|
188
|
+
toPeer: args.to,
|
|
189
|
+
text: log.content,
|
|
190
|
+
encrypt: opts.encrypt === true,
|
|
191
|
+
chatmode: opts.chatmode,
|
|
192
|
+
}, 1000);
|
|
193
|
+
}
|
|
194
|
+
catch { /* daemon 不在或 IPC 失败都忽略 */ }
|
|
195
|
+
}
|
|
196
|
+
export async function msgSend(args) {
|
|
197
|
+
const runtimeContext = await readBestTaskRuntimeContext();
|
|
198
|
+
let conn;
|
|
199
|
+
try {
|
|
200
|
+
let payload;
|
|
201
|
+
if (args.body.mode !== 'file') {
|
|
202
|
+
payload = buildSimplePayload(args.body);
|
|
203
|
+
applyRoutingPayloadFields(payload, args, runtimeContext, false);
|
|
204
|
+
const daemonResult = await tryDaemonMsgSend(args, payload, runtimeContext);
|
|
205
|
+
if (daemonResult)
|
|
206
|
+
return daemonMsgSendResult(args, payload, runtimeContext, daemonResult);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
const daemonResult = await tryDaemonMsgSend(args, undefined, runtimeContext, {
|
|
210
|
+
filePath: path.resolve(args.body.filePath),
|
|
211
|
+
as: args.body.as,
|
|
212
|
+
contentType: args.body.contentType,
|
|
213
|
+
text: args.body.text,
|
|
214
|
+
transcript: args.body.transcript,
|
|
215
|
+
});
|
|
216
|
+
if (daemonResult) {
|
|
217
|
+
return daemonMsgSendResult(args, { type: args.body.as || 'file' }, runtimeContext, daemonResult);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
221
|
+
if (args.body.mode === 'file') {
|
|
222
|
+
const built = await uploadFileAndBuildPayload(conn, args.from, args.body.filePath, {
|
|
223
|
+
as: args.body.as,
|
|
224
|
+
contentType: args.body.contentType,
|
|
225
|
+
text: args.body.text,
|
|
226
|
+
transcript: args.body.transcript,
|
|
227
|
+
onProgress: args.onUploadProgress,
|
|
228
|
+
});
|
|
229
|
+
payload = built.payload;
|
|
230
|
+
applyRoutingPayloadFields(payload, args, runtimeContext, false);
|
|
231
|
+
const daemonResult = await tryDaemonMsgSend(args, payload, runtimeContext);
|
|
232
|
+
if (daemonResult)
|
|
233
|
+
return daemonMsgSendResult(args, payload, runtimeContext, daemonResult);
|
|
234
|
+
}
|
|
235
|
+
// 1. 解析对端身份(30天缓存)。身份解析走 HTTP+PKI(store),与发消息的短连接无关。
|
|
236
|
+
const { agentsDir } = resolvePaths();
|
|
237
|
+
const selfAgentDir = path.join(agentsDir, args.from);
|
|
238
|
+
const { PeerIdentityCache } = await import('../../core/relation/peer-identity.js');
|
|
239
|
+
const idStore = await getAidStore({ slotId: args.slotId ?? SLOT.cli, aunPath: args.aunPath });
|
|
240
|
+
let peerIdentity;
|
|
241
|
+
try {
|
|
242
|
+
peerIdentity = await PeerIdentityCache.resolve('aun', args.to, selfAgentDir, idStore, false);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
try {
|
|
246
|
+
idStore.close();
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore */ }
|
|
249
|
+
}
|
|
250
|
+
// 2. 决定 chatmode(遵循来源1-3)
|
|
251
|
+
// 私聊:非 human 对端 → proactive,human 对端 → interactive
|
|
252
|
+
const chatmode = peerIdentity.isAgent ? 'proactive' : 'interactive';
|
|
253
|
+
// 3. 构建 payload
|
|
254
|
+
if (!payload) {
|
|
255
|
+
payload = buildSimplePayload(args.body);
|
|
256
|
+
}
|
|
257
|
+
// 4. 写入 payload.chatmode
|
|
258
|
+
payload.chatmode = chatmode;
|
|
259
|
+
// 5. 写入 payload.thread_id/ref_message_id(如果指定)
|
|
260
|
+
applyRoutingPayloadFields(payload, args, runtimeContext, false);
|
|
261
|
+
applyRoutingPayloadFields(payload, args, runtimeContext);
|
|
262
|
+
const sendParams = { to: args.to, payload };
|
|
263
|
+
// Default: plaintext. Set encrypt: true to enable E2EE.
|
|
264
|
+
sendParams.encrypt = args.encrypt === true;
|
|
265
|
+
const result = await conn.call('message.send', sendParams);
|
|
266
|
+
// 5. 写出方向 jsonl(与 daemon 一致格式,标记 source)
|
|
267
|
+
// source 标记:
|
|
268
|
+
// - 'cli': 用户手动调用 ec msg send
|
|
269
|
+
// - 'msg': agent 在会话中调用 ec msg send
|
|
270
|
+
if (result?.message_id) {
|
|
271
|
+
try {
|
|
272
|
+
await appendMsgSendOutboundLog(args, { ok: true, message_id: result.message_id }, {
|
|
273
|
+
payload,
|
|
274
|
+
runtimeContext,
|
|
275
|
+
chatmode,
|
|
276
|
+
encrypt: args.encrypt === true,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch { }
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
ok: true,
|
|
283
|
+
message_id: result?.message_id,
|
|
284
|
+
seq: result?.seq,
|
|
285
|
+
timestamp: result?.timestamp,
|
|
286
|
+
status: result?.status,
|
|
287
|
+
delivery_mode: result?.delivery_mode,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
return formatRpcError(e);
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
if (conn)
|
|
295
|
+
await conn.close();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export async function msgPull(args) {
|
|
299
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
300
|
+
try {
|
|
301
|
+
const params = {};
|
|
302
|
+
if (args.afterSeq !== undefined)
|
|
303
|
+
params.after_seq = args.afterSeq;
|
|
304
|
+
if (args.limit !== undefined)
|
|
305
|
+
params.limit = args.limit;
|
|
306
|
+
const result = await conn.call('message.pull', params);
|
|
307
|
+
return {
|
|
308
|
+
ok: true,
|
|
309
|
+
messages: result?.messages ?? [],
|
|
310
|
+
count: result?.count ?? 0,
|
|
311
|
+
latest_seq: result?.latest_seq ?? 0,
|
|
312
|
+
earliest_available_seq: result?.earliest_available_seq ?? null,
|
|
313
|
+
ephemeral_earliest_available_seq: result?.ephemeral_earliest_available_seq ?? null,
|
|
314
|
+
ephemeral_dropped_count: result?.ephemeral_dropped_count ?? 0,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
return formatRpcError(e);
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
await conn.close();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export async function msgAck(args) {
|
|
325
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
326
|
+
try {
|
|
327
|
+
const result = await conn.call('message.ack', { seq: args.seq });
|
|
328
|
+
return {
|
|
329
|
+
ok: true,
|
|
330
|
+
ack_seq: result?.ack_seq ?? args.seq,
|
|
331
|
+
event_published: result?.event_published,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
return formatRpcError(e);
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
await conn.close();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
export async function msgRecall(args) {
|
|
342
|
+
if (args.messageIds.length === 0) {
|
|
343
|
+
return { ok: false, error: 'message_ids 不能为空' };
|
|
344
|
+
}
|
|
345
|
+
if (args.messageIds.length > 100) {
|
|
346
|
+
return { ok: false, error: 'message_ids 最多 100 个' };
|
|
347
|
+
}
|
|
348
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
349
|
+
try {
|
|
350
|
+
const result = await conn.call('message.recall', { message_ids: args.messageIds });
|
|
351
|
+
return {
|
|
352
|
+
ok: true,
|
|
353
|
+
accepted: result?.accepted ?? 0,
|
|
354
|
+
recalled: result?.recalled ?? 0,
|
|
355
|
+
errors: result?.errors ?? null,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
return formatRpcError(e);
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
await conn.close();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
export async function msgOnline(args) {
|
|
366
|
+
if (args.targets.length === 0) {
|
|
367
|
+
return { ok: false, error: '查询目标不能为空' };
|
|
368
|
+
}
|
|
369
|
+
if (args.targets.length > 100) {
|
|
370
|
+
return { ok: false, error: '一次最多查询 100 个 AID' };
|
|
371
|
+
}
|
|
372
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
373
|
+
try {
|
|
374
|
+
const result = await conn.call('message.query_online', { aids: args.targets });
|
|
375
|
+
return {
|
|
376
|
+
ok: true,
|
|
377
|
+
online: result?.online ?? {},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
return formatRpcError(e);
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
await conn.close();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// ==================== Internal ====================
|
|
388
|
+
function formatRpcError(e) {
|
|
389
|
+
if (e?.code !== undefined && e?.message !== undefined) {
|
|
390
|
+
return { ok: false, error: String(e.message), code: e.code };
|
|
391
|
+
}
|
|
392
|
+
return { ok: false, error: String(e?.message ?? e) };
|
|
393
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
const EXT_TYPE_MAP = {
|
|
3
|
+
// image
|
|
4
|
+
'.png': 'image', '.jpg': 'image', '.jpeg': 'image',
|
|
5
|
+
'.gif': 'image', '.webp': 'image', '.svg': 'image',
|
|
6
|
+
'.bmp': 'image', '.heic': 'image', '.heif': 'image',
|
|
7
|
+
// video
|
|
8
|
+
'.mp4': 'video', '.mov': 'video', '.webm': 'video',
|
|
9
|
+
'.avi': 'video', '.mkv': 'video', '.m4v': 'video',
|
|
10
|
+
// voice
|
|
11
|
+
'.opus': 'voice', '.mp3': 'voice', '.aac': 'voice',
|
|
12
|
+
'.m4a': 'voice', '.wav': 'voice', '.flac': 'voice', '.ogg': 'voice',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* 按扩展名推断 payload.type。
|
|
16
|
+
* 未识别的扩展名归类为 'file'。
|
|
17
|
+
*/
|
|
18
|
+
export function inferPayloadType(filename) {
|
|
19
|
+
const ext = path.extname(filename).toLowerCase();
|
|
20
|
+
return EXT_TYPE_MAP[ext] ?? 'file';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 校验显式传入的 --as 值。
|
|
24
|
+
*/
|
|
25
|
+
export function isValidPayloadType(value) {
|
|
26
|
+
return value === 'image' || value === 'video' || value === 'voice' || value === 'file';
|
|
27
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { guessMime, formatSize } from '../../utils/media-cache.js';
|
|
5
|
+
import { inferPayloadType, isValidPayloadType } from './payload-type.js';
|
|
6
|
+
/** 单次上传最大大小(与 daemon sendFile 一致)。 */
|
|
7
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
8
|
+
/** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
|
|
9
|
+
function isInlineLimitError(err) {
|
|
10
|
+
const msg = err?.message ?? '';
|
|
11
|
+
return /inline\s*上限|inline limit|create_upload_session/i.test(msg);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 上传本地文件并构造发送用的 payload。
|
|
15
|
+
*
|
|
16
|
+
* 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
|
|
17
|
+
* 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
|
|
18
|
+
* 此时通过 onProgress 上报字节级进度。
|
|
19
|
+
*/
|
|
20
|
+
export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
|
|
21
|
+
const absPath = path.resolve(filePath);
|
|
22
|
+
if (!fs.existsSync(absPath)) {
|
|
23
|
+
throw new Error(`文件不存在: ${absPath}`);
|
|
24
|
+
}
|
|
25
|
+
const stat = fs.statSync(absPath);
|
|
26
|
+
if (stat.size === 0) {
|
|
27
|
+
throw new Error(`文件为空: ${absPath}`);
|
|
28
|
+
}
|
|
29
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
30
|
+
throw new Error(`文件过大 (${formatSize(stat.size)}, 上限 ${formatSize(MAX_FILE_SIZE)}): ${absPath}`);
|
|
31
|
+
}
|
|
32
|
+
const filename = path.basename(absPath);
|
|
33
|
+
const fileData = fs.readFileSync(absPath);
|
|
34
|
+
const sha256 = crypto.createHash('sha256').update(fileData).digest('hex');
|
|
35
|
+
const contentType = opts?.contentType ?? guessMime(filename);
|
|
36
|
+
const objectKey = `shared/${crypto.randomUUID()}/${filename}`;
|
|
37
|
+
const total = stat.size;
|
|
38
|
+
const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
|
|
39
|
+
// 1. 先按 inline 走
|
|
40
|
+
let inlineRejected = false;
|
|
41
|
+
try {
|
|
42
|
+
report('inline', 0);
|
|
43
|
+
await conn.call('storage.put_object', {
|
|
44
|
+
object_key: objectKey,
|
|
45
|
+
content: fileData.toString('base64'),
|
|
46
|
+
content_type: contentType,
|
|
47
|
+
is_private: false,
|
|
48
|
+
overwrite: true,
|
|
49
|
+
});
|
|
50
|
+
report('inline', total);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (!isInlineLimitError(e))
|
|
54
|
+
throw e;
|
|
55
|
+
inlineRejected = true;
|
|
56
|
+
}
|
|
57
|
+
// 2. inline 被拒:降级到 session + HTTP PUT
|
|
58
|
+
if (inlineRejected) {
|
|
59
|
+
report('session-create', 0);
|
|
60
|
+
const session = await conn.call('storage.create_upload_session', {
|
|
61
|
+
object_key: objectKey,
|
|
62
|
+
size_bytes: total,
|
|
63
|
+
content_type: contentType,
|
|
64
|
+
});
|
|
65
|
+
const uploadUrl = session?.upload_url;
|
|
66
|
+
if (!uploadUrl)
|
|
67
|
+
throw new Error('storage.create_upload_session 未返回 upload_url');
|
|
68
|
+
// 简单 Buffer 上传 + 周期性进度(不用流,避免某些 storage 网关对 chunked / duplex 不友好)
|
|
69
|
+
report('http-put', 0);
|
|
70
|
+
const PROGRESS_TICK_MS = 250;
|
|
71
|
+
let lastBytes = 0;
|
|
72
|
+
const tickerStart = Date.now();
|
|
73
|
+
// 简单的“估算”进度:实际上一次性发,但在等待响应期间按 elapsed 模拟字节数,让用户看到在跑
|
|
74
|
+
const ticker = setInterval(() => {
|
|
75
|
+
const elapsed = Date.now() - tickerStart;
|
|
76
|
+
// 假设 2MB/s 估算;不超过 99%
|
|
77
|
+
const estimated = Math.min(total - 1, Math.floor((elapsed / 1000) * 2 * 1024 * 1024));
|
|
78
|
+
if (estimated > lastBytes) {
|
|
79
|
+
lastBytes = estimated;
|
|
80
|
+
report('http-put', estimated);
|
|
81
|
+
}
|
|
82
|
+
}, PROGRESS_TICK_MS);
|
|
83
|
+
let uploadResp;
|
|
84
|
+
try {
|
|
85
|
+
uploadResp = await fetch(uploadUrl, {
|
|
86
|
+
method: 'PUT',
|
|
87
|
+
body: new Blob([new Uint8Array(fileData)], { type: contentType }),
|
|
88
|
+
headers: { 'Content-Type': contentType },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
clearInterval(ticker);
|
|
93
|
+
}
|
|
94
|
+
if (!uploadResp.ok)
|
|
95
|
+
throw new Error(`HTTP 上传失败: ${uploadResp.status}`);
|
|
96
|
+
report('http-put', total);
|
|
97
|
+
report('session-complete', total);
|
|
98
|
+
await conn.call('storage.complete_upload', {
|
|
99
|
+
object_key: objectKey,
|
|
100
|
+
sha256,
|
|
101
|
+
content_type: contentType,
|
|
102
|
+
is_private: false,
|
|
103
|
+
size_bytes: total,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
report('done', total);
|
|
107
|
+
const attachment = {
|
|
108
|
+
owner_aid: ownerAid,
|
|
109
|
+
object_key: objectKey,
|
|
110
|
+
filename,
|
|
111
|
+
size_bytes: total,
|
|
112
|
+
sha256,
|
|
113
|
+
content_type: contentType,
|
|
114
|
+
};
|
|
115
|
+
// 确定渲染类型
|
|
116
|
+
let type;
|
|
117
|
+
if (opts?.as) {
|
|
118
|
+
if (!isValidPayloadType(opts.as)) {
|
|
119
|
+
throw new Error(`--as 必须是 image|video|voice|file,收到: ${opts.as}`);
|
|
120
|
+
}
|
|
121
|
+
type = opts.as;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
type = inferPayloadType(filename);
|
|
125
|
+
}
|
|
126
|
+
const payload = {
|
|
127
|
+
type,
|
|
128
|
+
attachments: [attachment],
|
|
129
|
+
};
|
|
130
|
+
if (opts?.text)
|
|
131
|
+
payload.text = opts.text;
|
|
132
|
+
else if (type === 'file')
|
|
133
|
+
payload.text = `📎 ${filename} (${formatSize(total)})`;
|
|
134
|
+
if (type === 'voice' && opts?.transcript)
|
|
135
|
+
payload.transcript = opts.transcript;
|
|
136
|
+
return { payload, type, attachment };
|
|
137
|
+
}
|