evolcore 0.0.32 → 0.0.34

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 (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. package/kits/docs/venues/group.md +0 -43
@@ -1,6 +1,8 @@
1
+ import { appendExpireEntry, recordNextLaunchCleanup, cleanupPreviousByKind, runExpireCleanup, cleanupSessionRecord, } from '../core/background-session/session-cleanup.js';
1
2
  import { ExecutionTimeoutError } from '../utils/error-utils.js';
2
3
  import { normalizeExecutionMode } from '../trigger/validation.js';
3
4
  import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
5
+ import { assertSystemSessionLaunchable } from '../core/system-session/registry.js';
4
6
  const DEFAULT_WATCHDOG_MS = 11 * 60 * 1000;
5
7
  export class DaemonChannel {
6
8
  sessionManager;
@@ -17,6 +19,7 @@ export class DaemonChannel {
17
19
  thread: true,
18
20
  };
19
21
  pending = new Map();
22
+ bgPending = new Map();
20
23
  watchdogMs;
21
24
  totalExecutionMs;
22
25
  constructor(sessionManager, messageQueue, opts = {}) {
@@ -26,6 +29,8 @@ export class DaemonChannel {
26
29
  this.totalExecutionMs = normalizeTotalExecutionMs(opts.totalExecutionMs);
27
30
  }
28
31
  async converse(prompt, ctx) {
32
+ // 启动前跑一次 expire 扫描(成本低,只读少量小文件)
33
+ void runExpireCleanup(this.sessionManager).catch(() => { });
29
34
  const runId = ctx.runId;
30
35
  const pendingId = ctx.attemptId ?? runId;
31
36
  const messageId = pendingId;
@@ -41,6 +46,11 @@ export class DaemonChannel {
41
46
  ? undefined
42
47
  : setTimeout(() => this.totalExecutionTimeout(pendingId, remainingExecutionMs), remainingExecutionMs);
43
48
  totalExecutionTimer?.unref?.();
49
+ // trigger 清理策略:once → expire(3天),其他 → expire(30天)
50
+ const isOnce = ctx.trigger.source?.type === 'once';
51
+ const triggerCleanupPolicy = isOnce
52
+ ? { mode: 'expire', ttlMs: 3 * 24 * 3600 * 1000 }
53
+ : { mode: 'expire', ttlMs: 30 * 24 * 3600 * 1000 };
44
54
  this.pending.set(pendingId, {
45
55
  resolve,
46
56
  startedAt,
@@ -52,6 +62,11 @@ export class DaemonChannel {
52
62
  abortSignal: ctx.signal,
53
63
  runId,
54
64
  acc: { text: '', files: [], anomalies: [], toolCallCount: 0, seenToolCallIds: new Set() },
65
+ cleanupPolicy: triggerCleanupPolicy,
66
+ channelId: ctx.session.channelId,
67
+ agentAid: ctx.trigger.agentAid,
68
+ kind: `trigger:${ctx.trigger.id}`,
69
+ sessionId: ctx.session.id,
55
70
  });
56
71
  });
57
72
  const message = {
@@ -85,8 +100,13 @@ export class DaemonChannel {
85
100
  originChannelKey: ctx.trigger.origin?.channelKey,
86
101
  originChannelType: ctx.trigger.origin?.channelType
87
102
  ?? (ctx.trigger.origin?.channelKey ? channelTypeFromKey(ctx.trigger.origin.channelKey) : undefined),
103
+ originChatType: ctx.trigger.origin?.delivery?.chatType,
104
+ originConversationId: ctx.trigger.origin?.delivery?.chatType === 'group'
105
+ ? ctx.trigger.origin.channelId
106
+ : ctx.trigger.origin?.peerId,
88
107
  originControl: ctx.trigger.origin?.channelKey === 'daemon',
89
108
  authorizedBy: ctx.trigger.authorizedBy,
109
+ labels: ctx.trigger.labels,
90
110
  },
91
111
  };
92
112
  const onAbort = () => {
@@ -128,6 +148,15 @@ export class DaemonChannel {
128
148
  return promise;
129
149
  }
130
150
  async send(envelope, payload) {
151
+ // bg-session 路径:优先查 bgPending
152
+ const bgRunId = this.bgRunIdFromEnvelope(envelope);
153
+ if (bgRunId) {
154
+ const bgSlot = this.bgPending.get(bgRunId);
155
+ if (bgSlot && !bgSlot.terminated) {
156
+ this.handleBgSessionPayload(bgRunId, bgSlot, payload);
157
+ return;
158
+ }
159
+ }
131
160
  const runId = this.runIdFromEnvelope(envelope);
132
161
  if (!runId)
133
162
  return;
@@ -225,6 +254,9 @@ export class DaemonChannel {
225
254
  // 记忆整理会话:把会话原型与整理模式写进 metadata,供渲染层选 manifest / 手册,
226
255
  // 供 ipc 侧校验写命令的 --scope。
227
256
  const { memoryMode, memoryScope, consolidationBatchDir, consolidationChatDir, consolidationMemoryDir } = trigger.execution;
257
+ // 系统会话:类型 / subtype / runner 校验不过直接拒绝(Gemini 不支持),不降级成普通会话
258
+ if (memoryMode)
259
+ assertSystemSessionLaunchable({ type: 'memory', subtype: memoryMode }, baseagent);
228
260
  // 反馈目标与触发方式(consolidation.ts 在 normalize 后补挂的字段):
229
261
  // 收尾时据此投递反馈,并判断是否该反馈(定时触发不反馈)。
230
262
  const { feedbackOrigin, consolidationTriggeredBy } = trigger.execution;
@@ -232,7 +264,7 @@ export class DaemonChannel {
232
264
  channelKey: this.channelKey,
233
265
  peerId: `trigger:${trigger.id}`,
234
266
  peerName: trigger.name,
235
- ...(memoryMode ? { sessionType: 'memory', memoryMode } : {}),
267
+ ...(memoryMode ? { systemSession: { type: 'memory', subtype: memoryMode }, sessionType: 'memory', memoryMode } : {}),
236
268
  ...(memoryScope ? { memoryScope } : {}),
237
269
  ...(consolidationBatchDir ? { consolidationBatchDir } : {}),
238
270
  ...(consolidationChatDir ? { consolidationChatDir } : {}),
@@ -270,6 +302,11 @@ export class DaemonChannel {
270
302
  const pendingId = metadata?.triggerAttemptId ?? metadata?.triggerRunId;
271
303
  return typeof pendingId === 'string' ? pendingId : undefined;
272
304
  }
305
+ bgRunIdFromEnvelope(envelope) {
306
+ const metadata = envelope.replyContext?.metadata;
307
+ const runId = metadata?.bgSessionRunId;
308
+ return typeof runId === 'string' ? runId : undefined;
309
+ }
273
310
  recordToolCall(slot, callId) {
274
311
  if (typeof callId !== 'string' || !callId) {
275
312
  slot.acc.toolCallCount += 1;
@@ -290,13 +327,14 @@ export class DaemonChannel {
290
327
  ?? (origin.channelKey ? channelTypeFromKey(origin.channelKey) : undefined);
291
328
  if (!channelType)
292
329
  return undefined;
330
+ const chatType = origin.delivery?.chatType ?? 'private';
293
331
  return resolvePeerRoleDetail({
294
332
  selfAid: trigger.agentAid,
295
333
  channelKey: origin.channelKey,
296
334
  channelType,
297
- chatType: 'private',
335
+ chatType,
298
336
  actorId: origin.peerId,
299
- conversationId: origin.peerId,
337
+ conversationId: chatType === 'group' ? origin.channelId : origin.peerId,
300
338
  }).effectiveRole ?? undefined;
301
339
  }
302
340
  finish(runId, status, metadata) {
@@ -346,6 +384,29 @@ export class DaemonChannel {
346
384
  toolCallCount: Math.max(slot.acc.toolCallCount, completedToolCallCount),
347
385
  },
348
386
  });
387
+ // trigger 清理策略
388
+ const policy = slot.cleanupPolicy;
389
+ if (policy.mode === 'immediate') {
390
+ void cleanupSessionRecord({ sessionId: slot.sessionId, channelId: slot.channelId, agentAid: slot.agentAid }, this.sessionManager);
391
+ }
392
+ else if (policy.mode === 'on_next_launch') {
393
+ recordNextLaunchCleanup(slot.kind, {
394
+ channelId: slot.channelId,
395
+ sessionId: slot.sessionId,
396
+ agentAid: slot.agentAid,
397
+ recordedAt: Date.now(),
398
+ });
399
+ }
400
+ else if (policy.mode === 'expire') {
401
+ appendExpireEntry({
402
+ expireAt: Date.now() + policy.ttlMs,
403
+ sessionId: slot.sessionId,
404
+ channelId: slot.channelId,
405
+ kind: slot.kind,
406
+ agentAid: slot.agentAid,
407
+ });
408
+ }
409
+ // 'keep' 模式:不做任何处理
349
410
  }
350
411
  fail(runId, reason, text) {
351
412
  const slot = this.pending.get(runId);
@@ -412,6 +473,203 @@ export class DaemonChannel {
412
473
  }
413
474
  queue.cancelActive?.(messageId);
414
475
  }
476
+ // ── bg-session 生命周期 ──
477
+ handleBgSessionPayload(runId, slot, payload) {
478
+ if (slot.terminated)
479
+ return;
480
+ switch (payload.kind) {
481
+ case 'result.text':
482
+ if (payload.isFinal)
483
+ slot.text = payload.text;
484
+ else
485
+ slot.text += payload.text;
486
+ break;
487
+ case 'status.completed':
488
+ this.finishBgSession(runId, slot, 'completed');
489
+ break;
490
+ case 'result.error':
491
+ this.finishBgSession(runId, slot, 'error', payload.text ?? payload.reason ?? 'unknown error');
492
+ break;
493
+ case 'system.error':
494
+ this.finishBgSession(runId, slot, 'error', payload.text ?? payload.subtype ?? 'unknown error');
495
+ break;
496
+ case 'status.error':
497
+ this.finishBgSession(runId, slot, 'error', payload.metadata?.message ?? 'status error');
498
+ break;
499
+ case 'status.interrupted':
500
+ {
501
+ const reason = typeof payload.metadata?.reason === 'string'
502
+ ? payload.metadata.reason
503
+ : 'interrupted';
504
+ const outcome = slot.abortSignal?.aborted ? 'cancelled' : 'interrupted';
505
+ this.finishBgSession(runId, slot, outcome, reason);
506
+ }
507
+ break;
508
+ case 'status.timeout':
509
+ this.finishBgSession(runId, slot, 'error', 'bg-session watchdog timeout');
510
+ break;
511
+ default:
512
+ break;
513
+ }
514
+ }
515
+ finishBgSession(runId, slot, outcome, error) {
516
+ if (slot.terminated)
517
+ return;
518
+ slot.terminated = true;
519
+ if (slot.watchdogTimer)
520
+ clearTimeout(slot.watchdogTimer);
521
+ if (slot.abortListener)
522
+ slot.abortSignal?.removeEventListener('abort', slot.abortListener);
523
+ this.bgPending.delete(runId);
524
+ // 会话结束后按清理策略处理
525
+ const policy = slot.cleanupPolicy;
526
+ if (policy.mode === 'immediate') {
527
+ void cleanupSessionRecord({ sessionId: slot.sessionId, channelId: slot.channelId, agentAid: slot.agentAid }, this.sessionManager);
528
+ }
529
+ else if (policy.mode === 'on_next_launch') {
530
+ recordNextLaunchCleanup(slot.kind, {
531
+ channelId: slot.channelId,
532
+ sessionId: slot.sessionId,
533
+ agentAid: slot.agentAid,
534
+ recordedAt: Date.now(),
535
+ });
536
+ }
537
+ else if (policy.mode === 'expire') {
538
+ appendExpireEntry({
539
+ expireAt: Date.now() + policy.ttlMs,
540
+ sessionId: slot.sessionId,
541
+ channelId: slot.channelId,
542
+ kind: slot.kind,
543
+ agentAid: slot.agentAid,
544
+ });
545
+ }
546
+ // 'keep' 模式:不做任何处理
547
+ slot.resolve({
548
+ sessionId: slot.sessionId,
549
+ outcome,
550
+ ...(slot.text ? { text: slot.text } : {}),
551
+ ...(error ? { error } : {}),
552
+ });
553
+ }
554
+ /**
555
+ * 拉起一个独立后台会话。会话创建、首条消息入队后即返回 handle;
556
+ * 会话结束由 handle.result 通知。实现 BackgroundSessionLauncher 接口。
557
+ * 详见 docs/background-session/background-session-launcher.md
558
+ */
559
+ async launchBackgroundSession(spec) {
560
+ const runId = `bg-${spec.kind}-${spec.instanceId}`;
561
+ const channelId = `bg:${spec.kind}:${spec.instanceId}`;
562
+ const cleanupPolicy = spec.cleanupPolicy ?? { mode: 'on_next_launch' };
563
+ // 启动前清理:on_next_launch 清理同 kind 历史,expire 扫描到期条目
564
+ if (cleanupPolicy.mode === 'on_next_launch') {
565
+ await cleanupPreviousByKind(spec.kind, this.sessionManager);
566
+ }
567
+ else if (cleanupPolicy.mode !== 'keep') {
568
+ // immediate 和 expire 模式都在启动时跑一次 expire 扫描
569
+ await runExpireCleanup(this.sessionManager);
570
+ }
571
+ const session = await this.sessionManager.getOrCreateSession(this.channelName, channelId, spec.projectPath, channelId, // threadId,每次独立,保证 by_run 语义
572
+ {
573
+ channelKey: this.channelKey,
574
+ peerId: `bg:${spec.kind}`,
575
+ peerName: spec.kind,
576
+ peerType: 'system',
577
+ ...(spec.metadata ?? {}),
578
+ }, spec.kind, undefined, 'private', spec.baseagent, spec.agentAid, this.channelName, 'system');
579
+ const promise = new Promise((resolve) => {
580
+ const slot = {
581
+ resolve,
582
+ sessionId: session.id,
583
+ channelId,
584
+ kind: spec.kind,
585
+ startedAt: Date.now(),
586
+ abortSignal: spec.signal,
587
+ text: '',
588
+ terminated: false,
589
+ cleanupPolicy,
590
+ agentAid: spec.agentAid,
591
+ runtimeDir: spec.runtimeDir,
592
+ watchdogTimer: setTimeout(() => {
593
+ this.finishBgSession(runId, slot, 'error', 'bg-session watchdog timeout');
594
+ }, this.watchdogMs),
595
+ };
596
+ slot.watchdogTimer?.unref?.();
597
+ if (spec.signal) {
598
+ const onAbort = () => {
599
+ void this.cancelDelivery(runId).catch(() => { });
600
+ };
601
+ slot.abortListener = onAbort;
602
+ spec.signal.addEventListener('abort', onAbort, { once: true });
603
+ if (spec.signal.aborted)
604
+ onAbort();
605
+ }
606
+ this.bgPending.set(runId, slot);
607
+ });
608
+ const message = {
609
+ channel: this.channelName,
610
+ channelType: this.channelName,
611
+ channelId: session.channelId,
612
+ selfAID: spec.agentAid,
613
+ baseagent: spec.baseagent,
614
+ threadId: session.threadId || '',
615
+ chatType: 'private',
616
+ peerId: `bg:${spec.kind}`,
617
+ peerName: spec.kind,
618
+ peerType: 'system',
619
+ content: spec.prompt,
620
+ messageId: runId,
621
+ timestamp: Date.now(),
622
+ source: 'bg-session',
623
+ bgSessionMeta: {
624
+ kind: spec.kind,
625
+ runId,
626
+ instanceId: spec.instanceId,
627
+ boundSessionId: session.id,
628
+ },
629
+ replyContext: {
630
+ metadata: { bgSessionRunId: runId },
631
+ },
632
+ };
633
+ void this.messageQueue.enqueue?.(session.id, message, spec.projectPath, {
634
+ interruptible: false,
635
+ agentName: spec.agentAid,
636
+ sessionKeyField: session.sessionKey,
637
+ selfAID: spec.agentAid,
638
+ });
639
+ let settled = false;
640
+ void promise.then(() => { settled = true; });
641
+ const sessionId = session.id;
642
+ return {
643
+ sessionId,
644
+ result: promise,
645
+ waitForAgentSessionId: (opts) => this.waitForAgentSessionId(sessionId, () => settled, opts),
646
+ };
647
+ }
648
+ /**
649
+ * 轮询 session 记录,等 runner 写入 agentSessionId。
650
+ * 会话刚创建时只有 evolcore sessionId,agentSessionId 要等 runner 初始化并开始第一轮才写入。
651
+ * 会话结束后再读一次即停止,避免对已结束的会话空等到超时。
652
+ */
653
+ async waitForAgentSessionId(sessionId, isSettled, opts = {}) {
654
+ const deadline = Date.now() + (opts.timeoutMs ?? 10_000);
655
+ const interval = opts.intervalMs ?? 250;
656
+ while (true) {
657
+ const settledBeforeRead = isSettled();
658
+ try {
659
+ const id = (await this.sessionManager.getSessionById(sessionId))?.agentSessionId;
660
+ if (id)
661
+ return id;
662
+ }
663
+ catch { /* 会话记录写到一半,下一轮再读 */ }
664
+ if (settledBeforeRead || Date.now() >= deadline)
665
+ return undefined;
666
+ await new Promise(r => setTimeout(r, interval));
667
+ }
668
+ }
669
+ /** 实现 BackgroundSessionLauncher 接口 */
670
+ launch(spec) {
671
+ return this.launchBackgroundSession(spec);
672
+ }
415
673
  }
416
674
  function isObject(value) {
417
675
  return !!value && typeof value === 'object' && !Array.isArray(value);
@@ -41,6 +41,30 @@ function formatBytes(bytes) {
41
41
  return `${(bytes / 1024).toFixed(1)}KB`;
42
42
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
43
43
  }
44
+ /** 终端显示宽度:CJK 及全角字符占 2 列,ASCII/拉丁占 1 列 */
45
+ function displayWidth(s) {
46
+ let w = 0;
47
+ for (const ch of s) {
48
+ const cp = ch.codePointAt(0);
49
+ if ((cp >= 0x1100 && cp <= 0x115F) ||
50
+ (cp >= 0x2E80 && cp <= 0x303E) ||
51
+ (cp >= 0x3040 && cp <= 0x33FF) ||
52
+ (cp >= 0x3400 && cp <= 0x9FFF) ||
53
+ (cp >= 0xAC00 && cp <= 0xD7AF) ||
54
+ (cp >= 0xF900 && cp <= 0xFAFF) ||
55
+ (cp >= 0xFF01 && cp <= 0xFF60) ||
56
+ (cp >= 0xFFE0 && cp <= 0xFFE6)) {
57
+ w += 2;
58
+ }
59
+ else {
60
+ w += 1;
61
+ }
62
+ }
63
+ return w;
64
+ }
65
+ function padEndDisplay(s, targetWidth) {
66
+ return s + ' '.repeat(Math.max(0, targetWidth - displayWidth(s)));
67
+ }
44
68
  // ==================== Agent ====================
45
69
  export async function cmdAgent(args) {
46
70
  args = canonicalizeEvolcoreCommandArgv(['ec', 'agent', ...args]).slice(2);
@@ -78,8 +102,8 @@ Commands:
78
102
  persona show 查看当前 Agent 的 persona.md
79
103
  persona --text <text> 用文本设置当前 Agent 的 persona.md
80
104
  persona --file <path> 用 .md/.txt 文件设置当前 Agent 的 persona.md
81
- welcome show 查看已确认的 Bootstrap welcome 文案
82
- welcome --text <text> 保存 Owner 已确认的 Bootstrap welcome 文案
105
+ welcome show 查看 Agent 自主准备的 Bootstrap welcome 文案
106
+ welcome --text <text> 保存 Agent 自主准备的 Bootstrap welcome 文案
83
107
  rename <aid> <name> 修改 agent.md 中的显示名称
84
108
  ready <aid> 标记 bootstrap 完成,进入 active 状态
85
109
  reload [aid] 热重载配置(无参数=全量 resync)
@@ -220,25 +244,28 @@ Options:
220
244
  // 表头跟随系统语言
221
245
  const isChinese = isHostChinese();
222
246
  const headers = isChinese
223
- ? { name: '名称', personal: 'Personal名', status: '状态', channels: '渠道', project: '项目', baseagent: '基座', lastActive: '最后活跃' }
224
- : { name: 'NAME', personal: 'PERSONAL', status: 'STATUS', channels: 'CHANNELS', project: 'PROJECT', baseagent: 'BASEAGENT', lastActive: 'LAST ACTIVE' };
225
- // 计算各列实际需要的宽度
226
- let maxNameLen = headers.name.length;
227
- let maxPersonalLen = headers.personal.length;
228
- let maxStatusLen = headers.status.length;
229
- let maxChannelsLen = headers.channels.length;
230
- let maxProjectLen = headers.project.length;
231
- let maxBaseagentLen = headers.baseagent.length;
247
+ ? { name: '名称', personal: 'Personal名', status: '状态', channels: '渠道', project: '项目', baseagent: '基座', model: '模型', lastActive: '最后活跃' }
248
+ : { name: 'NAME', personal: 'PERSONAL', status: 'STATUS', channels: 'CHANNELS', project: 'PROJECT', baseagent: 'BASEAGENT', model: 'MODEL', lastActive: 'LAST ACTIVE' };
249
+ // 计算各列实际需要的显示宽度(CJK 字符占 2 列)
250
+ let maxNameLen = displayWidth(headers.name);
251
+ let maxPersonalLen = displayWidth(headers.personal);
252
+ let maxStatusLen = displayWidth(headers.status);
253
+ let maxChannelsLen = displayWidth(headers.channels);
254
+ let maxProjectLen = displayWidth(headers.project);
255
+ let maxBaseagentLen = displayWidth(headers.baseagent);
256
+ let maxModelLen = displayWidth(headers.model);
232
257
  for (const info of result.agents) {
233
- maxNameLen = Math.max(maxNameLen, info.name.length);
234
- maxPersonalLen = Math.max(maxPersonalLen, (info.personalName || '—').length);
235
- const displayedStatus = `${info.status || 'unknown'}${info.stale ? ' (stale)' : ''}`;
236
- maxStatusLen = Math.max(maxStatusLen, displayedStatus.length);
258
+ maxNameLen = Math.max(maxNameLen, displayWidth(info.name));
259
+ maxPersonalLen = Math.max(maxPersonalLen, displayWidth(info.personalName || '—'));
260
+ const lcSuffix = info.lifecycle && info.lifecycle !== 'active' ? ` (${info.lifecycle})` : '';
261
+ const displayedStatus = `${info.status || 'unknown'}${info.stale ? ' (stale)' : ''}${lcSuffix}`;
262
+ maxStatusLen = Math.max(maxStatusLen, displayWidth(displayedStatus));
237
263
  const channelsStr = info.channels?.length > 0 ? info.channels.join(', ') : '—';
238
- maxChannelsLen = Math.max(maxChannelsLen, channelsStr.length);
264
+ maxChannelsLen = Math.max(maxChannelsLen, displayWidth(channelsStr));
239
265
  const projectStr = info.projectPath ? path.basename(info.projectPath) : '—';
240
- maxProjectLen = Math.max(maxProjectLen, projectStr.length);
241
- maxBaseagentLen = Math.max(maxBaseagentLen, (info.baseagent || '—').length);
266
+ maxProjectLen = Math.max(maxProjectLen, displayWidth(projectStr));
267
+ maxBaseagentLen = Math.max(maxBaseagentLen, displayWidth(info.baseagent || '—'));
268
+ maxModelLen = Math.max(maxModelLen, displayWidth(info.model || '—'));
242
269
  }
243
270
  // 加 2 作为列间距
244
271
  const colName = maxNameLen + 2;
@@ -247,18 +274,25 @@ Options:
247
274
  const colChannels = maxChannelsLen + 1;
248
275
  const colProject = maxProjectLen + 2;
249
276
  const colBaseagent = maxBaseagentLen + 2;
250
- console.log(headers.name.padEnd(colName) + headers.personal.padEnd(colPersonal) + headers.status.padEnd(colStatus) + headers.channels.padEnd(colChannels) +
251
- headers.project.padEnd(colProject) + headers.baseagent.padEnd(colBaseagent) + headers.lastActive);
277
+ const colModel = maxModelLen + 2;
278
+ console.log(padEndDisplay(headers.name, colName) + padEndDisplay(headers.personal, colPersonal) +
279
+ padEndDisplay(headers.status, colStatus) + padEndDisplay(headers.channels, colChannels) +
280
+ padEndDisplay(headers.project, colProject) + padEndDisplay(headers.baseagent, colBaseagent) +
281
+ padEndDisplay(headers.model, colModel) + headers.lastActive);
252
282
  for (const info of result.agents) {
253
283
  const name = info.name;
254
284
  const personal = info.personalName || '—';
255
- const status = `${info.status || 'unknown'}${info.stale ? ' (stale)' : ''}`;
285
+ const lcSuffix = info.lifecycle && info.lifecycle !== 'active' ? ` (${info.lifecycle})` : '';
286
+ const status = `${info.status || 'unknown'}${info.stale ? ' (stale)' : ''}${lcSuffix}`;
256
287
  const channels = info.channels?.length > 0 ? info.channels.join(', ') : '—';
257
288
  const project = info.projectPath ? path.basename(info.projectPath) : '—';
258
289
  const baseagent = info.baseagent || '—';
290
+ const model = info.model || '—';
259
291
  const lastActive = info.lastActivity ? formatTimeAgo(Date.now() - info.lastActivity) : '—';
260
- console.log(name.padEnd(colName) + personal.padEnd(colPersonal) + status.padEnd(colStatus) + channels.padEnd(colChannels) +
261
- project.padEnd(colProject) + baseagent.padEnd(colBaseagent) + lastActive);
292
+ console.log(padEndDisplay(name, colName) + padEndDisplay(personal, colPersonal) +
293
+ padEndDisplay(status, colStatus) + padEndDisplay(channels, colChannels) +
294
+ padEndDisplay(project, colProject) + padEndDisplay(baseagent, colBaseagent) +
295
+ padEndDisplay(model, colModel) + lastActive);
262
296
  }
263
297
  return;
264
298
  }
@@ -697,7 +731,7 @@ Options:
697
731
  console.log(`用法: ${MAIN_CLI_BIN} agent welcome show [--format json]
698
732
  ${MAIN_CLI_BIN} agent welcome --text <text> [--format json]
699
733
 
700
- 读取或保存当前 Agent 在 Bootstrap 中经 Owner 确认的 welcome 文案。文案固定保存到 personal/welcome.md。`);
734
+ 读取或保存当前 Agent 在 Bootstrap 中自主准备的 welcome 文案。文案固定保存到 personal/welcome.md。`);
701
735
  return;
702
736
  }
703
737
  const result = { ok: false, code: 'INVALID_ARGS', error: error.message };
package/dist/cli/agent.js CHANGED
@@ -553,7 +553,7 @@ export async function agentPersona(aid, options = {}) {
553
553
  return { ok: false, code: 'WRITE_FAILED', error: e?.message || String(e) };
554
554
  }
555
555
  }
556
- /** Read or replace the current Agent's confirmed Bootstrap welcome template. */
556
+ /** Read or replace the current Agent's self-prepared Bootstrap welcome template. */
557
557
  export async function agentWelcome(aid, options = {}) {
558
558
  if (!isValidAid(aid))
559
559
  return { ok: false, code: 'INVALID_ARGS', error: 'Invalid Agent AID' };