evolcore 0.0.31 → 0.0.33

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 (179) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/install-codex-managed-hooks.mjs +124 -37
  3. package/dist/agents/claude-runner.js +29 -2
  4. package/dist/agents/codex/app-server-client.js +3 -0
  5. package/dist/agents/codex/diagnostics.js +76 -44
  6. package/dist/agents/codex-runner.js +51 -24
  7. package/dist/agents/ecagent-runner.js +9 -13
  8. package/dist/agents/gemini-runner.js +4 -0
  9. package/dist/aun/aid/store.js +0 -81
  10. package/dist/aun/group/fs-download.js +1 -10
  11. package/dist/aun/group/operations.js +0 -1
  12. package/dist/aun/msg/history.js +1 -0
  13. package/dist/aun/msg/managed-operation.js +130 -32
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/aun/service-proxy.js +12 -18
  17. package/dist/aun/storage/index.js +29 -0
  18. package/dist/channels/aun.js +38 -137
  19. package/dist/channels/daemon.js +257 -2
  20. package/dist/cli/agent-command.js +64 -31
  21. package/dist/cli/agent-create.js +43 -24
  22. package/dist/cli/agent.js +16 -70
  23. package/dist/cli/aun-commands.js +512 -32
  24. package/dist/cli/contact.js +8 -2
  25. package/dist/cli/ctl-command.js +1 -2
  26. package/dist/cli/daemon-commands.js +82 -13
  27. package/dist/cli/debug-mode.js +154 -0
  28. package/dist/cli/diag.js +2 -2
  29. package/dist/cli/handoff-command.js +18 -5
  30. package/dist/cli/index.js +81 -64
  31. package/dist/cli/init.js +2 -2
  32. package/dist/cli/managed-command-guard.js +5 -0
  33. package/dist/cli/memory-command.js +93 -12
  34. package/dist/cli/model.js +14 -138
  35. package/dist/cli/queue-command.js +51 -0
  36. package/dist/cli/response.js +5 -54
  37. package/dist/cli/session-command.js +194 -0
  38. package/dist/cli/skill-command.js +85 -3
  39. package/dist/cli/stats.js +10 -4
  40. package/dist/cli/task-command.js +1 -0
  41. package/dist/cli/task-context.js +16 -0
  42. package/dist/cli/thread-command.js +620 -0
  43. package/dist/cli/trigger-command.js +76 -50
  44. package/dist/config/builtin-roles.js +9 -7
  45. package/dist/config/config-field-policy.js +2 -3
  46. package/dist/config/config-manager.js +7 -2
  47. package/dist/config/resolved-config-op.js +3 -0
  48. package/dist/config-store.js +15 -0
  49. package/dist/core/agent-ipc-registration.js +65 -22
  50. package/dist/core/auth/agent-delegation.js +42 -2
  51. package/dist/core/auth/debug-context.js +139 -0
  52. package/dist/core/auth/delegation-authorization.js +1 -0
  53. package/dist/core/auth/operation-authorizer.js +24 -1
  54. package/dist/core/auth/operation-catalog.js +44 -3
  55. package/dist/core/background-session/session-cleanup.js +212 -0
  56. package/dist/core/background-session/types.js +10 -0
  57. package/dist/core/bootstrap-messages.js +1 -1
  58. package/dist/core/bootstrap-service.js +6 -3
  59. package/dist/core/command/command-handler.js +224 -45
  60. package/dist/core/command/diag-service.js +5 -1
  61. package/dist/core/command/memory-slash.js +6 -2
  62. package/dist/core/command/menu-catalog.js +48 -1
  63. package/dist/core/command/menu-handler.js +92 -16
  64. package/dist/core/command/menu-protocol.js +24 -1
  65. package/dist/core/command/skill-slash.js +322 -0
  66. package/dist/core/command/slash-gate.js +6 -4
  67. package/dist/core/command/slash-handler.js +193 -13
  68. package/dist/core/command/topic-slash.js +528 -0
  69. package/dist/core/evolagent-registry.js +20 -5
  70. package/dist/core/handoff/ipc-registration.js +38 -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 +210 -9
  74. package/dist/core/memory/consolidation-context.js +49 -16
  75. package/dist/core/memory/consolidation-ops-records.js +35 -0
  76. package/dist/core/memory/consolidation-queue-format.js +21 -0
  77. package/dist/core/memory/consolidation.js +174 -180
  78. package/dist/core/message/aun-ipc-sender.js +207 -8
  79. package/dist/core/message/im-renderer.js +149 -6
  80. package/dist/core/message/message-bridge.js +13 -3
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +131 -3
  83. package/dist/core/message/peer-mode.js +6 -1
  84. package/dist/core/message/response-engine.js +379 -69
  85. package/dist/core/message/subtask-tool-projection.js +140 -0
  86. package/dist/core/model/catalog-service.js +17 -12
  87. package/dist/core/permission/ec-command-parser.js +126 -7
  88. package/dist/core/permission/sandbox-runtime.js +14 -1
  89. package/dist/core/session/session-idle-monitor.js +249 -22
  90. package/dist/core/session/session-manager.js +54 -9
  91. package/dist/core/skill/command-parser.js +13 -0
  92. package/dist/core/skill/index-generator.js +4 -1
  93. package/dist/core/skill/startup.js +41 -0
  94. package/dist/core/skill/switch-resolver.js +55 -29
  95. package/dist/core/task/task-recovery-service.js +2 -0
  96. package/dist/core/task/task-resume-operation.js +11 -0
  97. package/dist/core/thread/command-parser.js +125 -0
  98. package/dist/core/thread/index.js +1 -0
  99. package/dist/core/thread/ipc-operation.js +95 -0
  100. package/dist/core/thread/thread-context-storage.js +131 -0
  101. package/dist/core/thread/thread-operation.js +258 -0
  102. package/dist/core/thread/thread-route.js +33 -0
  103. package/dist/core/thread/thread-service.js +777 -0
  104. package/dist/core/thread/types.js +1 -0
  105. package/dist/eck/message-renderer.js +35 -0
  106. package/dist/index.js +10 -7
  107. package/dist/ipc.js +234 -17
  108. package/dist/paths.js +23 -0
  109. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  110. package/dist/trigger/authorization.js +59 -29
  111. package/dist/trigger/feedback.js +7 -1
  112. package/dist/trigger/ipc-service.js +49 -35
  113. package/dist/trigger/parser.js +63 -1
  114. package/dist/trigger/script-sandbox.js +58 -77
  115. package/dist/trigger/validation.js +14 -0
  116. package/dist/utils/error-utils.js +22 -1
  117. package/kits/docs/AUTHORING.md +1 -1
  118. package/kits/docs/channels/aun.md +8 -0
  119. package/kits/docs/context-assembly.md +1 -0
  120. package/kits/docs/evolcore/INDEX.md +3 -2
  121. package/kits/docs/evolcore/agent-create.md +39 -28
  122. package/kits/docs/evolcore/agent.md +19 -4
  123. package/kits/docs/evolcore/ec-capabilities.md +4 -0
  124. package/kits/docs/evolcore/group-collaboration.md +3 -5
  125. package/kits/docs/evolcore/group.md +3 -4
  126. package/kits/docs/evolcore/memory-consolidation.md +1 -1
  127. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  128. package/kits/docs/evolcore/menu-protocol.md +7 -0
  129. package/kits/docs/evolcore/msg.md +5 -8
  130. package/kits/docs/evolcore/task.md +2 -0
  131. package/kits/docs/evolcore/thread-chain.md +267 -0
  132. package/kits/docs/evolcore/trigger.md +2 -2
  133. package/kits/docs/prompt-loading-architecture.md +1 -0
  134. package/kits/eck_manifest.json +61 -26
  135. package/kits/eck_message_manifest.json +169 -10
  136. package/kits/rules/01-overview.md +0 -9
  137. package/kits/rules/05-venue.md +1 -1
  138. package/kits/rules/06-channel.md +7 -1
  139. package/kits/rules/07-memory-and-thread.md +202 -0
  140. package/kits/schemas/relation-config.schema.9.json +18 -3
  141. package/kits/schemas/role-config.schema.1.json +6 -6
  142. package/kits/schemas/single-session.schema.1.json +6 -6
  143. package/kits/schemas/single-session.schema.2.json +6 -6
  144. package/kits/schemas/single-session.schema.3.json +6 -6
  145. package/kits/skills/.switches.json +12 -1
  146. package/kits/skills/ec-group/SKILL.md +234 -0
  147. package/kits/skills/ec-skill/SKILL.md +10 -1
  148. package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
  149. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  150. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  151. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  152. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  153. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  154. package/kits/skills/thread-ops/SKILL.md +492 -0
  155. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  156. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  157. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  158. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  159. package/kits/templates/bootstrap-welcome.md +7 -7
  160. package/kits/templates/memory-consolidation/forget.md +5 -0
  161. package/kits/templates/memory-consolidation/generalize.md +5 -0
  162. package/kits/templates/memory-consolidation/profile.md +5 -0
  163. package/kits/templates/memory-consolidation/promote.md +5 -0
  164. package/kits/templates/message-fragments/item.md +2 -1
  165. package/kits/templates/message-fragments/thread-context.md +6 -0
  166. package/kits/templates/message-fragments/trigger-session.md +2 -0
  167. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  168. package/kits/templates/roles/member.json +2 -0
  169. package/kits/templates/roles/visitor.json +2 -0
  170. package/kits/templates/system-fragments/bootstrap.md +66 -72
  171. package/kits/templates/system-fragments/channel.md +3 -6
  172. package/kits/templates/system-fragments/commands.md +2 -1
  173. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  174. package/kits/templates/system-fragments/reply.md +3 -2
  175. package/kits/templates/system-fragments/session.md +5 -14
  176. package/package.json +3 -1
  177. package/kits/docs/evolcore/group-fs.md +0 -17
  178. package/kits/docs/venues/aun-group.md +0 -45
  179. package/kits/docs/venues/group.md +0 -43
