evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
package/dist/paths.js CHANGED
@@ -26,11 +26,11 @@ export function resolvePaths() {
26
26
  const agentsDir = path.join(root, 'agents');
27
27
  return {
28
28
  root,
29
- // Session storage is partitioned by owning Agent. Keep the historical
30
- // location separately so the explicit data migration can inventory it.
29
+ // Session storage is partitioned by owning Agent.
31
30
  sessionsDir: agentsDir,
32
- legacySessionsDir: path.join(root, 'data', 'sessions'),
33
31
  instanceDir: path.join(root, 'data', 'instance'),
32
+ // Historical outbox root. Runtime writes use agentOutboxPath() below;
33
+ // this directory is retained only for explicit data migration.
34
34
  outboxDir: path.join(root, 'data', 'outbox'),
35
35
  dataDir: path.join(root, 'data'),
36
36
  claudeModelCache: path.join(root, 'data', 'claude-model-cache.json'),
@@ -89,7 +89,13 @@ export function agentContactConfig(aid) {
89
89
  return path.join(agentDir(aid), 'contact.json');
90
90
  }
91
91
  export function agentContactOperations(aid) {
92
- return path.join(agentDir(aid), 'contact-operations.jsonl');
92
+ return path.join(agentDataDir(aid), 'contact', 'operations.jsonl');
93
+ }
94
+ export function agentContactDataDir(aid) {
95
+ return path.join(agentDataDir(aid), 'contact');
96
+ }
97
+ export function agentContactAuditPath(aid) {
98
+ return path.join(agentContactDataDir(aid), 'audit.jsonl');
93
99
  }
94
100
  export function agentPersonalDir(aid) {
95
101
  return path.join(agentDir(aid), 'personal');
@@ -132,28 +138,44 @@ export function agentSessionsDir(aid) {
132
138
  export function agentDataDir(aid) {
133
139
  return path.join(agentDir(aid), 'data');
134
140
  }
141
+ export function agentTaskActivityDir(aid) {
142
+ return path.join(agentDataDir(aid), 'task-activity');
143
+ }
135
144
  export function agentDataCacheDir(aid) {
136
145
  return path.join(agentDataDir(aid), 'cache');
137
146
  }
138
147
  export function agentHandoffDir(aid) {
139
148
  return path.join(agentDataDir(aid), 'handoff');
140
149
  }
150
+ export function agentCodexTokensDir(aid) {
151
+ return path.join(agentDataDir(aid), 'codex-tokens');
152
+ }
153
+ export function agentCodexTokenPath(aid, threadId) {
154
+ return path.join(agentCodexTokensDir(aid), `${encodeURIComponent(threadId)}.json`);
155
+ }
141
156
  export function agentGroupInviteReviewsPath(aid) {
142
157
  return path.join(agentDataDir(aid), 'aun', 'group-invite-reviews.json');
143
158
  }
144
159
  export function agentOutboxPath(aid) {
145
- return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'outbox.jsonl');
160
+ return path.join(agentDataDir(aid), 'outbox', 'delivery.jsonl');
146
161
  }
147
162
  export function agentActivityOutboxPath(aid) {
148
- return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'activity-outbox.jsonl');
163
+ return path.join(agentDataDir(aid), 'outbox', 'activity.jsonl');
149
164
  }
150
- /** Pre-v4 per-Agent outbox paths retained only for compatibility import. */
165
+ /** Historical per-Agent outbox paths used only by the explicit migration. */
151
166
  export function legacyAgentOutboxPath(aid) {
152
167
  return path.join(agentDataDir(aid), 'outbox.jsonl');
153
168
  }
154
169
  export function legacyAgentActivityOutboxPath(aid) {
155
170
  return path.join(agentDataDir(aid), 'activity-outbox.jsonl');
156
171
  }
172
+ /** Historical shared per-Agent outbox directory layout. */
173
+ export function legacySharedOutboxPath(aid) {
174
+ return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'outbox.jsonl');
175
+ }
176
+ export function legacySharedActivityOutboxPath(aid) {
177
+ return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'activity-outbox.jsonl');
178
+ }
157
179
  /** Earliest global outbox layout: data/outbox/<aid>.jsonl. */
158
180
  export function legacyFlatOutboxPath(aid) {
159
181
  return path.join(resolvePaths().outboxDir, `${aid}.jsonl`);
@@ -261,7 +283,7 @@ export function getWindowsInstanceSocketPathCandidates(root) {
261
283
  }
262
284
  return candidates;
263
285
  }
