evolcore 0.0.7 → 0.0.9

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 (230) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +38 -4
  3. package/dist/agents/baseagent.js +117 -24
  4. package/dist/agents/claude-runner.js +31 -2
  5. package/dist/agents/codex-app-server-client.js +37 -2
  6. package/dist/agents/codex-runner.js +11 -3
  7. package/dist/aun/aid/control-aid.js +40 -27
  8. package/dist/aun/aid/domain.js +23 -0
  9. package/dist/aun/msg/group.js +9 -9
  10. package/dist/aun/msg/p2p.js +11 -6
  11. package/dist/aun/msg/upload.js +43 -29
  12. package/dist/aun/outbox.js +48 -5
  13. package/dist/channels/aun.js +225 -71
  14. package/dist/cli/aun-commands.js +10 -7
  15. package/dist/cli/bench.js +4 -3
  16. package/dist/cli/daemon-commands.js +208 -68
  17. package/dist/cli/data-command.js +62 -35
  18. package/dist/cli/index.js +11 -1
  19. package/dist/cli/init-channel.js +1 -1
  20. package/dist/cli/init.js +112 -17
  21. package/dist/cli/model.js +5 -5
  22. package/dist/cli/restart-monitor.js +102 -22
  23. package/dist/config/builtin-role-templates.js +4 -3
  24. package/dist/config/config-field-policy.js +6 -2
  25. package/dist/config/config-manager.js +79 -11
  26. package/dist/config/gateway-config.js +80 -35
  27. package/dist/config/role-schema.js +18 -3
  28. package/dist/config/role-service.js +3 -3
  29. package/dist/config/role-store.js +4 -3
  30. package/dist/config-store.js +19 -3
  31. package/dist/core/auth/operation-authorizer.js +3 -1
  32. package/dist/core/baseagent-loader.js +5 -3
  33. package/dist/core/bootstrap-service.js +0 -12
  34. package/dist/core/capability/providers/codex-capability-provider.js +2 -2
  35. package/dist/core/channel-loader.js +15 -2
  36. package/dist/core/command/command-handler.js +1 -1
  37. package/dist/core/command/evol-menu-version-gate.js +3 -2
  38. package/dist/core/command/menu-handler.js +50 -28
  39. package/dist/core/command/menu-protocol.js +7 -4
  40. package/dist/core/command/slash-handler.js +4 -4
  41. package/dist/core/data-migration.js +517 -24
  42. package/dist/core/inference/text-inference.js +18 -5
  43. package/dist/core/message/message-bridge.js +17 -6
  44. package/dist/core/message/response-engine.js +22 -3
  45. package/dist/core/model/config-scope.js +3 -3
  46. package/dist/core/permission/tool-policy.js +18 -0
  47. package/dist/core/protected-paths.js +12 -1
  48. package/dist/index.js +794 -725
  49. package/dist/ipc.js +3 -1
  50. package/dist/product.js +1 -0
  51. package/dist/utils/autostart.js +27 -0
  52. package/dist/utils/codex-cli.js +39 -0
  53. package/dist/utils/cross-platform.js +147 -18
  54. package/dist/utils/ecweb-utils.js +15 -2
  55. package/dist/utils/instance-registry.js +43 -6
  56. package/dist/utils/macos-autostart.js +179 -0
  57. package/dist/utils/process-introspect.js +7 -3
  58. package/dist/utils/windows-autostart.js +130 -0
  59. package/ecagent/dist/agent-loop.d.ts +1 -1
  60. package/ecagent/dist/agent-loop.js +1 -1
  61. package/ecagent/dist/agent.d.ts +1 -1
  62. package/ecagent/dist/agent.js +1 -1
  63. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  64. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +1 -1
  65. package/ecagent/dist/harness/compaction/compaction.d.ts +1 -1
  66. package/ecagent/dist/harness/compaction/utils.d.ts +1 -1
  67. package/ecagent/dist/harness/messages.d.ts +1 -1
  68. package/ecagent/dist/harness/session/session.d.ts +1 -1
  69. package/ecagent/dist/harness/types.d.ts +1 -1
  70. package/ecagent/dist/proxy.d.ts +1 -1
  71. package/ecagent/dist/proxy.js +1 -1
  72. package/ecagent/dist/types.d.ts +1 -1
  73. package/kits/migrations/migrate-role-config-v5.mjs +27 -2
  74. package/kits/rules/01-overview.md +3 -2
  75. package/kits/schemas/_meta.json +2 -1
  76. package/kits/schemas/agent-config.schema.10.json +19 -1
  77. package/kits/schemas/daemon.schema.4.json +136 -0
  78. package/kits/schemas/defaults.schema.4.json +15 -1
  79. package/kits/schemas/relation-config.schema.8.json +22 -1
  80. package/kits/schemas/role-config.schema.1.json +2 -2
  81. package/package.json +2 -4
  82. package/ecagent/dist/agent-loop.d.ts.map +0 -1
  83. package/ecagent/dist/agent-loop.js.map +0 -1
  84. package/ecagent/dist/agent.d.ts.map +0 -1
  85. package/ecagent/dist/agent.js.map +0 -1
  86. package/ecagent/dist/harness/agent-harness.d.ts.map +0 -1
  87. package/ecagent/dist/harness/agent-harness.js.map +0 -1
  88. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
  89. package/ecagent/dist/harness/compaction/branch-summarization.js.map +0 -1
  90. package/ecagent/dist/harness/compaction/compaction.d.ts.map +0 -1
  91. package/ecagent/dist/harness/compaction/compaction.js.map +0 -1
  92. package/ecagent/dist/harness/compaction/utils.d.ts.map +0 -1
  93. package/ecagent/dist/harness/compaction/utils.js.map +0 -1
  94. package/ecagent/dist/harness/env/nodejs.d.ts.map +0 -1
  95. package/ecagent/dist/harness/env/nodejs.js.map +0 -1
  96. package/ecagent/dist/harness/messages.d.ts.map +0 -1
  97. package/ecagent/dist/harness/messages.js.map +0 -1
  98. package/ecagent/dist/harness/prompt-templates.d.ts.map +0 -1
  99. package/ecagent/dist/harness/prompt-templates.js.map +0 -1
  100. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +0 -1
  101. package/ecagent/dist/harness/session/jsonl-repo.js.map +0 -1
  102. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +0 -1
  103. package/ecagent/dist/harness/session/jsonl-storage.js.map +0 -1
  104. package/ecagent/dist/harness/session/memory-repo.d.ts.map +0 -1
  105. package/ecagent/dist/harness/session/memory-repo.js.map +0 -1
  106. package/ecagent/dist/harness/session/memory-storage.d.ts.map +0 -1
  107. package/ecagent/dist/harness/session/memory-storage.js.map +0 -1
  108. package/ecagent/dist/harness/session/repo-utils.d.ts.map +0 -1
  109. package/ecagent/dist/harness/session/repo-utils.js.map +0 -1
  110. package/ecagent/dist/harness/session/session.d.ts.map +0 -1
  111. package/ecagent/dist/harness/session/session.js.map +0 -1
  112. package/ecagent/dist/harness/session/uuid.d.ts.map +0 -1
  113. package/ecagent/dist/harness/session/uuid.js.map +0 -1
  114. package/ecagent/dist/harness/skills.d.ts.map +0 -1
  115. package/ecagent/dist/harness/skills.js.map +0 -1
  116. package/ecagent/dist/harness/system-prompt.d.ts.map +0 -1
  117. package/ecagent/dist/harness/system-prompt.js.map +0 -1
  118. package/ecagent/dist/harness/types.d.ts.map +0 -1
  119. package/ecagent/dist/harness/types.js.map +0 -1
  120. package/ecagent/dist/harness/utils/shell-output.d.ts.map +0 -1
  121. package/ecagent/dist/harness/utils/shell-output.js.map +0 -1
  122. package/ecagent/dist/harness/utils/truncate.d.ts.map +0 -1
  123. package/ecagent/dist/harness/utils/truncate.js.map +0 -1
  124. package/ecagent/dist/index.d.ts.map +0 -1
  125. package/ecagent/dist/index.js.map +0 -1
  126. package/ecagent/dist/node.d.ts.map +0 -1
  127. package/ecagent/dist/node.js.map +0 -1
  128. package/ecagent/dist/proxy.d.ts.map +0 -1
  129. package/ecagent/dist/proxy.js.map +0 -1
  130. package/ecagent/dist/runtime/api/lazy.d.ts.map +0 -1
  131. package/ecagent/dist/runtime/api/lazy.js.map +0 -1
  132. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +0 -1
  133. package/ecagent/dist/runtime/api/pi-messages.js.map +0 -1
  134. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +0 -1
  135. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +0 -1
  136. package/ecagent/dist/runtime/auth/context.d.ts.map +0 -1
  137. package/ecagent/dist/runtime/auth/context.js.map +0 -1
  138. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +0 -1
  139. package/ecagent/dist/runtime/auth/credential-store.js.map +0 -1
  140. package/ecagent/dist/runtime/auth/helpers.d.ts.map +0 -1
  141. package/ecagent/dist/runtime/auth/helpers.js.map +0 -1
  142. package/ecagent/dist/runtime/auth/resolve.d.ts.map +0 -1
  143. package/ecagent/dist/runtime/auth/resolve.js.map +0 -1
  144. package/ecagent/dist/runtime/auth/types.d.ts.map +0 -1
  145. package/ecagent/dist/runtime/auth/types.js.map +0 -1
  146. package/ecagent/dist/runtime/compat.d.ts.map +0 -1
  147. package/ecagent/dist/runtime/compat.js.map +0 -1
  148. package/ecagent/dist/runtime/index.d.ts.map +0 -1
  149. package/ecagent/dist/runtime/index.js.map +0 -1
  150. package/ecagent/dist/runtime/models.d.ts.map +0 -1
  151. package/ecagent/dist/runtime/models.js.map +0 -1
  152. package/ecagent/dist/runtime/oauth.d.ts.map +0 -1
  153. package/ecagent/dist/runtime/oauth.js.map +0 -1
  154. package/ecagent/dist/runtime/providers/faux.d.ts.map +0 -1
  155. package/ecagent/dist/runtime/providers/faux.js.map +0 -1
  156. package/ecagent/dist/runtime/types.d.ts.map +0 -1
  157. package/ecagent/dist/runtime/types.js.map +0 -1
  158. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +0 -1
  159. package/ecagent/dist/runtime/utils/diagnostics.js.map +0 -1
  160. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +0 -1
  161. package/ecagent/dist/runtime/utils/event-stream.js.map +0 -1
  162. package/ecagent/dist/runtime/utils/headers.d.ts.map +0 -1
  163. package/ecagent/dist/runtime/utils/headers.js.map +0 -1
  164. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +0 -1
  165. package/ecagent/dist/runtime/utils/json-parse.js.map +0 -1
  166. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +0 -1
  167. package/ecagent/dist/runtime/utils/oauth/types.js.map +0 -1
  168. package/ecagent/dist/runtime/utils/overflow.d.ts.map +0 -1
  169. package/ecagent/dist/runtime/utils/overflow.js.map +0 -1
  170. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +0 -1
  171. package/ecagent/dist/runtime/utils/provider-env.js.map +0 -1
  172. package/ecagent/dist/runtime/utils/retry.d.ts.map +0 -1
  173. package/ecagent/dist/runtime/utils/retry.js.map +0 -1
  174. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +0 -1
  175. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +0 -1
  176. package/ecagent/dist/runtime/utils/validation.d.ts.map +0 -1
  177. package/ecagent/dist/runtime/utils/validation.js.map +0 -1
  178. package/ecagent/dist/types.d.ts.map +0 -1
  179. package/ecagent/dist/types.js.map +0 -1
  180. package/ecagent/package.json +0 -93
  181. package/ecagent/src/agent-loop.ts +0 -792
  182. package/ecagent/src/agent.ts +0 -575
  183. package/ecagent/src/harness/agent-harness.ts +0 -1029
  184. package/ecagent/src/harness/compaction/branch-summarization.ts +0 -261
  185. package/ecagent/src/harness/compaction/compaction.ts +0 -753
  186. package/ecagent/src/harness/compaction/utils.ts +0 -144
  187. package/ecagent/src/harness/env/nodejs.ts +0 -569
  188. package/ecagent/src/harness/messages.ts +0 -164
  189. package/ecagent/src/harness/prompt-templates.ts +0 -267
  190. package/ecagent/src/harness/session/jsonl-repo.ts +0 -179
  191. package/ecagent/src/harness/session/jsonl-storage.ts +0 -314
  192. package/ecagent/src/harness/session/memory-repo.ts +0 -50
  193. package/ecagent/src/harness/session/memory-storage.ts +0 -133
  194. package/ecagent/src/harness/session/repo-utils.ts +0 -51
  195. package/ecagent/src/harness/session/session.ts +0 -338
  196. package/ecagent/src/harness/session/uuid.ts +0 -54
  197. package/ecagent/src/harness/skills.ts +0 -375
  198. package/ecagent/src/harness/system-prompt.ts +0 -34
  199. package/ecagent/src/harness/types.ts +0 -838
  200. package/ecagent/src/harness/utils/shell-output.ts +0 -135
  201. package/ecagent/src/harness/utils/truncate.ts +0 -344
  202. package/ecagent/src/index.ts +0 -46
  203. package/ecagent/src/node.ts +0 -2
  204. package/ecagent/src/proxy.ts +0 -367
  205. package/ecagent/src/runtime/api/lazy.ts +0 -70
  206. package/ecagent/src/runtime/api/pi-messages.lazy.ts +0 -4
  207. package/ecagent/src/runtime/api/pi-messages.ts +0 -436
  208. package/ecagent/src/runtime/auth/context.ts +0 -45
  209. package/ecagent/src/runtime/auth/credential-store.ts +0 -47
  210. package/ecagent/src/runtime/auth/helpers.ts +0 -46
  211. package/ecagent/src/runtime/auth/resolve.ts +0 -141
  212. package/ecagent/src/runtime/auth/types.ts +0 -182
  213. package/ecagent/src/runtime/compat.ts +0 -158
  214. package/ecagent/src/runtime/index.ts +0 -31
  215. package/ecagent/src/runtime/models.ts +0 -452
  216. package/ecagent/src/runtime/oauth.ts +0 -1
  217. package/ecagent/src/runtime/providers/faux.ts +0 -538
  218. package/ecagent/src/runtime/types.ts +0 -718
  219. package/ecagent/src/runtime/utils/diagnostics.ts +0 -45
  220. package/ecagent/src/runtime/utils/event-stream.ts +0 -88
  221. package/ecagent/src/runtime/utils/headers.ts +0 -18
  222. package/ecagent/src/runtime/utils/json-parse.ts +0 -124
  223. package/ecagent/src/runtime/utils/oauth/types.ts +0 -79
  224. package/ecagent/src/runtime/utils/overflow.ts +0 -165
  225. package/ecagent/src/runtime/utils/provider-env.ts +0 -52
  226. package/ecagent/src/runtime/utils/retry.ts +0 -101
  227. package/ecagent/src/runtime/utils/typebox-helpers.ts +0 -24
  228. package/ecagent/src/runtime/utils/validation.ts +0 -310
  229. package/ecagent/src/types.ts +0 -430
  230. package/ecagent/tsconfig.build.json +0 -32