@@ -1,3 +1,4 @@
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';
@@ -17,6 +18,7 @@ export class DaemonChannel {
17
18
  thread: true,
18
19
  };
19
20
  pending = new Map();
21
+ bgPending = new Map();
20
22
  watchdogMs;
21
23
  totalExecutionMs;
22
24
  constructor(sessionManager, messageQueue, opts = {}) {
@@ -26,6 +28,8 @@ export class DaemonChannel {
26
28
  this.totalExecutionMs = normalizeTotalExecutionMs(opts.totalExecutionMs);
27
29
  }
28
30
  async converse(prompt, ctx) {
31
+ // 启动前跑一次 expire 扫描(成本低,只读少量小文件)
32
+ void runExpireCleanup(this.sessionManager).catch(() => { });
29
33
  const runId = ctx.runId;
30
34
  const pendingId = ctx.attemptId ?? runId;
31
35
  const messageId = pendingId;
@@ -41,6 +45,11 @@ export class DaemonChannel {
41
45
  ? undefined
42
46
  : setTimeout(() => this.totalExecutionTimeout(pendingId, remainingExecutionMs), remainingExecutionMs);
43
47
  totalExecutionTimer?.unref?.();
48
+ // trigger 清理策略:once → expire(3天),其他 → expire(30天)
49
+ const isOnce = ctx.trigger.source?.type === 'once';
50
+ const triggerCleanupPolicy = isOnce
51
+ ? { mode: 'expire', ttlMs: 3 * 24 * 3600 * 1000 }
52
+ : { mode: 'expire', ttlMs: 30 * 24 * 3600 * 1000 };
44
53
  this.pending.set(pendingId, {
45
54
  resolve,
46
55
  startedAt,
@@ -52,6 +61,11 @@ export class DaemonChannel {
52
61
  abortSignal: ctx.signal,
53
62
  runId,
54
63
  acc: { text: '', files: [], anomalies: [], toolCallCount: 0, seenToolCallIds: new Set() },
64
+ cleanupPolicy: triggerCleanupPolicy,
65
+ channelId: ctx.session.channelId,
66
+ agentAid: ctx.trigger.agentAid,
67
+ kind: `trigger:${ctx.trigger.id}`,
68
+ sessionId: ctx.session.id,
55
69
  });
56
70
  });
57
71
  const message = {
@@ -85,8 +99,13 @@ export class DaemonChannel {
85
99
  originChannelKey: ctx.trigger.origin?.channelKey,
86
100
  originChannelType: ctx.trigger.origin?.channelType
87
101
  ?? (ctx.trigger.origin?.channelKey ? channelTypeFromKey(ctx.trigger.origin.channelKey) : undefined),
102
+ originChatType: ctx.trigger.origin?.delivery?.chatType,
103
+ originConversationId: ctx.trigger.origin?.delivery?.chatType === 'group'
104
+ ? ctx.trigger.origin.channelId
105
+ : ctx.trigger.origin?.peerId,
88
106
  originControl: ctx.trigger.origin?.channelKey === 'daemon',
89
107
  authorizedBy: ctx.trigger.authorizedBy,
108
+ labels: ctx.trigger.labels,
90
109
  },
91
110
  };
92
111
  const onAbort = () => {
@@ -128,6 +147,15 @@ export class DaemonChannel {
128
147
  return promise;
129
148
  }
130
149
  async send(envelope, payload) {
150
+ // bg-session 路径:优先查 bgPending
151
+ const bgRunId = this.bgRunIdFromEnvelope(envelope);
152
+ if (bgRunId) {
153
+ const bgSlot = this.bgPending.get(bgRunId);
154
+ if (bgSlot && !bgSlot.terminated) {
155
+ this.handleBgSessionPayload(bgRunId, bgSlot, payload);
156
+ return;
157
+ }
158
+ }
131
159
  const runId = this.runIdFromEnvelope(envelope);
132
160
  if (!runId)
133
161
  return;
@@ -270,6 +298,11 @@ export class DaemonChannel {
270
298
  const pendingId = metadata?.triggerAttemptId ?? metadata?.triggerRunId;
271
299
  return typeof pendingId === 'string' ? pendingId : undefined;
272
300
  }
301
+ bgRunIdFromEnvelope(envelope) {
302
+ const metadata = envelope.replyContext?.metadata;
303
+ const runId = metadata?.bgSessionRunId;
304
+ return typeof runId === 'string' ? runId : undefined;
305
+ }
273
306
  recordToolCall(slot, callId) {
274
307
  if (typeof callId !== 'string' || !callId) {
275
308
  slot.acc.toolCallCount += 1;
@@ -290,13 +323,14 @@ export class DaemonChannel {
290
323
  ?? (origin.channelKey ? channelTypeFromKey(origin.channelKey) : undefined);
291
324
  if (!channelType)
292
325
  return undefined;
326
+ const chatType = origin.delivery?.chatType ?? 'private';
293
327
  return resolvePeerRoleDetail({
294
328
  selfAid: trigger.agentAid,
295
329
  channelKey: origin.channelKey,
296
330
  channelType,
297
- chatType: 'private',
331
+ chatType,
298
332
  actorId: origin.peerId,
299
- conversationId: origin.peerId,
333
+ conversationId: chatType === 'group' ? origin.channelId : origin.peerId,
300
334
  }).effectiveRole ?? undefined;
301
335
  }
302
336
  finish(runId, status, metadata) {
@@ -346,6 +380,29 @@ export class DaemonChannel {
346
380
  toolCallCount: Math.max(slot.acc.toolCallCount, completedToolCallCount),
347
381
  },
348
382
  });
383
+ // trigger 清理策略
384
+ const policy = slot.cleanupPolicy;
385
+ if (policy.mode === 'immediate') {
386
+ void cleanupSessionRecord({ sessionId: slot.sessionId, channelId: slot.channelId, agentAid: slot.agentAid }, this.sessionManager);
387
+ }
388
+ else if (policy.mode === 'on_next_launch') {
389
+ recordNextLaunchCleanup(slot.kind, {
390
+ channelId: slot.channelId,
391
+ sessionId: slot.sessionId,
392
+ agentAid: slot.agentAid,
393
+ recordedAt: Date.now(),
394
+ });
395
+ }
396
+ else if (policy.mode === 'expire') {
397
+ appendExpireEntry({
398
+ expireAt: Date.now() + policy.ttlMs,
399
+ sessionId: slot.sessionId,
400
+ channelId: slot.channelId,
401
+ kind: slot.kind,
402
+ agentAid: slot.agentAid,
403
+ });
404
+ }
405
+ // 'keep' 模式:不做任何处理
349
406
  }
350
407
  fail(runId, reason, text) {
351
408
  const slot = this.pending.get(runId);
@@ -412,6 +469,204 @@ export class DaemonChannel {
412
469
  }
413
470
  queue.cancelActive?.(messageId);
414
471
  }
472
+ // ── bg-session 生命周期 ──
473
+ handleBgSessionPayload(runId, slot, payload) {
474
+ if (slot.terminated)
475
+ return;
476
+ switch (payload.kind) {
477
+ case 'result.text':
478
+ if (payload.isFinal)
479
+ slot.text = payload.text;
480
+ else
481
+ slot.text += payload.text;
482
+ break;
483
+ case 'status.completed':
484
+ this.finishBgSession(runId, slot, 'completed');
485
+ break;
486
+ case 'result.error':
487
+ this.finishBgSession(runId, slot, 'error', payload.text ?? payload.reason ?? 'unknown error');
488
+ break;
489
+ case 'system.error':
490
+ this.finishBgSession(runId, slot, 'error', payload.text ?? payload.subtype ?? 'unknown error');
491
+ break;
492
+ case 'status.error':
493
+ this.finishBgSession(runId, slot, 'error', payload.metadata?.message ?? 'status error');
494
+ break;
495
+ case 'status.interrupted':
496
+ {
497
+ const reason = typeof payload.metadata?.reason === 'string'
498
+ ? payload.metadata.reason
499
+ : 'interrupted';
500
+ const outcome = slot.abortSignal?.aborted ? 'cancelled' : 'interrupted';
501
+ this.finishBgSession(runId, slot, outcome, reason);
502
+ }
503
+ break;
504
+ case 'status.timeout':
505
+ this.finishBgSession(runId, slot, 'error', 'bg-session watchdog timeout');
506
+ break;
507
+ default:
508
+ break;
509
+ }
510
+ }
511
+ finishBgSession(runId, slot, outcome, error) {
512
+ if (slot.terminated)
513
+ return;
514
+ slot.terminated = true;
515
+ if (slot.watchdogTimer)
516
+ clearTimeout(slot.watchdogTimer);
517
+ if (slot.abortListener)
518
+ slot.abortSignal?.removeEventListener('abort', slot.abortListener);
519
+ this.bgPending.delete(runId);
520
+ // 会话结束后按清理策略处理
521
+ const policy = slot.cleanupPolicy;
522
+ if (policy.mode === 'immediate') {
523
+ void cleanupSessionRecord({ sessionId: slot.sessionId, channelId: slot.channelId, agentAid: slot.agentAid }, this.sessionManager);
524
+ }
525
+ else if (policy.mode === 'on_next_launch') {
526
+ recordNextLaunchCleanup(slot.kind, {
527
+ channelId: slot.channelId,
528
+ sessionId: slot.sessionId,
529
+ agentAid: slot.agentAid,
530
+ recordedAt: Date.now(),
531
+ });
532
+ }
533
+ else if (policy.mode === 'expire') {
534
+ appendExpireEntry({
535
+ expireAt: Date.now() + policy.ttlMs,
536
+ sessionId: slot.sessionId,
537
+ channelId: slot.channelId,
538
+ kind: slot.kind,
539
+ agentAid: slot.agentAid,
540
+ });
541
+ }
542
+ // 'keep' 模式:不做任何处理
543
+ slot.resolve({
544
+ sessionId: slot.sessionId,
545
+ outcome,
546
+ ...(slot.text ? { text: slot.text } : {}),
547
+ ...(error ? { error } : {}),
548
+ });
549
+ }
550
+ /**
551
+ * 拉起一个独立后台会话。会话创建、首条消息入队后即返回 handle;
552
+ * 会话结束由 handle.result 通知。实现 BackgroundSessionLauncher 接口。
553
+ * 详见 docs/background-session/background-session-launcher.md
554
+ */
555
+ async launchBackgroundSession(spec) {
556
+ const runId = `bg-${spec.kind}-${spec.instanceId}`;
557
+ const channelId = `bg:${spec.kind}:${spec.instanceId}`;
558
+ const cleanupPolicy = spec.cleanupPolicy ?? { mode: 'on_next_launch' };
559
+ // 启动前清理:on_next_launch 清理同 kind 历史,expire 扫描到期条目
560
+ if (cleanupPolicy.mode === 'on_next_launch') {
561
+ await cleanupPreviousByKind(spec.kind, this.sessionManager);
562
+ }
563
+ else if (cleanupPolicy.mode !== 'keep') {
564
+ // immediate 和 expire 模式都在启动时跑一次 expire 扫描
565
+ await runExpireCleanup(this.sessionManager);
566
+ }
567
+ const session = await this.sessionManager.getOrCreateSession(this.channelName, channelId, spec.projectPath, channelId, // threadId,每次独立,保证 by_run 语义
568
+ {
569
+ channelKey: this.channelKey,
570
+ peerId: `bg:${spec.kind}`,
571
+ peerName: spec.kind,
572
+ peerType: 'system',
573
+ bgKind: spec.kind,
574
+ bgInstanceId: spec.instanceId,
575
+ ...(spec.metadata ?? {}),
576
+ }, spec.kind, undefined, 'private', spec.baseagent, spec.agentAid, this.channelName, 'system');
577
+ const promise = new Promise((resolve) => {
578
+ const slot = {
579
+ resolve,
580
+ sessionId: session.id,
581
+ channelId,
582
+ kind: spec.kind,
583
+ startedAt: Date.now(),
584
+ abortSignal: spec.signal,
585
+ text: '',
586
+ terminated: false,
587
+ cleanupPolicy,
588
+ agentAid: spec.agentAid,
589
+ watchdogTimer: setTimeout(() => {
590
+ this.finishBgSession(runId, slot, 'error', 'bg-session watchdog timeout');
591
+ }, this.watchdogMs),
592
+ };
593
+ slot.watchdogTimer?.unref?.();
594
+ if (spec.signal) {
595
+ const onAbort = () => {
596
+ void this.cancelDelivery(runId).catch(() => { });
597
+ };
598
+ slot.abortListener = onAbort;
599
+ spec.signal.addEventListener('abort', onAbort, { once: true });
600
+ if (spec.signal.aborted)
601
+ onAbort();
602
+ }
603
+ this.bgPending.set(runId, slot);
604
+ });
605
+ const message = {
606
+ channel: this.channelName,
607
+ channelType: this.channelName,
608
+ channelId: session.channelId,
609
+ selfAID: spec.agentAid,
610
+ baseagent: spec.baseagent,
611
+ threadId: session.threadId || '',
612
+ chatType: 'private',
613
+ peerId: `bg:${spec.kind}`,
614
+ peerName: spec.kind,
615
+ peerType: 'system',
616
+ content: spec.prompt,
617
+ messageId: runId,
618
+ timestamp: Date.now(),
619
+ source: 'bg-session',
620
+ bgSessionMeta: {
621
+ kind: spec.kind,
622
+ runId,
623
+ instanceId: spec.instanceId,
624
+ boundSessionId: session.id,
625
+ },
626
+ replyContext: {
627
+ metadata: { bgSessionRunId: runId },
628
+ },
629
+ };
630
+ void this.messageQueue.enqueue?.(session.id, message, spec.projectPath, {
631
+ interruptible: false,
632
+ agentName: spec.agentAid,
633
+ sessionKeyField: session.sessionKey,
634
+ selfAID: spec.agentAid,
635
+ });
636
+ let settled = false;
637
+ void promise.then(() => { settled = true; });
638
+ const sessionId = session.id;
639
+ return {
640
+ sessionId,
641
+ result: promise,
642
+ waitForAgentSessionId: (opts) => this.waitForAgentSessionId(sessionId, () => settled, opts),
643
+ };
644
+ }
645
+ /**
646
+ * 轮询 session 记录,等 runner 写入 agentSessionId。
647
+ * 会话刚创建时只有 evolcore sessionId,agentSessionId 要等 runner 初始化并开始第一轮才写入。
648
+ * 会话结束后再读一次即停止,避免对已结束的会话空等到超时。
649
+ */
650
+ async waitForAgentSessionId(sessionId, isSettled, opts = {}) {
651
+ const deadline = Date.now() + (opts.timeoutMs ?? 10_000);
652
+ const interval = opts.intervalMs ?? 250;
653
+ while (true) {
654
+ const settledBeforeRead = isSettled();
655
+ try {
656
+ const id = (await this.sessionManager.getSessionById(sessionId))?.agentSessionId;
657
+ if (id)
658
+ return id;
659
+ }
660
+ catch { /* 会话记录写到一半,下一轮再读 */ }
661
+ if (settledBeforeRead || Date.now() >= deadline)
662
+ return undefined;
663
+ await new Promise(r => setTimeout(r, interval));
664
+ }
665
+ }
666
+ /** 实现 BackgroundSessionLauncher 接口 */
667
+ launch(spec) {
668
+ return this.launchBackgroundSession(spec);
669
+ }
415
670
  }
416
671
  function isObject(value) {
417
672
  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)
@@ -148,8 +172,7 @@ Options:
148
172
  process.exitCode = 1;
149
173
  return;
150
174
  }
151
- const managedArgs = commands.has(sub) ? args : ['show', ...args];
152
- const result = await runManagedAgentOperation(managedArgs);
175
+ const result = await runManagedAgentOperation(args);
153
176
  if (!result.ok) {
154
177
  if (formatJson)
155
178
  console.log(JSON.stringify(result));
@@ -221,25 +244,28 @@ Options:
221
244
  // 表头跟随系统语言
222
245
  const isChinese = isHostChinese();
223
246
  const headers = isChinese
224
- ? { name: '名称', personal: 'Personal名', status: '状态', channels: '渠道', project: '项目', baseagent: '基座', lastActive: '最后活跃' }
225
- : { name: 'NAME', personal: 'PERSONAL', status: 'STATUS', channels: 'CHANNELS', project: 'PROJECT', baseagent: 'BASEAGENT', lastActive: 'LAST ACTIVE' };
226
- // 计算各列实际需要的宽度
227
- let maxNameLen = headers.name.length;
228
- let maxPersonalLen = headers.personal.length;
229
- let maxStatusLen = headers.status.length;
230
- let maxChannelsLen = headers.channels.length;
231
- let maxProjectLen = headers.project.length;
232
- 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);
233
257
  for (const info of result.agents) {
234
- maxNameLen = Math.max(maxNameLen, info.name.length);
235
- maxPersonalLen = Math.max(maxPersonalLen, (info.personalName || '—').length);
236
- const displayedStatus = `${info.status || 'unknown'}${info.stale ? ' (stale)' : ''}`;
237
- 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));
238
263
  const channelsStr = info.channels?.length > 0 ? info.channels.join(', ') : '—';
239
- maxChannelsLen = Math.max(maxChannelsLen, channelsStr.length);
264
+ maxChannelsLen = Math.max(maxChannelsLen, displayWidth(channelsStr));
240
265
  const projectStr = info.projectPath ? path.basename(info.projectPath) : '—';
241
- maxProjectLen = Math.max(maxProjectLen, projectStr.length);
242
- 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 || '—'));
243
269
  }
