evolcore 0.0.25 → 0.0.27

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.
Files changed (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  import crypto from 'crypto';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
- import { guessMime, formatSize } from '../../utils/media-cache.js';
4
+ import { formatSize } from '../../utils/media-cache.js';
5
5
  const EXT_TYPE_MAP = {
6
6
  '.png': 'image', '.jpg': 'image', '.jpeg': 'image',
7
7
  '.gif': 'image', '.webp': 'image', '.svg': 'image',
@@ -11,6 +11,15 @@ const EXT_TYPE_MAP = {
11
11
  '.opus': 'voice', '.mp3': 'voice', '.aac': 'voice',
12
12
  '.m4a': 'voice', '.wav': 'voice', '.flac': 'voice', '.ogg': 'voice',
13
13
  };
14
+ const EXT_CONTENT_TYPE_MAP = {
15
+ '.txt': 'text/plain', '.md': 'text/markdown', '.csv': 'text/csv',
16
+ '.json': 'application/json', '.xml': 'application/xml', '.html': 'text/html',
17
+ '.pdf': 'application/pdf', '.zip': 'application/zip',
18
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
19
+ '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
20
+ '.mp4': 'video/mp4', '.mov': 'video/quicktime', '.webm': 'video/webm',
21
+ '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg',
22
+ };
14
23
  export function inferPayloadType(filename) {
15
24
  const ext = path.extname(filename).toLowerCase();
16
25
  return EXT_TYPE_MAP[ext] ?? 'file';
@@ -20,101 +29,57 @@ export function isValidPayloadType(value) {
20
29
  }
21
30
  /** 单次上传最大大小(与 daemon sendFile 一致)。 */
22
31
  export const MAX_AUN_ATTACHMENT_SIZE = 10 * 1024 * 1024;
23
- /** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
24
- function isInlineLimitError(err) {
25
- const msg = err?.message ?? '';
26
- return /inline\s*上限|inline limit|create_upload_session/i.test(msg);
27
- }
28
32
  /**
29
33
  * 上传内存中的二进制内容并构造发送用的标准附件 payload。
30
34
  */
31
35
  export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename, opts) {
32
- const safeFilename = path.basename(filename.trim());
33
- if (!safeFilename || safeFilename === '.' || safeFilename === '..') {
36
+ const displayFilename = path.basename(filename);
37
+ if (!displayFilename.trim() || displayFilename === '.' || displayFilename === '..') {
34
38
  throw new Error('文件名不能为空');
35
39
  }
36
40
  if (data.length === 0) {
37
- throw new Error(`文件为空: ${safeFilename}`);
41
+ throw new Error(`文件为空: ${displayFilename}`);
38
42
  }
39
43
  if (data.length > MAX_AUN_ATTACHMENT_SIZE) {
40
- throw new Error(`文件过大 (${formatSize(data.length)}, 上限 ${formatSize(MAX_AUN_ATTACHMENT_SIZE)}): ${safeFilename}`);
44
+ throw new Error(`文件过大 (${formatSize(data.length)}, 上限 ${formatSize(MAX_AUN_ATTACHMENT_SIZE)}): ${displayFilename}`);
41
45
  }
42
46
  const sha256 = crypto.createHash('sha256').update(data).digest('hex');
43
- const contentType = opts?.contentType ?? guessMime(safeFilename);
44
- const objectKey = `shared/${crypto.randomUUID()}/${safeFilename}`;
47
+ const explicitContentType = opts?.contentType?.trim() || undefined;
48
+ // Storage keys have a narrower character set than local filenames. Keep the
49
+ // original basename in the attachment; only the opaque storage key is sanitized.
50
+ const storageFilename = displayFilename.replace(/[^A-Za-z0-9._-]/g, '_');
51
+ const objectKey = `shared/${crypto.randomUUID()}/${storageFilename}`;
45
52
  const total = data.length;
46
53
  const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
47
- // 1. 先按 inline 走
48
- let inlineRejected = false;
49
- try {
50
- report('inline', 0);
51
- await conn.call('storage.put_object', {
52
- object_key: objectKey,
53
- content: data.toString('base64'),
54
- content_type: contentType,
55
- is_private: false,
56
- overwrite: true,
57
- });
58
- report('inline', total);
59
- }
60
- catch (e) {
61
- if (!isInlineLimitError(e))
62
- throw e;
63
- inlineRejected = true;
64
- }
65
- // 2. inline 被拒:降级到 session + HTTP PUT
66
- if (inlineRejected) {
67
- report('session-create', 0);
68
- const session = await conn.call('storage.create_upload_session', {
69
- object_key: objectKey,
70
- size_bytes: total,
71
- content_type: contentType,
72
- });
73
- const uploadUrl = session?.upload_url;
74
- if (!uploadUrl)
75
- throw new Error('storage.create_upload_session 未返回 upload_url');
76
- // 简单 Buffer 上传 + 周期性进度(不用流,避免某些 storage 网关对 chunked / duplex 不友好)
77
- report('http-put', 0);
78
- const PROGRESS_TICK_MS = 250;
79
- let lastBytes = 0;
80
- const tickerStart = Date.now();
81
- // 实际上一次性发送;等待响应期间按 2MB/s 估算进度,且不超过 99%。
82
- const ticker = setInterval(() => {
83
- const elapsed = Date.now() - tickerStart;
84
- const estimated = Math.min(total - 1, Math.floor((elapsed / 1000) * 2 * 1024 * 1024));
85
- if (estimated > lastBytes) {
86
- lastBytes = estimated;
87
- report('http-put', estimated);
88
- }
89
- }, PROGRESS_TICK_MS);
90
- let uploadResp;
54
+ // Use the client-owned facade so storage RPCs stay inside the SDK. Do not
55
+ // rebuild StorageVFS over conn.call: SDK-internal RPCs are not application APIs.
56
+ report('upload', 0);
57
+ const uploaded = await conn.storage.writeBytes(`/${objectKey}`, data, {
58
+ contentType: explicitContentType,
59
+ public: true,
60
+ overwrite: true,
61
+ });
62
+ // Use the stored MIME instead of inferring a second, potentially different
63
+ // type for the attachment. Older storage responses may omit this metadata.
64
+ let contentType = uploaded.contentType?.trim() || explicitContentType;
65
+ if (!contentType && typeof conn.storage.stat === 'function') {
91
66
  try {
92
- uploadResp = await fetch(uploadUrl, {
93
- method: 'PUT',
94
- body: new Blob([new Uint8Array(data)], { type: contentType }),
95
- headers: { 'Content-Type': contentType },
96
- });
67
+ contentType = (await conn.storage.stat(`/${objectKey}`)).contentType?.trim();
97
68
  }
98
- finally {
99
- clearInterval(ticker);
69
+ catch {
70
+ // A successful upload should remain usable when a legacy facade cannot
71
+ // perform a follow-up stat call.
100
72
  }
101
- if (!uploadResp.ok)
102
- throw new Error(`HTTP 上传失败: ${uploadResp.status}`);
103
- report('http-put', total);
104
- report('session-complete', total);
105
- await conn.call('storage.complete_upload', {
106
- object_key: objectKey,
107
- sha256,
108
- content_type: contentType,
109
- is_private: false,
110
- size_bytes: total,
111
- });
112
73
  }
74
+ if (!contentType)
75
+ contentType = EXT_CONTENT_TYPE_MAP[path.extname(displayFilename).toLowerCase()] ?? 'application/octet-stream';
76
+ if (!contentType)
77
+ throw new Error('Storage 上传结果缺少 Content-Type');
113
78
  report('done', total);
114
79
  const attachment = {
115
80
  owner_aid: ownerAid,
116
81
  object_key: objectKey,
117
- filename: safeFilename,
82
+ filename: displayFilename,
118
83
  size_bytes: total,
119
84
  sha256,
120
85
  content_type: contentType,
@@ -127,7 +92,7 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
127
92
  type = opts.as;
128
93
  }
129
94
  else {
130
- type = inferPayloadType(safeFilename);
95
+ type = inferPayloadType(displayFilename);
131
96
  }
132
97
  const payload = {
133
98
  type,
@@ -136,7 +101,7 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
136
101
  if (opts?.text)
137
102
  payload.text = opts.text;
138
103
  else if (type === 'file')
139
- payload.text = `📎 ${safeFilename} (${formatSize(total)})`;
104
+ payload.text = `📎 ${displayFilename} (${formatSize(total)})`;
140
105
  if (type === 'voice' && opts?.transcript)
141
106
  payload.transcript = opts.transcript;
142
107
  return { payload, type, attachment };
@@ -144,9 +109,7 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
144
109
  /**
145
110
  * 上传本地文件并构造发送用的 payload。
146
111
  *
147
- * 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
148
- * 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
149
- * 此时通过 onProgress 上报字节级进度。
112
+ * 通过 SDK StorageVFS 协商并执行上传,onProgress 上报上传阶段。
150
113
  */
151
114
  export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
152
115
  const absPath = path.resolve(filePath);
@@ -115,14 +115,14 @@ function writeEntries(aid, entries, queue = 'default') {
115
115
  atomicWrite(file, content);
116
116
  }
117
117
  function trimFinalEntries(entries) {
118
- const finalEntries = entries.filter(entry => !!entry.terminal || !!entry.deliveryResult);
118
+ const finalEntries = entries.filter(entry => !entry.critical && (!!entry.terminal || !!entry.deliveryResult));
119
119
  if (finalEntries.length <= MAX_TERMINAL_ENTRIES_PER_AID)
120
120
  return entries;
121
121
  const retainedFinalIds = new Set(finalEntries
122
122
  .sort((left, right) => (left.terminal?.at ?? left.deliveryResult?.at ?? left.ts) - (right.terminal?.at ?? right.deliveryResult?.at ?? right.ts))
123
123
  .slice(-MAX_TERMINAL_ENTRIES_PER_AID)
124
124
  .map(entry => entry.id));
125
- return entries.filter(entry => (!entry.terminal && !entry.deliveryResult) || retainedFinalIds.has(entry.id));
125
+ return entries.filter(entry => entry.critical || (!entry.terminal && !entry.deliveryResult) || retainedFinalIds.has(entry.id));
126
126
  }
127
127
  export function enqueue(aid, opts) {
128
128
  const queue = opts.queue ?? 'default';
@@ -138,12 +138,11 @@ export function enqueue(aid, opts) {
138
138
  markExpired(existing);
139
139
  }
140
140
  if (opts.dedupeKey) {
141
- // Terminal results are decisions for this logical operation. Reusing the
142
- // same route must not silently turn a permanent rejection into a retry;
143
- // an explicit route correction is the only case that replaces it.
141
+ // A terminal result or a delivery receipt is also a decision for this
142
+ // logical operation. Reusing the same route must return that decision
143
+ // instead of silently creating a second send attempt. Route correction
144
+ // remains the one intentional replacement path.
144
145
  const existing = existingEntries.find(entry => entry.dedupeKey === opts.dedupeKey
145
- && !entry.terminal
146
- && !entry.deliveryResult
147
146
  && !isExpired(entry));
148
147
  if (existing
149
148
  && existing.channelId === opts.channelId
@@ -323,6 +322,17 @@ export function markTerminal(aid, id, failure, submitted, queue = submitted ? en
323
322
  writeEntries(aid, entries, queue);
324
323
  return true;
325
324
  }
325
+ /** Keep the latest transient failure visible without changing the operation or its route. */
326
+ export function recordRetry(aid, submitted, failure) {
327
+ const queue = entryQueue(submitted);
328
+ const entries = readEntries(aid, queue);
329
+ const entry = entries.find(candidate => candidate.id === submitted.id);
330
+ if (!entry || !hasSamePersistedRoute(entry, submitted) || entry.terminal || entry.deliveryResult)
331
+ return;
332
+ entry.lastError = failure.error ?? 'delivery is pending';
333
+ entry.lastErrorCode = failure.code;
334
+ writeEntries(aid, entries, queue);
335
+ }
326
336
  /** Retain a bounded success receipt for queued Trigger history reconciliation. */
327
337
  export function markDelivered(aid, id, messageId, submitted, queue = submitted ? entryQueue(submitted) : 'default') {
328
338
  const entries = readEntries(aid, queue);
@@ -364,6 +374,18 @@ async function drainOnce(aid, sender, queue) {
364
374
  let permanent = 0;
365
375
  const remaining = [];
366
376
  for (const entry of entries) {
377
+ // Cancellation or another sender may have changed an
378
+ // entry while this drain was awaiting a previous network call.
379
+ if (entry.critical) {
380
+ const currentEntry = readEntries(aid, queue).find(candidate => candidate.id === entry.id);
381
+ if (!currentEntry || !hasSamePersistedRoute(currentEntry, entry))
382
+ continue;
383
+ if (currentEntry.terminal || currentEntry.deliveryResult) {
384
+ remaining.push(currentEntry);
385
+ continue;
386
+ }
387
+ Object.assign(entry, currentEntry);
388
+ }
367
389
  if (isExpired(entry)) {
368
390
  expired++;
369
391
  markExpired(entry);
@@ -5,6 +5,7 @@ export async function createShortConnection(aid, opts) {
5
5
  const client = await loadClient(store, aid);
6
6
  await client.connect({ connection_kind: 'short', short_ttl_ms: opts?.shortTtlMs ?? 30000, auto_reconnect: false });
7
7
  return {
8
+ get storage() { return client.storage; },
8
9
  async call(method, params) {
9
10
  return client.call(method, params);
10
11
  },
@@ -1,9 +1,10 @@
1
1
  import fs from 'fs';
2
- import crypto from 'crypto';
3
- import { rpcCall } from '../rpc/index.js';
2
+ import { createShortConnection, rpcCall } from '../rpc/index.js';
4
3
  function rpcErrorToString(error) {
5
4
  if (!error)
6
5
  return 'unknown error';
6
+ if (error instanceof Error)
7
+ return error.message;
7
8
  if (typeof error === 'string')
8
9
  return error;
9
10
  try {
@@ -13,67 +14,55 @@ function rpcErrorToString(error) {
13
14
  return String(error);
14
15
  }
15
16
  }
16
- function pickUrl(value) {
17
- return typeof value === 'string' && value.trim() ? value : undefined;
18
- }
19
17
  export async function storageUpload(aid, localFile, remotePath, opts) {
20
18
  const fileBuffer = Buffer.isBuffer(localFile) ? localFile : fs.readFileSync(localFile);
21
19
  return storageUploadBuffer(aid, fileBuffer, remotePath, opts);
22
20
  }
23
21
  export async function storageUploadBuffer(aid, fileBuffer, remotePath, opts) {
24
- const contentType = opts?.contentType ?? 'application/octet-stream';
25
- const sha256 = crypto.createHash('sha256').update(fileBuffer).digest('hex');
26
- const createResult = await rpcCall(aid, 'storage.create_upload_session', {
27
- object_key: remotePath,
28
- content_type: contentType,
29
- size_bytes: fileBuffer.length,
30
- }, { aunPath: opts?.aunPath });
31
- if (!createResult.ok) {
32
- return { ok: false, objectKey: remotePath, error: rpcErrorToString(createResult.error) };
33
- }
34
- const uploadUrl = createResult.result.upload_url;
35
- if (!uploadUrl) {
36
- return { ok: false, objectKey: remotePath, error: 'storage.create_upload_session did not return upload_url' };
37
- }
38
- const putResp = await fetch(uploadUrl, {
39
- method: 'PUT',
40
- headers: { 'Content-Type': contentType },
41
- body: new Blob([new Uint8Array(fileBuffer)], { type: contentType }),
42
- redirect: 'follow',
43
- });
44
- if (!putResp.ok) {
45
- return { ok: false, objectKey: remotePath, error: `HTTP PUT failed: ${putResp.status}` };
22
+ let conn;
23
+ try {
24
+ conn = await createShortConnection(aid, { aunPath: opts?.aunPath });
25
+ const uploaded = await conn.storage.writeBytes(remotePath, fileBuffer, {
26
+ contentType: opts?.contentType,
27
+ public: opts?.isPublic ?? false,
28
+ overwrite: true,
29
+ });
30
+ const objectKey = uploaded.path.replace(/^\/+/, '');
31
+ if (!objectKey)
32
+ throw new Error('Storage 上传结果缺少文件路径');
33
+ // agent.md avatars require a stable public URL, never an expiring ticket.
34
+ const publicUrl = opts?.isPublic
35
+ ? `https://${aid}/${objectKey.split('/').map(encodeURIComponent).join('/')}`
36
+ : undefined;
37
+ return { ok: true, objectKey, publicUrl };
46
38
  }
47
- const completeResult = await rpcCall(aid, 'storage.complete_upload', {
48
- object_key: remotePath,
49
- sha256,
50
- content_type: contentType,
51
- size_bytes: fileBuffer.length,
52
- is_private: !opts?.isPublic,
53
- }, { aunPath: opts?.aunPath });
54
- if (!completeResult.ok) {
55
- return { ok: false, objectKey: remotePath, error: rpcErrorToString(completeResult.error) };
39
+ catch (error) {
40
+ return { ok: false, objectKey: remotePath, error: rpcErrorToString(error) };
56
41
  }
57
- let publicUrl = pickUrl(completeResult.result?.url);
58
- if (opts?.isPublic) {
59
- if (!publicUrl) {
60
- const ticketResult = await rpcCall(aid, 'storage.create_download_ticket', {
61
- object_key: remotePath,
62
- expire_in_seconds: 86400 * 30,
63
- }, { aunPath: opts?.aunPath });
64
- if (ticketResult.ok) {
65
- publicUrl = pickUrl(ticketResult.result?.url) ?? pickUrl(ticketResult.result?.download_url);
66
- }
67
- }
68
- publicUrl ??= `https://${aid}/${remotePath}`;
42
+ finally {
43
+ if (conn)
44
+ await conn.close();
69
45
  }
70
- return { ok: true, objectKey: remotePath, publicUrl };
71
46
  }
72
47
  export async function storageLs(aid, prefix, opts) {
73
48
  return rpcCall(aid, 'storage.list_objects', { prefix: prefix || '' }, { aunPath: opts?.aunPath });
74
49
  }
75
50
  export async function storageRm(aid, remotePath, opts) {
76
- return rpcCall(aid, 'storage.delete_object', { object_key: remotePath }, { aunPath: opts?.aunPath });
51
+ let conn;
52
+ try {
53
+ conn = await createShortConnection(aid, { aunPath: opts?.aunPath });
54
+ const result = await conn.storage.remove(remotePath);
55
+ return { ok: true, result };
56
+ }
57
+ catch (error) {
58
+ const code = error && typeof error === 'object' && 'code' in error && typeof error.code === 'number'
59
+ ? error.code : -1;
60
+ return { ok: false, error: { code, message: rpcErrorToString(error) } };
61
+ }
62
+ finally {
63
+ if (conn)
64
+ await conn.close();
65
+ }
77
66
  }
78
67
  export async function storageQuota(aid, opts) {
79
68
  return rpcCall(aid, 'storage.get_quota', {}, { aunPath: opts?.aunPath });