@@ -5,7 +5,7 @@ import { TextDecoder } from 'util';
5
5
  import { createShortConnection } from '../rpc/index.js';
6
6
  import { getAidStore, loadClient, SLOT } from '../aid/store.js';
7
7
  import { uploadFileAndBuildPayload } from './upload.js';
8
- import { daemonTaskSendIpcTimeoutMs } from './p2p.js';
8
+ import { daemonTaskSendIpcTimeoutMs, resolveAunMessageEncrypt } from './p2p.js';
9
9
  import { checkGroupIndex, getGroupIndex } from './group-index.js';
10
10
  import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from '../../core/message/message-log.js';
11
11
  import { chatDirPath } from '../../core/session/session-fs-store.js';
@@ -50,7 +50,7 @@ function groupSendLogMetadata(args, payload, runtimeContext) {
50
50
  source: (runtimeContext ? 'msg' : 'cli'),
51
51
  };
52
52
  }
53
- function appendGroupSendOutboundLog(args, messageId, payload, runtimeContext) {
53
+ function appendGroupSendOutboundLog(args, messageId, payload, runtimeContext, encrypt = resolveAunMessageEncrypt(args.encrypt)) {
54
54
  const log = groupSendLogMetadata(args, payload, runtimeContext);
55
55
  const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', args.groupId, args.from);
56
56
  fs.mkdirSync(chatDir, { recursive: true });
@@ -63,7 +63,7 @@ function appendGroupSendOutboundLog(args, messageId, payload, runtimeContext) {
63
63
  groupId: args.groupId,
64
64
  msgId: messageId,
65
65
  content: log.content,
66
- encrypt: args.encrypt === true,
66
+ encrypt,
67
67
  msgType: log.msgType,
68
68
  payloadType: log.payloadType,
69
69
  payloadSummary: log.payloadSummary,
@@ -95,7 +95,7 @@ async function tryDaemonGroupSend(args, payload, runtimeContext, file) {
95
95
  scope: 'group',
96
96
  ...(payload ? { payload } : { file }),
97
97
  mentions: args.mentions,
98
- encrypt: args.encrypt === true,
98
+ encrypt: args.encrypt,
99
99
  thread,
100
100
  returnPolicy: args.returnPolicy,
101
101
  originSessionId: runtimeContext.sessionId,
@@ -135,7 +135,7 @@ async function daemonGroupSendResult(args, payload, runtimeContext, result) {
135
135
  const messageId = result.message_id
136
136
  ?? (typeof result.message.message_id === 'string' ? result.message.message_id : undefined);
137
137
  if (messageId && !result.log_written) {
138
- appendGroupSendOutboundLog(args, messageId, payload, runtimeContext);
138
+ appendGroupSendOutboundLog(args, messageId, payload, runtimeContext, result.encrypt ?? resolveAunMessageEncrypt(args.encrypt));
139
139
  }
140
140
  return {
141
141
  ok: true,
@@ -193,12 +193,12 @@ export async function groupSend(args) {
193
193
  payload.mentions = args.mentions;
194
194
  }
195
195
  applyGroupRoutingPayloadFields(payload, args, runtimeContext);
196
- const sendParams = { group_id: args.groupId, payload };
197
- sendParams.encrypt = args.encrypt === true;
196
+ const encrypt = resolveAunMessageEncrypt(args.encrypt);
197
+ const sendParams = { group_id: args.groupId, payload, encrypt };
198
198
  const result = await conn.call('group.send', sendParams);
199
199
  const messageId = typeof result?.message?.message_id === 'string' ? result.message.message_id : undefined;
200
200
  if (messageId)
201
- appendGroupSendOutboundLog(args, messageId, payload, runtimeContext);
201
+ appendGroupSendOutboundLog(args, messageId, payload, runtimeContext, encrypt);
202
202
  return {
203
203
  ok: true,
204
204
  group_id: result?.group_id ?? args.groupId,
@@ -954,7 +954,7 @@ async function sendGroupRulesUpdatedNotice(client, args) {
954
954
  const result = await client.group.send({
955
955
  group_id: args.groupId,
956
956
  payload,
957
- encrypt: false,
957
+ encrypt: resolveAunMessageEncrypt(),
958
958
  });
959
959
  const message = isRecord(result.message) ? result.message : {};
960
960
  const messageId = typeof result.message_id === 'string'
@@ -9,6 +9,7 @@ import { chatDirPath } from '../../core/session/session-fs-store.js';
9
9
  import { resolvePaths } from '../../paths.js';
10
10
  import { ipcQuery } from '../../ipc.js';
11
11
  import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
12
+ import { loadDaemonConfig } from '../../config-store.js';
12
13
  const DEFAULT_TASK_EXECUTION_MS = 60 * 60 * 1000;
13
14
  const DAEMON_SEND_TIMEOUT_GRACE_MS = 10_000;
14
15
  const MAX_TIMER_MS = 2_147_483_647;
@@ -24,6 +25,11 @@ export function daemonTaskSendIpcTimeoutMs(file = false) {
24
25
  catch { }
25
26
  return Math.min(MAX_TIMER_MS, Math.max(file ? 120_000 : 5_000, executionMs + DAEMON_SEND_TIMEOUT_GRACE_MS));
26
27
  }
28
+ export function resolveAunMessageEncrypt(explicit) {
29
+ if (typeof explicit === 'boolean')
30
+ return explicit;
31
+ return loadDaemonConfig().aun?.defaultEncrypt ?? true;
32
+ }
27
33
  function buildSimplePayload(body) {
28
34
  switch (body.mode) {
29
35
  case 'text':
@@ -113,7 +119,7 @@ async function tryDaemonMsgSend(args, payload, runtimeContext, file) {
113
119
  to: args.to,
114
120
  scope: 'msg',
115
121
  ...(payload ? { payload } : { file }),
116
- encrypt: args.encrypt === true,
122
+ encrypt: args.encrypt,
117
123
  thread,
118
124
  returnPolicy: args.returnPolicy,
119
125
  originSessionId: runtimeContext.sessionId,
@@ -155,7 +161,7 @@ async function daemonMsgSendResult(args, payload, runtimeContext, daemonResult)
155
161
  payload,
156
162
  runtimeContext,
157
163
  chatmode: daemonResult.chatmode,
158
- encrypt: daemonResult.encrypt ?? args.encrypt === true,
164
+ encrypt: daemonResult.encrypt ?? resolveAunMessageEncrypt(args.encrypt),
159
165
  });
160
166
  }
161
167
  return sent;
@@ -259,9 +265,8 @@ export async function msgSend(args) {
259
265
  // 5. 写入 payload.thread_id/ref_message_id(如果指定)
260
266
  applyRoutingPayloadFields(payload, args, runtimeContext, false);
261
267
  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;
268
+ const encrypt = resolveAunMessageEncrypt(args.encrypt);
269
+ const sendParams = { to: args.to, payload, encrypt };
265
270
  const result = await conn.call('message.send', sendParams);
266
271
  // 5. 写出方向 jsonl(与 daemon 一致格式,标记 source)
267
272
  // source 标记:
@@ -273,7 +278,7 @@ export async function msgSend(args) {
273
278
  payload,
274
279
  runtimeContext,
275
280
  chatmode,
276
- encrypt: args.encrypt === true,
281
+ encrypt,
277
282
  });
278
283
  }
279
284
  catch { }
@@ -4,37 +4,30 @@ import path from 'path';
4
4
  import { guessMime, formatSize } from '../../utils/media-cache.js';
5
5
  import { inferPayloadType, isValidPayloadType } from './payload-type.js';
6
6
  /** 单次上传最大大小(与 daemon sendFile 一致)。 */
7
- const MAX_FILE_SIZE = 10 * 1024 * 1024;
7
+ export const MAX_AUN_ATTACHMENT_SIZE = 10 * 1024 * 1024;
8
8
  /** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
9
9
  function isInlineLimitError(err) {
10
10
  const msg = err?.message ?? '';
11
11
  return /inline\s*上限|inline limit|create_upload_session/i.test(msg);
12
12
  }
13
13
  /**
14
- * 上传本地文件并构造发送用的 payload。
15
- *
16
- * 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
17
- * 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
18
- * 此时通过 onProgress 上报字节级进度。
14
+ * 上传内存中的二进制内容并构造发送用的标准附件 payload。
19
15
  */
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}`);
16
+ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename, opts) {
17
+ const safeFilename = path.basename(filename.trim());
18
+ if (!safeFilename || safeFilename === '.' || safeFilename === '..') {
19
+ throw new Error('文件名不能为空');
24
20
  }
25
- const stat = fs.statSync(absPath);
26
- if (stat.size === 0) {
27
- throw new Error(`文件为空: ${absPath}`);
21
+ if (data.length === 0) {
22
+ throw new Error(`文件为空: ${safeFilename}`);
28
23
  }
29
- if (stat.size > MAX_FILE_SIZE) {
30
- throw new Error(`文件过大 (${formatSize(stat.size)}, 上限 ${formatSize(MAX_FILE_SIZE)}): ${absPath}`);
24
+ if (data.length > MAX_AUN_ATTACHMENT_SIZE) {
25
+ throw new Error(`文件过大 (${formatSize(data.length)}, 上限 ${formatSize(MAX_AUN_ATTACHMENT_SIZE)}): ${safeFilename}`);
31
26
  }
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;
27
+ const sha256 = crypto.createHash('sha256').update(data).digest('hex');
28
+ const contentType = opts?.contentType ?? guessMime(safeFilename);
29
+ const objectKey = `shared/${crypto.randomUUID()}/${safeFilename}`;
30
+ const total = data.length;
38
31
  const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
39
32
  // 1. 先按 inline 走
40
33
  let inlineRejected = false;
@@ -42,7 +35,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
42
35
  report('inline', 0);
43
36
  await conn.call('storage.put_object', {
44
37
  object_key: objectKey,
45
- content: fileData.toString('base64'),
38
+ content: data.toString('base64'),
46
39
  content_type: contentType,
47
40
  is_private: false,
48
41
  overwrite: true,
@@ -70,10 +63,9 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
70
63
  const PROGRESS_TICK_MS = 250;
71
64
  let lastBytes = 0;
72
65
  const tickerStart = Date.now();
73
- // 简单的“估算”进度:实际上一次性发,但在等待响应期间按 elapsed 模拟字节数,让用户看到在跑
66
+ // 实际上一次性发送;等待响应期间按 2MB/s 估算进度,且不超过 99%。
74
67
  const ticker = setInterval(() => {
75
68
  const elapsed = Date.now() - tickerStart;
76
- // 假设 2MB/s 估算;不超过 99%
77
69
  const estimated = Math.min(total - 1, Math.floor((elapsed / 1000) * 2 * 1024 * 1024));
78
70
  if (estimated > lastBytes) {
79
71
  lastBytes = estimated;
@@ -84,7 +76,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
84
76
  try {
85
77
  uploadResp = await fetch(uploadUrl, {
86
78
  method: 'PUT',
87
- body: new Blob([new Uint8Array(fileData)], { type: contentType }),
79
+ body: new Blob([new Uint8Array(data)], { type: contentType }),
88
80
  headers: { 'Content-Type': contentType },
89
81
  });
90
82
  }
@@ -107,12 +99,11 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
107
99
  const attachment = {
108
100
  owner_aid: ownerAid,
109
101
  object_key: objectKey,
110
- filename,
102
+ filename: safeFilename,
111
103
  size_bytes: total,
112
104
  sha256,
113
105
  content_type: contentType,
114
106
  };
115
- // 确定渲染类型
116
107
  let type;
117
108
  if (opts?.as) {
118
109
  if (!isValidPayloadType(opts.as)) {
@@ -121,7 +112,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
121
112
  type = opts.as;
122
113
  }
123
114
  else {
124
- type = inferPayloadType(filename);
115
+ type = inferPayloadType(safeFilename);
125
116
  }
126
117
  const payload = {
127
118
  type,
@@ -130,8 +121,31 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
130
121
  if (opts?.text)
131
122
  payload.text = opts.text;
132
123
  else if (type === 'file')
133
- payload.text = `📎 ${filename} (${formatSize(total)})`;
124
+ payload.text = `📎 ${safeFilename} (${formatSize(total)})`;
134
125
  if (type === 'voice' && opts?.transcript)
135
126
  payload.transcript = opts.transcript;
136
127
  return { payload, type, attachment };
137
128
  }
129
+ /**
130
+ * 上传本地文件并构造发送用的 payload。
131
+ *
132
+ * 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
133
+ * 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
134
+ * 此时通过 onProgress 上报字节级进度。
135
+ */
136
+ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
137
+ const absPath = path.resolve(filePath);
138
+ if (!fs.existsSync(absPath)) {
139
+ throw new Error(`文件不存在: ${absPath}`);
140
+ }
141
+ const stat = fs.statSync(absPath);
142
+ if (stat.size === 0) {
143
+ throw new Error(`文件为空: ${absPath}`);
144
+ }
145
+ if (stat.size > MAX_AUN_ATTACHMENT_SIZE) {
146
+ throw new Error(`文件过大 (${formatSize(stat.size)}, 上限 ${formatSize(MAX_AUN_ATTACHMENT_SIZE)}): ${absPath}`);
147
+ }
148
+ const filename = path.basename(absPath);
149
+ const fileData = fs.readFileSync(absPath);
150
+ return uploadBufferAndBuildPayload(conn, ownerAid, fileData, filename, opts);
151
+ }
@@ -59,6 +59,7 @@ export function enqueue(aid, opts) {
59
59
  payload: opts.payload,
60
60
  text: opts.text,
61
61
  filePath: opts.filePath,
62
+ image: opts.image,
62
63
  logText: opts.logText,
63
64
  context: opts.context,
64
65
  ttl: opts.ttl ?? DEFAULT_TTL,
@@ -81,6 +82,16 @@ export function remove(aid, id) {
81
82
  const entries = readEntries(aid).filter(e => e.id !== id);
82
83
  writeEntries(aid, entries);
83
84
  }
85
+ /** Replace an existing entry without consuming another outbox slot. */
86
+ export function replace(aid, entry) {
87
+ const entries = readEntries(aid);
88
+ const index = entries.findIndex(candidate => candidate.id === entry.id);
89
+ if (index < 0)
90
+ return false;
91
+ entries[index] = entry;
92
+ writeEntries(aid, entries);
93
+ return true;
94
+ }
84
95
  /** Remove durable interaction cards that were cancelled before delivery. */
85
96
  export function removeInteractionCards(aid, requestId) {
86
97
  const entries = readEntries(aid);
@@ -102,7 +113,11 @@ export function cleanup(aid) {
102
113
  writeEntries(aid, valid);
103
114
  return removed;
104
115
  }
105
- export async function drain(aid, sender) {
116
+ // Each caller gets its own pass over the current disk state. This prevents
117
+ // duplicate sends while allowing a later drain trigger to pick up entries
118
+ // enqueued during an active pass.
119
+ const activeDrainTails = new Map();
120
+ async function drainOnce(aid, sender) {
106
121
  const entries = readEntries(aid);
107
122
  if (entries.length === 0)
108
123
  return { sent: 0, expired: 0, failed: 0 };
@@ -135,12 +150,40 @@ export async function drain(aid, sender) {
135
150
  }
136
151
  }
137
152
  const current = readEntries(aid);
138
- const currentIds = new Set(current.map(e => e.id));
139
- const retainedNewEntries = current.filter(e => !drainedIds.has(e.id));
140
- const retainedFailedEntries = remaining.filter(e => currentIds.has(e.id));
141
- writeEntries(aid, [...retainedFailedEntries, ...retainedNewEntries]);
153
+ const failedById = new Map(remaining.map(entry => [entry.id, entry]));
154
+ const retained = current.flatMap(entry => {
155
+ if (!drainedIds.has(entry.id))
156
+ return [entry];
157
+ const failedEntry = failedById.get(entry.id);
158
+ if (!failedEntry)
159
+ return [];
160
+ return [{
161
+ ...failedEntry,
162
+ ...entry,
163
+ attempts: failedEntry.attempts,
164
+ lastError: failedEntry.lastError,
165
+ }];
166
+ });
167
+ writeEntries(aid, retained);
142
168
  return { sent, expired, failed };
143
169
  }
170
+ export async function drain(aid, sender) {
171
+ const previous = activeDrainTails.get(aid);
172
+ let releaseTurn;
173
+ const turn = new Promise(resolve => { releaseTurn = resolve; });
174
+ const tail = (previous ?? Promise.resolve()).then(() => turn);
175
+ activeDrainTails.set(aid, tail);
176
+ if (previous)
177
+ await previous;
178
+ try {
179
+ return await drainOnce(aid, sender);
180
+ }
181
+ finally {
182
+ releaseTurn();
183
+ if (activeDrainTails.get(aid) === tail)
184
+ activeDrainTails.delete(aid);
185
+ }
186
+ }
144
187
  export function hasPending(aid) {
145
188
  const file = outboxFile(aid);
146
189
  if (!fs.existsSync(file))