244
270
  // 加 2 作为列间距
245
271
  const colName = maxNameLen + 2;
@@ -248,18 +274,25 @@ Options:
248
274
  const colChannels = maxChannelsLen + 1;
249
275
  const colProject = maxProjectLen + 2;
250
276
  const colBaseagent = maxBaseagentLen + 2;
251
- console.log(headers.name.padEnd(colName) + headers.personal.padEnd(colPersonal) + headers.status.padEnd(colStatus) + headers.channels.padEnd(colChannels) +
252
- 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);
253
282
  for (const info of result.agents) {
254
283
  const name = info.name;
255
284
  const personal = info.personalName || '—';
256
- 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}`;
257
287
  const channels = info.channels?.length > 0 ? info.channels.join(', ') : '—';
258
288
  const project = info.projectPath ? path.basename(info.projectPath) : '—';
259
289
  const baseagent = info.baseagent || '—';
290
+ const model = info.model || '—';
260
291
  const lastActive = info.lastActivity ? formatTimeAgo(Date.now() - info.lastActivity) : '—';
261
- console.log(name.padEnd(colName) + personal.padEnd(colPersonal) + status.padEnd(colStatus) + channels.padEnd(colChannels) +
262
- 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);
263
296
  }
264
297
  return;
265
298
  }
@@ -671,7 +704,7 @@ Options:
671
704
  process.exitCode = 1;
672
705
  return;
673
706
  }
674
- const result = await agentPersona(aid, { text, file });
707
+ const result = await agentPersona(aid, { text, file, argv: args });
675
708
  if (!result.ok) {
676
709
  if (formatJson)
677
710
  console.log(JSON.stringify(result));
@@ -698,7 +731,7 @@ Options:
698
731
  console.log(`用法: ${MAIN_CLI_BIN} agent welcome show [--format json]