264
- function resolveInstanceSocketPath(root) {
286
+ export function resolveInstanceSocketPath(root) {
265
287
  if (isWindows)
266
288
  return getWindowsInstanceSocketPathCandidates(root)[0];
267
289
  const filesystemPath = path.join(root, 'data', 'instance', INSTANCE_SOCKET_FILENAME);
@@ -291,7 +313,6 @@ export function ensureDataDirs() {
291
313
  fs.mkdirSync(p.agentsDir, { recursive: true });
292
314
  fs.mkdirSync(p.sessionsDir, { recursive: true });
293
315
  fs.mkdirSync(p.instanceDir, { recursive: true });
294
- fs.mkdirSync(p.outboxDir, { recursive: true });
295
316
  fs.mkdirSync(p.eckDir, { recursive: true });
296
317
  fs.mkdirSync(aidsDir(), { recursive: true });
297
318
  migrateLegacyAgentJsonlLogs(p.agentsDir, p.logs);
@@ -9,7 +9,10 @@
9
9
  * 依赖全部由调用点显式传入。
10
10
  */
11
11
  const STATE_KEY = 'proactive';
12
- const TOOL_REPORT_INTERVAL = 10;
12
+ const SNAPSHOT_KEY = 'proactive:continuation';
13
+ const OWNER_KEY = 'proactive:owner';
14
+ const SNAPSHOT_MAX_AGE_MS = 30 * 60 * 1_000;
15
+ const TOOL_REPORT_INTERVAL = 20;
13
16
  const SEND_RECEIPT_RE = /["']?(?:message[_ -]?id|handoff[_ -]?id)["']?\s*[:=]\s*["']?([A-Za-z0-9][A-Za-z0-9._:-]*)/gi;
14
17
  const HANDOFF_TEXT_RECEIPT_RE = /\bhandoff\s+(h-[A-Za-z0-9._-]+)/i;
15
18
  const RETURN_CONTENT_RE = /["']?(?:return[_ -]?content|returnContent)["']?\s*[:=]\s*["']?([^}\n,]*)/i;
@@ -161,6 +164,79 @@ function buildState(message, cfg) {
161
164
  toolReportPending: false,
162
165
  };
163
166
  }
167
+ function normalizedThread(threadId) {
168
+ return threadId && threadId !== 'main' ? threadId : '';
169
+ }
170
+ function isResumableTerminal(terminal) {
171
+ return terminal?.status === 'interrupted' && terminal.reason === 'daemon_restart'
172
+ || terminal?.status === 'cancelled' && (terminal.reason === 'stop' || terminal.reason === 'explicit_cancel');
173
+ }
174
+ function canRestoreSnapshot(ctx, state, snapshot) {
175
+ const previous = ctx.previousTurnState;
176
+ const terminal = previous?.lastTerminal;
177
+ const continuation = ctx.continuation;
178
+ if (!continuation || !previous || previous.active || !isResumableTerminal(terminal))
179
+ return false;
180
+ if (!snapshot.agentSessionId || !snapshot.baseagent)
181
+ return false;
182
+ const age = Date.now() - snapshot.finishedAt;
183
+ if (!Number.isFinite(age) || age < 0 || age > SNAPSHOT_MAX_AGE_MS
184
+ || !Number.isSafeInteger(snapshot.toolCount) || snapshot.toolCount < 0
185
+ || typeof snapshot.firstToolDone !== 'boolean' || typeof snapshot.toolReportPending !== 'boolean'
186
+ || snapshot.sessionId !== ctx.session.id || snapshot.selfAid !== ctx.session.selfAID
187
+ || snapshot.channel !== ctx.session.channel || snapshot.channelId !== ctx.session.channelId
188
+ || snapshot.chatType !== ctx.message.chatType || snapshot.chatType !== ctx.session.chatType
189
+ || snapshot.threadId !== normalizedThread(ctx.session.threadId)
190
+ || snapshot.threadId !== normalizedThread(continuation.threadId)
191
+ || snapshot.baseagent !== ctx.session.baseagent || snapshot.agentSessionId !== ctx.session.agentSessionId
192
+ || snapshot.firstSendRequired !== state.firstSendRequired
193
+ || snapshot.toolReportRequired !== state.toolReportRequired
194
+ || snapshot.toolReportInterval !== state.toolReportInterval
195
+ || snapshot.turnId !== terminal.turnId || snapshot.taskId !== terminal.taskId
196
+ || snapshot.generation !== terminal.generation || snapshot.finishedAt !== terminal.finishedAt)
197
+ return false;
198
+ if (state.chatType === 'group' && continuation.groupId !== snapshot.channelId)
199
+ return false;
200
+ if (continuation.kind === 'taskRecovery') {
201
+ return continuation.taskId === snapshot.taskId && continuation.generation === previous.generation;
202
+ }
203
+ return continuation.kind === 'restartResume'
204
+ && terminal.status === 'interrupted' && terminal.reason === 'daemon_restart';
205
+ }
206
+ function saveContinuation(ctx) {
207
+ const state = ctx.state.get(STATE_KEY);
208
+ if (!state || !ctx.sessionState || ctx.sessionState.get(OWNER_KEY) !== state)
209
+ return;
210
+ ctx.sessionState.delete(OWNER_KEY);
211
+ const terminal = ctx.terminal;
212
+ if (!terminal || terminal.turnId !== ctx.turn.turnId || terminal.taskId !== ctx.turn.taskId
213
+ || terminal.generation !== ctx.turn.generation)
214
+ return;
215
+ ctx.sessionState.delete(SNAPSHOT_KEY);
216
+ if (!isResumableTerminal(terminal))
217
+ return;
218
+ const snapshot = {
219
+ sessionId: ctx.session.id,
220
+ selfAid: ctx.session.selfAID,
221
+ channel: ctx.session.channel,
222
+ channelId: ctx.session.channelId,
223
+ chatType: state.chatType,
224
+ threadId: normalizedThread(ctx.session.threadId),
225
+ baseagent: ctx.session.baseagent,
226
+ agentSessionId: ctx.session.agentSessionId,
227
+ turnId: terminal.turnId,
228
+ taskId: terminal.taskId,
229
+ generation: terminal.generation,
230
+ finishedAt: terminal.finishedAt,
231
+ firstToolDone: state.firstToolDone,
232
+ toolCount: state.toolCount,
233
+ toolReportPending: state.toolReportPending,
234
+ firstSendRequired: state.firstSendRequired,
235
+ toolReportRequired: state.toolReportRequired,
236
+ toolReportInterval: state.toolReportInterval,
237
+ };
238
+ ctx.sessionState.set(SNAPSHOT_KEY, snapshot);
239
+ }
164
240
  export const proactiveFlow = {
165
241
  chatMode: 'proactive',
166
242
  // ─── 入站决策:构造 runtimeState ───
@@ -187,7 +263,15 @@ export const proactiveFlow = {
187
263
  // ─── beforeProcess:把 runtimeState 落入 ctx.state,供后续钩子共享 ───
188
264
  beforeProcess(ctx) {
189
265
  const state = buildState(ctx.message, ctx.modeConfig ?? {});
266
+ const snapshot = ctx.sessionState?.get(SNAPSHOT_KEY);
267
+ ctx.sessionState?.delete(SNAPSHOT_KEY);
268
+ if (snapshot && canRestoreSnapshot(ctx, state, snapshot)) {
269
+ state.firstToolDone = snapshot.firstToolDone;
270
+ state.toolCount = snapshot.toolCount;
271
+ state.toolReportPending = state.toolReportRequired && snapshot.toolReportPending;
272
+ }
190
273
  ctx.state.set(STATE_KEY, state);
274
+ ctx.sessionState?.set(OWNER_KEY, state);
191
275
  },
192
276
  // ─── configureRun:policyHook 只判定,不推进状态 ───
193
277
  configureRun(ctx) {
@@ -240,29 +324,43 @@ export const proactiveFlow = {
240
324
  const isAllowedTool = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
241
325
  if (!state.toolUseReminder)
242
326
  return;
243
- // 队列未读变化提醒
244
- const queueLen = ctx.getQueueLength();
245
- if (queueLen !== state.lastQueueReminderLen) {
246
- state.lastQueueReminderLen = queueLen;
247
- if (queueLen > 0) {
248
- const queueMsg = queueLen >= 5
249
- ? `⚠️ 有 ${queueLen} 条消息未读,请尽快完成当前任务,或使用 ec ctl queue 读取后同步处理。`
250
- : `⚠️ 有 ${queueLen} 条消息未读,可使用 ec ctl queue 读取。`;
251
- ctx.recordReminder?.({ kind: 'queue_unread', text: queueMsg, queueLength: queueLen });
252
- ctx.injectToModel(queueMsg);
253
- }
254
- }
255
327
  // 工具计数(排除表态命令白名单)
256
328
  if (state.toolReportRequired && !isAllowedTool)
257
329
  state.toolCount++;
330
+ // 队列未读变化提醒:首次达到 3 条立即提醒,后续复用工具汇报的计数间隔,
331
+ // 避免连续工具调用时每次队列数量变化都重复注入。
332
+ const queueLen = ctx.getQueueLength();
333
+ const queueChanged = queueLen !== state.lastQueueReminderLen;
334
+ const toolReportDue = state.toolReportRequired
335
+ && !isAllowedTool
336
+ && state.toolCount > 0
337
+ && state.toolCount % state.toolReportInterval === 0;
338
+ let queueReminder;
339
+ const queueReminderDue = state.lastQueueReminderLen === 0
340
+ ? queueLen >= 3
341
+ : toolReportDue;
342
+ if (queueLen === 0) {
343
+ state.lastQueueReminderLen = 0;
344
+ }
345
+ else if (queueChanged && queueReminderDue) {
346
+ state.lastQueueReminderLen = queueLen;
347
+ queueReminder = queueLen >= 5
348
+ ? `⚠️ 有 ${queueLen} 条消息未读,请尽快完成当前任务,或使用 ec ctl queue 读取后同步处理。`
349
+ : `⚠️ 有 ${queueLen} 条消息未读,可使用 ec ctl queue 读取。`;
350
+ ctx.recordReminder?.({ kind: 'queue_unread', text: queueReminder, queueLength: queueLen });
351
+ }
352
+ if (queueReminder && !toolReportDue) {
353
+ ctx.injectToModel(queueReminder);
354
+ }
258
355
  // 每 N 次工具调用警告
259
- if (state.toolReportRequired && state.toolCount > 0 && state.toolCount % state.toolReportInterval === 0) {
356
+ // 只有计数推进到新阈值时才提醒;发送工具不能重新打开已解除的汇报门禁。
357
+ if (toolReportDue) {
260
358
  state.toolReportPending = true;
261
359
  const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
262
360
  const target = state.chatType === 'group' ? '群里' : '对方';
263
361
  const reportMsg = `⚠️ 工具调用已达到 ${state.toolCount} 次,请立即用 ${cmdHint} 向${target}汇报当前情况和下一步意图。`;
264
362
  ctx.recordReminder?.({ kind: 'tool_report', text: reportMsg, toolCount: state.toolCount });
265
- ctx.injectToModel(reportMsg);
363
+ ctx.injectToModel(queueReminder ? `${queueReminder}\n${reportMsg}` : reportMsg);
266
364
  }
267
365
  },
268
366
  // ─── onToolResult:成功发送回执后解除门禁 ───
@@ -291,6 +389,9 @@ export const proactiveFlow = {
291
389
  await ctx.updateSessionMeta({ lastProactiveFlag: true });
292
390
  }
293
391
  },
392
+ onTurnEnd(ctx) {
393
+ saveContinuation(ctx);
394
+ },
294
395
  // ─── afterProcess:Unknown skill 兜底 ───
295
396
  async afterProcess(ctx) {
296
397
  if (!ctx.streamResult.hasReceivedText && /^Unknown skill:\s+\S+/i.test(ctx.fullText.trim())) {
@@ -88,4 +88,7 @@ export class SingleSessionMode {
88
88
  afterProcess(ctx) {
89
89
  return this.flow().afterProcess?.(ctx);
90
90
  }
91
+ onTurnEnd(ctx) {
92
+ return this.flow().onTurnEnd?.(ctx);
93
+ }
91
94
  }
@@ -274,6 +274,7 @@ export class TriggerFeedbackDispatcher {
274
274
  ?? (input.trigger.origin?.channelKey ? channelTypeFromKey(input.trigger.origin.channelKey) : undefined),
275
275
  ...(eventSession ? { originChatType: messageChatType, originConversationId: target.channelId } : {}),
276
276
  originControl: !eventSession && input.trigger.origin?.channelKey === 'daemon',
277
+ noopSentinel: input.trigger.execution.noopSentinel,
277
278
  authorizedBy: input.trigger.authorizedBy,
278
279
  },
279
280
  };
@@ -4,7 +4,6 @@ import { agentTriggersDir } from '../paths.js';
4
4
  import { assertExplicitAunDeliveryRoutes, definitionRevision, normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from './validation.js';
5
5
  import { atomicWriteJson } from '../core/session/session-fs-store.js';
6
6
  import { TriggerHistoryStore } from './history.js';
7
- import { rewriteLegacyTriggerSessionPaths } from './legacy-session-history.js';
8
7
  export class TriggerDefinitionManager {
9
8
  agentAid;
10
9
  rootDir;
@@ -42,15 +41,6 @@ export class TriggerDefinitionManager {
42
41
  this.writeDefinition(definition);
43
42
  this.history.recordDefinition('trigger.updated', definition, { reason: 'legacy-aun-target-chat-type' });
44
43
  }
45
- if (typeof definition.execution.prompt === 'string') {
46
- const rewritten = rewriteLegacyTriggerSessionPaths(definition.execution.prompt, definition.agentAid);
47
- if (rewritten !== definition.execution.prompt) {
48
- definition.execution = { ...definition.execution, prompt: rewritten };
49
- definition.updatedAt = Date.now();
50
- this.writeDefinition(definition);
51
- this.history.recordDefinition('trigger.updated', definition, { reason: 'legacy-session-history-paths' });
52
- }
53
- }
54
44
  return definition;
55
45
  }
56
46
  require(triggerId) {
@@ -193,6 +193,17 @@ export class TriggerRuntimeScheduler {
193
193
  history(triggerId, limit = 100) {
194
194
  return this.manager.history.events(triggerId, limit);
195
195
  }
196
+ recentRuns(triggerId, limit = 50) {
197
+ return this.audit.recent(triggerId, limit).map(run => ({
198
+ runId: run.runId,
199
+ status: run.status,
200
+ ...(run.reason ? { reason: run.reason } : {}),
201
+ startedAt: run.startedAt,
202
+ finishedAt: run.finishedAt,
203
+ durationMs: Math.max(0, run.finishedAt - run.startedAt),
204
+ ...(run.error?.code ? { errorCode: run.error.code } : {}),
205
+ }));
206
+ }
196
207
  scriptPreview(definition) {
197
208
  if (definition.execution.type !== 'script' || !definition.execution.script)
198
209
  return undefined;
@@ -452,12 +452,34 @@ function normalizeExecution(value, _opts) {
452
452
  permissionMode: normalizeTriggerPermissionMode(raw.permissionMode),
453
453
  onError: normalizeOnError(raw.onError),
454
454
  noopSentinel: optionalString(raw.noopSentinel) ?? DEFAULT_NOOP_SENTINEL,
455
+ memoryMode: normalizeMemoryMode(raw.memoryMode),
456
+ memoryScope: normalizeMemoryScope(raw.memoryScope),
457
+ consolidationBatchDir: optionalString(raw.consolidationBatchDir ?? raw.consolidationLogBase),
458
+ consolidationChatDir: optionalString(raw.consolidationChatDir),
459
+ consolidationMemoryDir: optionalString(raw.consolidationMemoryDir),
455
460
  };
456
461
  if (execution.type === 'script' && execution.permissionMode === 'fullaccess') {
457
462
  throw new Error('script Trigger already executes as the daemon OS user and cannot declare execution.permissionMode=fullaccess');
458
463
  }
459
464
  return execution;
460
465
  }
466
+ const MEMORY_MODES = ['notes', 'generalize', 'promote', 'profile', 'forget'];
467
+ function normalizeMemoryMode(value) {
468
+ if (value === undefined)
469
+ return undefined;
470
+ if (typeof value !== 'string' || !MEMORY_MODES.includes(value)) {
471
+ throw new Error(`execution.memoryMode must be one of ${MEMORY_MODES.join(', ')}`);
472
+ }
473
+ return value;
474
+ }
475
+ function normalizeMemoryScope(value) {
476
+ if (value === undefined)
477
+ return undefined;
478
+ if (value !== 'relation' && value !== 'agent' && value !== 'owner') {
479
+ throw new Error('execution.memoryScope must be relation, agent, or owner');
480
+ }
481
+ return value;
482
+ }
461
483
  function normalizeExecutionBaseagent(value) {
462
484
  if (value === undefined)
463
485
  return undefined;
@@ -5,8 +5,10 @@ import net from 'net';
5
5
  import { agentmdGet, agentmdPut, updateAgentMdFrontmatterAvatar } from '../aun/aid/agentmd.js';
6
6
  import { storageRm, storageUpload } from '../aun/storage/index.js';
7
7
  import { logger } from './logger.js';
8
+ import sharp from 'sharp';
8
9
  import { validateImage } from './media-cache.js';
9
10
  const MAX_AVATAR_BYTES = 500 * 1024;
11
+ export const MAX_AVATAR_SOURCE_BYTES = 10 * 1024 * 1024;
10
12
  const MIN_AVATAR_DIMENSION = 128;
11
13
  const MAX_AVATAR_DIMENSION = 2048;
12
14
  const MIN_AVATAR_RATIO = 0.8;
@@ -20,6 +22,29 @@ const MIME_EXT = {
20
22
  'image/jpeg': 'jpg',
21
23
  'image/webp': 'webp',
22
24
  };
25
+ export async function normalizeAvatarImage(buffer) {
26
+ if (buffer.length > MAX_AVATAR_SOURCE_BYTES)
27
+ throw new Error('头像源文件超过 10 MiB 限制');
28
+ const input = sharp(buffer, { failOn: 'error', limitInputPixels: 80_000_000, animated: false });
29
+ const metadata = await input.metadata();
30
+ const sourceMimeType = metadata.format === 'jpeg' ? 'image/jpeg' : `image/${metadata.format ?? ''}`;
31
+ if (!['image/jpeg', 'image/png', 'image/webp'].includes(sourceMimeType)) {
32
+ throw new Error(`头像格式不支持:${sourceMimeType || 'unknown'};请提供 PNG、JPEG 或 WebP 图片`);
33
+ }
34
+ if (!metadata.width || !metadata.height)
35
+ throw new Error('无法读取头像尺寸');
36
+ const sizes = [512, 384, 256, 192, 128];
37
+ const qualities = [82, 74, 66, 58, 50];
38
+ for (let i = 0; i < sizes.length; i++) {
39
+ const output = await sharp(buffer, { failOn: 'error', limitInputPixels: 80_000_000, animated: false })
40
+ .rotate().resize(sizes[i], sizes[i], { fit: 'contain', background: '#ffffff' })
41
+ .jpeg({ mozjpeg: true, quality: qualities[i] }).toBuffer();
42
+ if (output.length <= MAX_AVATAR_BYTES) {
43
+ return { buffer: output, mimeType: 'image/jpeg', width: sizes[i], height: sizes[i], sourceWidth: metadata.width, sourceHeight: metadata.height };
44
+ }
45
+ }
46
+ throw new Error('图片经过缩放和压缩后仍超过头像 500 KiB 上限');
47
+ }
23
48
  function fail(error, code) {
24
49
  return { ok: false, error, code };
25
50
  }
@@ -50,12 +75,12 @@ function parseAvatarDataUrl(dataUrl) {
50
75
  return fail('avatar base64 编码无效', 'INVALID_ARGS');
51
76
  }
52
77
  const estimatedSize = estimateDecodedBytes(base64);
53
- if (estimatedSize > MAX_AVATAR_BYTES) {
54
- return fail(`头像大小超过 500KB 限制(当前: ${formatKb(estimatedSize)})`, 'INVALID_ARGS');
78
+ if (estimatedSize > MAX_AVATAR_SOURCE_BYTES) {
79
+ return fail(`头像源文件超过 ${formatKb(MAX_AVATAR_SOURCE_BYTES)} 限制(当前: ${formatKb(estimatedSize)})`, 'INVALID_ARGS');
55
80
  }
56
81
  const buffer = Buffer.from(base64, 'base64');
57
- if (buffer.length > MAX_AVATAR_BYTES) {
58
- return fail(`头像大小超过 500KB 限制(当前: ${formatKb(buffer.length)})`, 'INVALID_ARGS');
82
+ if (buffer.length > MAX_AVATAR_SOURCE_BYTES) {
83
+ return fail(`头像源文件超过 ${formatKb(MAX_AVATAR_SOURCE_BYTES)} 限制(当前: ${formatKb(buffer.length)})`, 'INVALID_ARGS');
59
84
  }
60
85
  return { mime: declaredMime, buffer };
61
86
  }
@@ -71,13 +96,16 @@ export async function uploadAvatar(aid, dataUrl, opts) {
71
96
  const parsed = parseAvatarDataUrl(dataUrl);
72
97
  if ('ok' in parsed)
73
98
  return parsed;
74
- const validated = await validateAvatarBuffer(parsed.buffer);
75
- if (!('dimensions' in validated))
76
- return validated;
77
- if (validated.mime !== parsed.mime) {
78
- return fail(`头像格式验证失败:声明 ${parsed.mime},实际 ${validated.mime}`, 'INVALID_ARGS');
99
+ const detected = await validateImage(parsed.buffer, {
100
+ maxSize: MAX_AVATAR_SOURCE_BYTES,
101
+ allowedMimes: ALLOWED_AVATAR_MIMES,
102
+ });
103
+ if (detected.mime === null)
104
+ return fail(`头像格式验证失败:${detected.reason}`, 'INVALID_ARGS');
105
+ if (detected.mime !== parsed.mime) {
106
+ return fail(`头像格式验证失败:声明 ${parsed.mime},实际 ${detected.mime}`, 'INVALID_ARGS');
79
107
  }
80
- return uploadValidatedAvatar(aid, parsed.buffer, validated.mime, validated.dimensions, opts);
108
+ return uploadAvatarBuffer(aid, parsed.buffer, opts);
81
109
  }
82
110
  function parseJpegDimensions(buffer) {
83
111
  let offset = 2;
@@ -258,9 +286,9 @@ async function downloadAvatarBuffer(sourceUrl) {
258
286
  return fail(`头像 URL 返回 HTTP ${response.status}`, 'UPLOAD_FAILED');
259
287
  }
260
288
  const length = Number(response.headers.get('content-length') || 0);
261
- if (length > MAX_AVATAR_BYTES) {
289
+ if (length > MAX_AVATAR_SOURCE_BYTES) {
262
290
  clearTimeout(timeout);
263
- return fail('头像大小超过 500KB 限制', 'INVALID_ARGS');
291
+ return fail('头像源文件超过 10 MiB 限制', 'INVALID_ARGS');
264
292
  }
265
293
  if (!response.body) {
266
294
  clearTimeout(timeout);
@@ -276,9 +304,9 @@ async function downloadAvatarBuffer(sourceUrl) {
276
304
  break;
277
305
  const chunk = Buffer.from(part.value);
278
306
  total += chunk.length;
279
- if (total > MAX_AVATAR_BYTES) {
307
+ if (total > MAX_AVATAR_SOURCE_BYTES) {
280
308
  await reader.cancel();
281
- return fail('头像大小超过 500KB 限制', 'INVALID_ARGS');
309
+ return fail('头像源文件超过 10 MiB 限制', 'INVALID_ARGS');
282
310
  }
283
311
  chunks.push(chunk);
284
312
  }
@@ -344,7 +372,7 @@ async function uploadValidatedAvatar(aid, buffer, mime, dimensions, opts) {
344
372
  }
345
373
  async function validateAvatarBuffer(buffer) {
346
374
  const validated = await validateImage(buffer, {
347
- maxSize: MAX_AVATAR_BYTES,
375
+ maxSize: MAX_AVATAR_SOURCE_BYTES,
348
376
  allowedMimes: ALLOWED_AVATAR_MIMES,
349
377
  });
350
378
  if (validated.mime === null)
@@ -367,9 +395,22 @@ export async function uploadAvatarFile(aid, filePath, opts) {
367
395
  /** Upload already-read image bytes; callers can use this after secure fd validation. */
368
396
  export async function uploadAvatarBuffer(aid, buffer, opts) {
369
397
  const validated = await validateAvatarBuffer(buffer);
370
- if (!('dimensions' in validated))
371
- return validated;
372
- return uploadValidatedAvatar(aid, buffer, validated.mime, validated.dimensions, opts);
398
+ if ('dimensions' in validated && buffer.length <= MAX_AVATAR_BYTES) {
399
+ return uploadValidatedAvatar(aid, buffer, validated.mime, validated.dimensions, opts);
400
+ }
401
+ try {
402
+ const normalized = await normalizeAvatarImage(buffer);
403
+ const uploaded = await uploadValidatedAvatar(aid, normalized.buffer, normalized.mimeType, { width: normalized.width, height: normalized.height }, opts);
404
+ return uploaded.ok ? {
405
+ ...uploaded,
406
+ normalized: true,
407
+ sourceWidth: normalized.sourceWidth,
408
+ sourceHeight: normalized.sourceHeight,
409
+ } : uploaded;
410
+ }
411
+ catch (error) {
412
+ return fail(`${'error' in validated && validated.error ? validated.error : '头像不符合规范'};自动规范化失败: ${messageOf(error)}`, 'INVALID_ARGS');
413
+ }
373
414
  }
374
415
  /** Download an HTTPS image with redirect/SSRF/size guards, then upload it. */
375
416
  export async function uploadAvatarUrl(aid, url, opts) {
@@ -109,19 +109,22 @@
109
109
  "id": "api-error-502",
110
110
  "match": "api error: 502",
111
111
  "action": "retry",
112
- "type": "api_error"
112
+ "type": "api_error",
113
+ "message": "❌ API 网关异常,请稍后重试"
113
114
  },
114
115
  {
115
116
  "id": "api-error-503",
116
117
  "match": "api error: 503",
117
118
  "action": "retry",
118
- "type": "api_error"
119
+ "type": "api_error",
120
+ "message": "❌ API 服务暂时不可用,请稍后重试"
119
121
  },
120
122
  {
121
123
  "id": "api-error-504",
122
124
  "match": "api error: 504",
123
125
  "action": "retry",
124
- "type": "api_error"
126
+ "type": "api_error",
127
+ "message": "❌ API 请求超时,请稍后重试"
125
128
  },
126
129
  {
127
130
  "id": "not-valid-json",
@@ -25,6 +25,7 @@ export function isExecutionFailure(error) {
25
25
  return error instanceof ExecutionTimeoutError || error instanceof OrphanedTargetSessionError;
26
26
  }
27
27
  export const CLAUDE_STARTUP_ARGUMENTS_TOO_LONG = 'claude_startup_arguments_too_long';
28
+ export const CLAUDE_CODE_STARTUP_FAILED = 'claude_code_startup_failed';
28
29
  /** A completed provider response exhausted its output budget, rather than a transport failure. */
29
30
  export function isOutputTokenLimitError(error) {
30
31
  const record = error && typeof error === 'object' ? error : undefined;
@@ -58,6 +59,22 @@ export function createClaudeStartupArgumentsTooLongError(error) {
58
59
  wrapped.cause = error;
59
60
  return wrapped;
60
61
  }
62
+ export function isClaudeCodeStartupProcessError(error) {
63
+ const record = error && typeof error === 'object' ? error : undefined;
64
+ const code = String(record?.code ?? '').toLowerCase();
65
+ const message = String(record?.message ?? error ?? '').toLowerCase();
66
+ return code === 'eftype'
67
+ || message.includes('spawn eftype')
68
+ || message.includes('not a valid application for this os platform');
69
+ }
70
+ export function createClaudeCodeStartupError(error) {
71
+ const wrapped = new Error(`${CLAUDE_CODE_STARTUP_FAILED}: Claude Code SDK 本地运行时启动失败,任务未执行`);
72
+ wrapped.name = 'ClaudeCodeStartupError';
73
+ wrapped.code = CLAUDE_CODE_STARTUP_FAILED;
74
+ if (error !== undefined)
75
+ wrapped.cause = error;
76
+ return wrapped;
77
+ }
61
78
  export var ErrorType;
62
79
  (function (ErrorType) {
63
80
  ErrorType["SDK_TIMEOUT"] = "sdk_timeout";
@@ -71,6 +88,7 @@ export var ErrorType;
71
88
  ErrorType["SESSION_UNAVAILABLE"] = "session_unavailable";
72
89
  ErrorType["SANDBOX_INITIALIZATION_FAILED"] = "sandbox_initialization_failed";
73
90
  ErrorType["CLAUDE_STARTUP_ARGUMENTS_TOO_LONG"] = "claude_startup_arguments_too_long";
91
+ ErrorType["CLAUDE_CODE_STARTUP_FAILED"] = "claude_code_startup_failed";
74
92
  ErrorType["UNKNOWN"] = "unknown";
75
93
  })(ErrorType || (ErrorType = {}));
76
94
  /**
@@ -329,6 +347,9 @@ export function classifyError(error) {
329
347
  if (isClaudeStartupArgumentsTooLong(error)) {
330
348
  return ErrorType.CLAUDE_STARTUP_ARGUMENTS_TOO_LONG;
331
349
  }
350
+ if (error?.code === CLAUDE_CODE_STARTUP_FAILED) {
351
+ return ErrorType.CLAUDE_CODE_STARTUP_FAILED;
352
+ }
332
353
  // 字典优先 — 命中则直接返回
333
354
  const rule = matchErrorRule(msg);
334
355
  if (rule) {
@@ -410,6 +431,10 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
410
431
  const prefix = includeEmoji ? '⚠️ ' : '';
411
432
  return `${prefix}当前 Claude 启动参数过长,任务未执行`;
412
433
  }
434
+ if (error?.code === CLAUDE_CODE_STARTUP_FAILED) {
435
+ const prefix = includeEmoji ? '❌ ' : '';
436
+ return `${prefix}Claude Code SDK 本地运行时启动失败(${CLAUDE_CODE_STARTUP_FAILED}),当前任务未执行。请重新安装并对齐 Claude Code SDK 与 Windows 原生组件版本,然后重启 EvolCore 后重试`;
437
+ }
413
438
  // terminalReason 提供更精确的错误提示(SDK 0.2.100+)
414
439
  if (terminalReason) {
415
440
  const prefix = includeEmoji ? '❌ ' : '';
@@ -433,6 +458,8 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
433
458
  return `${warnPrefix}执行沙箱启动失败(sandbox_initialization_failed),当前操作未执行;请检查文件系统挂载规则及 user namespace/seccomp 配置`;
434
459
  case 'claude_startup_arguments_too_long':
435
460
  return `${warnPrefix}当前 Claude 启动参数过长,任务未执行`;
461
+ case 'claude_code_startup_failed':
462
+ return `${prefix}Claude Code SDK 本地运行时启动失败(${CLAUDE_CODE_STARTUP_FAILED}),当前任务未执行。请重新安装并对齐 Claude Code SDK 与 Windows 原生组件版本,然后重启 EvolCore 后重试`;
436
463
  case 'aborted_streaming':
437
464
  case 'aborted_tools':
438
465
  return `${prefix}任务已中断`;