evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /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,109 +29,57 @@ export function isValidPayloadType(value) {
20
29
  }
21
30
  /** 单次上传最大大小(与 daemon sendFile 一致)。 */
22
31
  export const MAX_AUN_ATTACHMENT_SIZE = 10 * 1024 * 1024;
23
- /**
24
- * Inline JSON-RPC 上传阈值。超过该大小直接使用上传会话,避免把文件
25
- * Base64 编码后放进 storage.put_object 的 RPC 参数。
26
- */
27
- export const INLINE_AUN_ATTACHMENT_SIZE = 64 * 1024;
28
- /** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
29
- function isInlineLimitError(err) {
30
- const msg = err?.message ?? '';
31
- return /inline\s*上限|inline limit|create_upload_session/i.test(msg);
32
- }
33
32
  /**
34
33
  * 上传内存中的二进制内容并构造发送用的标准附件 payload。
35
34
  */
36
35
  export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename, opts) {
37
- const safeFilename = path.basename(filename.trim());
38
- if (!safeFilename || safeFilename === '.' || safeFilename === '..') {
36
+ const displayFilename = path.basename(filename);
37
+ if (!displayFilename.trim() || displayFilename === '.' || displayFilename === '..') {
39
38
  throw new Error('文件名不能为空');
40
39
  }
41
40
  if (data.length === 0) {
42
- throw new Error(`文件为空: ${safeFilename}`);
41
+ throw new Error(`文件为空: ${displayFilename}`);
43
42
  }
44
43
  if (data.length > MAX_AUN_ATTACHMENT_SIZE) {
45
- 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}`);
46
45
  }
47
46
  const sha256 = crypto.createHash('sha256').update(data).digest('hex');
48
- const contentType = opts?.contentType ?? guessMime(safeFilename);
49
- 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}`;
50
52
  const total = data.length;
51
53
  const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
52
- // 小文件优先走 inline,兼容不支持上传会话的服务端;大文件直接走
53
- // session,避免先构造一份可能超过 JSON-RPC 限制的 Base64 请求。
54
- let useUploadSession = total > INLINE_AUN_ATTACHMENT_SIZE;
55
- if (!useUploadSession) {
56
- try {
57
- report('inline', 0);
58
- await conn.call('storage.put_object', {
59
- object_key: objectKey,
60
- content: data.toString('base64'),
61
- content_type: contentType,
62
- is_private: false,
63
- overwrite: true,
64
- });
65
- report('inline', total);
66
- }
67
- catch (e) {
68
- if (!isInlineLimitError(e))
69
- throw e;
70
- useUploadSession = true;
71
- }
72
- }
73
- // inline 被拒或文件超过 inline 阈值:session + HTTP PUT
74
- if (useUploadSession) {
75
- report('session-create', 0);
76
- const session = await conn.call('storage.create_upload_session', {
77
- object_key: objectKey,
78
- size_bytes: total,
79
- content_type: contentType,
80
- });
81
- const uploadUrl = session?.upload_url;
82
- if (!uploadUrl)
83
- throw new Error('storage.create_upload_session 未返回 upload_url');
84
- // 简单 Buffer 上传 + 周期性进度(不用流,避免某些 storage 网关对 chunked / duplex 不友好)
85
- report('http-put', 0);
86
- const PROGRESS_TICK_MS = 250;
87
- let lastBytes = 0;
88
- const tickerStart = Date.now();
89
- // 实际上一次性发送;等待响应期间按 2MB/s 估算进度,且不超过 99%。
90
- const ticker = setInterval(() => {
91
- const elapsed = Date.now() - tickerStart;
92
- const estimated = Math.min(total - 1, Math.floor((elapsed / 1000) * 2 * 1024 * 1024));
93
- if (estimated > lastBytes) {
94
- lastBytes = estimated;
95
- report('http-put', estimated);
96
- }
97
- }, PROGRESS_TICK_MS);
98
- 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') {
99
66
  try {
100
- uploadResp = await fetch(uploadUrl, {
101
- method: 'PUT',
102
- body: new Blob([new Uint8Array(data)], { type: contentType }),
103
- headers: { 'Content-Type': contentType },
104
- });
67
+ contentType = (await conn.storage.stat(`/${objectKey}`)).contentType?.trim();
105
68
  }
106
- finally {
107
- clearInterval(ticker);
69
+ catch {
70
+ // A successful upload should remain usable when a legacy facade cannot
71
+ // perform a follow-up stat call.
108
72
  }
109
- if (!uploadResp.ok)
110
- throw new Error(`HTTP 上传失败: ${uploadResp.status}`);
111
- report('http-put', total);
112
- report('session-complete', total);
113
- await conn.call('storage.complete_upload', {
114
- object_key: objectKey,
115
- sha256,
116
- content_type: contentType,
117
- is_private: false,
118
- size_bytes: total,
119
- });
120
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');
121
78
  report('done', total);
122
79
  const attachment = {
123
80
  owner_aid: ownerAid,
124
81
  object_key: objectKey,
125
- filename: safeFilename,
82
+ filename: displayFilename,
126
83
  size_bytes: total,
127
84
  sha256,
128
85
  content_type: contentType,
@@ -135,7 +92,7 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
135
92
  type = opts.as;
136
93
  }
137
94
  else {
138
- type = inferPayloadType(safeFilename);
95
+ type = inferPayloadType(displayFilename);
139
96
  }
140
97
  const payload = {
141
98
  type,
@@ -144,7 +101,7 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
144
101
  if (opts?.text)
145
102
  payload.text = opts.text;
146
103
  else if (type === 'file')
147
- payload.text = `📎 ${safeFilename} (${formatSize(total)})`;
104
+ payload.text = `📎 ${displayFilename} (${formatSize(total)})`;
148
105
  if (type === 'voice' && opts?.transcript)
149
106
  payload.transcript = opts.transcript;
150
107
  return { payload, type, attachment };
@@ -152,9 +109,7 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
152
109
  /**
153
110
  * 上传本地文件并构造发送用的 payload。
154
111
  *
155
- * 策略:小文件使用 storage.put_object(内联 base64);大文件直接使用
156
- * storage.create_upload_session + HTTP PUT + storage.complete_upload,避免
157
- * 将大文件编码后放入 JSON-RPC。此时通过 onProgress 上报字节级进度。
112
+ * 通过 SDK StorageVFS 协商并执行上传,onProgress 上报上传阶段。
158
113
  */
159
114
  export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
160
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 });