699
732
  ${MAIN_CLI_BIN} agent welcome --text <text> [--format json]
700
733
 
701
- 读取或保存当前 Agent 在 Bootstrap 中经 Owner 确认的 welcome 文案。文案固定保存到 personal/welcome.md。`);
734
+ 读取或保存当前 Agent 在 Bootstrap 中自主准备的 welcome 文案。文案固定保存到 personal/welcome.md。`);
702
735
  return;
703
736
  }
704
737
  const result = { ok: false, code: 'INVALID_ARGS', error: error.message };
@@ -720,7 +753,7 @@ Options:
720
753
  process.exitCode = 1;
721
754
  return;
722
755
  }
723
- const result = await agentWelcome(aid, { text });
756
+ const result = await agentWelcome(aid, { text, argv: args });
724
757
  if (!result.ok) {
725
758
  if (formatJson)
726
759
  console.log(JSON.stringify(result));
@@ -756,7 +789,7 @@ Options:
756
789
  console.error('用法: ec agent get <aid> <key>');
757
790
  process.exit(1);
758
791
  }
759
- const result = await agentGet(aid, key);
792
+ const result = await agentGet(aid, key, { argv: args });
760
793
  if (!result.ok) {
761
794
  if (formatJson) {
762
795
  console.log(JSON.stringify(result));
@@ -797,7 +830,7 @@ Options:
797
830
  console.error('用法: ec agent set <aid> <key> <value>');
798
831
  process.exit(1);
799
832
  }
800
- const result = await agentSet(aid, key, val);
833
+ const result = await agentSet(aid, key, val, { argv: args });
801
834
  if (!result.ok) {
802
835
  if (formatJson) {
803
836
  console.log(JSON.stringify(result));
@@ -828,7 +861,7 @@ Options:
828
861
  console.error('用法: ec agent ready <aid>');
829
862
  process.exit(1);
830
863
  }
831
- const result = await agentReady(aid);
864
+ const result = await agentReady(aid, args);
832
865
  if (!result.ok) {
833
866
  if (formatJson) {
834
867
  console.log(JSON.stringify(result));
@@ -20,6 +20,7 @@ import { atomicWriteJson } from '../utils/atomic-write.js';
20
20
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../core/permission/protected-paths.js';
21
21
  import { MAIN_CLI_BIN } from '../product.js';
22
22
  import { uploadAvatarFile, uploadAvatarUrl } from '../utils/avatar-upload.js';
23
+ import { readTaskRuntimeContextFromEnv } from './task-context.js';
23
24
  /** Parse the current Agent's Bootstrap welcome subcommand. */
24
25
  export function parseAgentWelcomeArgs(args) {
25
26
  const tail = args.at(-2) === '--format' && args.at(-1) === 'json' ? args.slice(0, -2) : args;
@@ -190,9 +191,9 @@ function projectSlug(project) {
190
191
  .replace(/-+$/g, '');
191
192
  return slug || 'agent';
192
193
  }
193
- export function resolveAgentCreatePlan(options) {
194
+ export function resolveAgentCreatePlan(options, projectCwd = process.cwd()) {
194
195
  const aid = options.aid.trim();
195
- const project = path.resolve(options.project || process.cwd());
196
+ const project = path.resolve(projectCwd, options.project || '.');
196
197
  const metadata = readProjectMetadata(project);
197
198
  const defaults = loadDefaults();
198
199
  const daemon = loadDaemonConfig();
@@ -940,6 +941,20 @@ export class AgentInitializationService {
940
941
  await this.running.get(aid);
941
942
  }
942
943
  async settle(aid) { await this.running.get(aid); }
944
+ async wait(aid, seconds) {
945
+ const work = this.running.get(aid);
946
+ if (work && seconds > 0) {
947
+ let timer;
948
+ try {
949
+ await Promise.race([work, new Promise(resolve => { timer = setTimeout(resolve, seconds * 1000); })]);
950
+ }
951
+ finally {
952
+ if (timer)
953
+ clearTimeout(timer);
954
+ }
955
+ }
956
+ return this.status(aid);
957
+ }
943
958
  launch(r) {
944
959
  const aid = r.plan.aid;
945
960
  const work = Promise.resolve().then(() => this.run(r)).catch(error => {
@@ -1167,33 +1182,37 @@ export function parseInitializationArgs(args) {
1167
1182
  throw new Error('--wait must be an integer from 0 to 300');
1168
1183
  return { aid: args[1], resume, dryRun: seen.has('--dry-run'), wait, values };
1169
1184
  }
1170
- export function resolveInitializationPlan(args) {
1185
+ export function resolveInitializationPlan(args, context) {
1171
1186
  const parsed = parseInitializationArgs(args);
1172
1187
  if (parsed.resume)
1173
1188
  throw new Error('A resume request cannot supply a new plan');
1174
1189
  const v = parsed.values;
1175
1190
  if (v['--avatar-file'] !== undefined && v['--avatar-url'] !== undefined)
1176
1191
  throw new Error('--avatar-file and --avatar-url are mutually exclusive');
1177
- if (v['--avatar-file']?.startsWith('attachment:') && !process.env.EVOLCORE_SESSION_ID) {
1192
+ if (v['--avatar-file']?.startsWith('attachment:') && !context && !process.env.EVOLCORE_SESSION_ID) {
1178
1193
  throw new Error('attachment:<id> avatar sources require a managed Agent task');
1179
1194
  }
1180
- if (v['--persona-file'] !== undefined)
1181
- throw new Error('CLI must submit persona file contents as text');
1195
+ const persona = v['--persona-file'] !== undefined
1196
+ ? readInitializationPersona(v['--persona-file'], !!context, context)
1197
+ : v['--persona-text'];
1182
1198
  const p = resolveAgentCreatePlan({
1183
1199
  aid: parsed.aid, project: v['--project'], baseagent: v['--baseagent'], ...parseAgentOwnerArgs(args),
1184
1200
  avatarFile: v['--avatar-file'], avatarUrl: v['--avatar-url'],
1185
1201
  name: v['--name'], description: v['--description'], tags: parseAgentTags(v['--tags']), type: v['--type'],
1186
- });
1202
+ }, context?.projectCwd);
1187
1203
  return {
1188
1204
  aid: p.aid, project: p.project, baseagent: p.baseagent ?? '', owners: p.owners ?? (p.owner ? [p.owner] : []),
1189
1205
  name: p.name, description: p.description, tags: p.tags, type: p.type,
1190
- ...(v['--persona-text'] !== undefined ? { persona: v['--persona-text'] } : {}),
1206
+ ...(persona !== undefined ? { persona } : {}),
1191
1207
  ...(v['--avatar-file'] !== undefined ? { avatarFile: v['--avatar-file'] } : {}),
1192
1208
  ...(v['--avatar-url'] !== undefined ? { avatarUrl: v['--avatar-url'] } : {}),
1193
1209
  };
1194
1210
  }
1195
- export function readInitializationPersona(file, managed) {
1196
- const resolved = path.resolve(file);
1211
+ export function readInitializationPersona(file, managed, context = {
1212
+ projectCwd: managed ? readTaskRuntimeContextFromEnv()?.projectPath ?? process.cwd() : process.cwd(),
1213
+ tempDir: process.env.TMPDIR,
1214
+ }) {
1215
+ const resolved = path.resolve(context.projectCwd, file);
1197
1216
  if (!/\.(md|txt)$/i.test(resolved))
1198
1217
  throw new Error('persona file must be .md or .txt');
1199
1218
  const stat = fs.lstatSync(resolved);
@@ -1201,8 +1220,8 @@ export function readInitializationPersona(file, managed) {
1201
1220
  throw new Error('persona must be a regular text file of at most 64 KiB');
1202
1221
  if (managed) {
1203
1222
  const real = fs.realpathSync(resolved);
1204
- const roots = [process.cwd(), process.env.TMPDIR].filter((r) => !!r && path.isAbsolute(r));
1205
- const allowed = roots.some(root => isSameOrDescendant(real, fs.realpathSync(root)));
1223
+ const roots = [context.projectCwd, context.tempDir].filter((root) => !!root && path.isAbsolute(root));
1224
+ const allowed = roots.some(root => fs.existsSync(root) && isSameOrDescendant(real, fs.realpathSync(root)));
1206
1225
  if (!allowed || isHClassPath(real) || isLClassPath(real))
1207
1226
  throw new Error('persona file must be inside the project or current TMPDIR');
1208
1227
  }
@@ -1216,27 +1235,24 @@ export async function runInitializationCommand(args) {
1216
1235
  const parsed = parseInitializationArgs(args);
1217
1236
  const managed = !!process.env.EVOLCORE_SESSION_ID;
1218
1237
  const argv = [...args];
1219
- if (parsed.values['--persona-file'] !== undefined) {
1238
+ if (parsed.values['--persona-file'] !== undefined && !managed) {
1239
+ const persona = readInitializationPersona(parsed.values['--persona-file'], managed);
1220
1240
  const at = argv.indexOf('--persona-file');
1221
- argv.splice(at, 2, '--persona-text', readInitializationPersona(parsed.values['--persona-file'], managed));
1241
+ argv.splice(at, 2, '--persona-text', persona);
1222
1242
  }
1223
- if (!parsed.resume) {
1243
+ if (!managed && !parsed.resume) {
1224
1244
  const at = argv.indexOf('--project');
1225
1245
  if (at < 0)
1226
1246
  argv.push('--project', process.cwd());
1227
1247
  else
1228
1248
  argv[at + 1] = path.resolve(argv[at + 1]);
1229
1249
  }
1250
+ if (managed) {
1251
+ const { runManagedAgentOperation } = await import('./agent.js');
1252
+ const result = await runManagedAgentOperation(argv, (parsed.wait + 10) * 1000);
1253
+ return initializationCommandResult(result);
1254
+ }
1230
1255
  const request = async (poll) => {
1231
- if (managed) {
1232
- const { runManagedAgentOperation } = await import('./agent.js');
1233
- const r = await runManagedAgentOperation(poll ? ['show', parsed.aid, '--format', 'json'] : argv);
1234
- if (!poll || !r.ok || !r.initialization)
1235
- return r;
1236
- const init = r.initialization;
1237
- return { ...init, ok: !['failed', 'cancelled'].includes(init.status), aid: parsed.aid,
1238
- accepted: true, lifecycle: r.lifecycle, usable: init.complete && r.connection?.status === 'connected' };
1239
- }
1240
1256
  return await ipcQuery(resolvePaths().socket, {
1241
1257
  type: 'evolagent.initialize', argv, poll,
1242
1258
  }, 10_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Initialization requires a running daemon' };
@@ -1249,6 +1265,9 @@ export async function runInitializationCommand(args) {
1249
1265
  await new Promise(resolve => setTimeout(resolve, 500));
1250
1266
  result = await request(true);
1251
1267
  }
1268
+ return initializationCommandResult(result);
1269
+ }
1270
+ function initializationCommandResult(result) {
1252
1271
  if (result.ok && !result.complete && result.accepted)
1253
1272
  return { ...result, ok: false, code: 'INITIALIZE_PENDING', error: 'Initialization is still running; inspect agent show' };
1254
1273
  if (result.complete && !result.usable)