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
@@ -33,13 +33,6 @@ export class HandoffDeliveryFailedError extends Error {
33
33
  this.name = 'HandoffDeliveryFailedError';
34
34
  }
35
35
  }
36
- export class HandoffReturnPolicyRequiredError extends Error {
37
- code = 'HANDOFF_RETURN_POLICY_REQUIRED';
38
- constructor() {
39
- super('跨会话 ec msg/group send 必须显式指定 --return none|required');
40
- this.name = 'HandoffReturnPolicyRequiredError';
41
- }
42
- }
43
36
  export class TriggerHandoffRequiredForbiddenError extends Error {
44
37
  code = 'TRIGGER_HANDOFF_REQUIRED_FORBIDDEN';
45
38
  constructor() {
@@ -99,9 +92,10 @@ export class HandoffRuntime {
99
92
  throw new Error('handoff context requires a different target session');
100
93
  return { crossSession: false, targetSession: target };
101
94
  }
102
- if (!input.explicitReturnPolicy)
103
- throw new HandoffReturnPolicyRequiredError();
104
- if (input.explicitReturnPolicy === 'required' && input.causation?.trigger?.path.length) {
95
+ // --return 参数已废弃:跨会话 send 不再要求显式指定返回策略,默认 'none'。
96
+ // 内部调用方(agent-create 等)仍可显式传 'required' 走回流路径。
97
+ const effectiveReturnPolicy = input.explicitReturnPolicy ?? 'none';
98
+ if (effectiveReturnPolicy === 'required' && input.causation?.trigger?.path.length) {
105
99
  throw new TriggerHandoffRequiredForbiddenError();
106
100
  }
107
101
  const handoffCausation = deriveCausation(normalizeCausation(input.causation) ?? createRootCausation());
@@ -127,7 +121,7 @@ export class HandoffRuntime {
127
121
  delivery: targetChatType === 'group'
128
122
  ? { chatType: 'group', groupId: input.to }
129
123
  : { chatType: 'private' },
130
- returnPolicy: input.explicitReturnPolicy ?? 'required',
124
+ returnPolicy: effectiveReturnPolicy,
131
125
  originAuthorization: input.originAuthorization,
132
126
  causation: handoffCausation,
133
127
  now: this.now?.(),
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @deprecated Handoff 机制已被 Thread Context 取代,见
3
+ * `docs/thread-chain/handoff-deprecation.md`。保留仅为兼容在途 handoff,
4
+ * 将在清理阶段删除。
5
+ */
1
6
  import crypto from 'crypto';
2
7
  import fs from 'fs';
3
8
  import path from 'path';
@@ -1,10 +1,16 @@
1
1
  import { TriggerDefinitionManager } from '../trigger/manager.js';
2
+ import { ensureSessionRuntimeDir } from '../cli/task-context.js';
2
3
  import { logger } from '../utils/logger.js';
3
4
  import crypto from 'crypto';
4
5
  import fs from 'fs';
5
6
  import path from 'path';
6
7
  import { executeTaskResume } from './task/task-resume-operation.js';
7
- import { isHClassPath, isSameOrDescendant } from './permission/protected-paths.js';
8
+ import { isHClassPath, isLClassPath, isSameOrDescendant } from './permission/protected-paths.js';
9
+ import { resolveStorageCommandArgv } from '../aun/storage/index.js';
10
+ import { agentDir } from '../paths.js';
11
+ import { parsePeerKey } from './relation/peer-identity.js';
12
+ import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
13
+ import { buildDebugTaskContext } from './auth/debug-context.js';
8
14
  import { computeBuildId } from '../utils/evolcore-version.js';
9
15
  import { loadAgent } from '../config-store.js';
10
16
  import { executeLocalInitializationCommand, hasSuppliedInitialization } from '../cli/agent-create.js';
@@ -16,6 +22,7 @@ import { registerHandoffIpc } from './handoff/ipc-registration.js';
16
22
  import { registerAunIpcSender } from './message/aun-ipc-sender.js';
17
23
  import { registerConsolidationCheckExecutor, registerConsolidationLaunchExecutor, } from './memory/consolidation.js';
18
24
  import { createTriggerBaseagentResolver, registerTriggerIpcExecutor } from '../trigger/ipc-service.js';
25
+ import { createThreadOperationExecutor } from './thread/ipc-operation.js';
19
26
  import { isLinkedInstall } from '../utils/npm-ops.js';
20
27
  export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationRegistry, sessionManager, statsCollector, aidStatsCollector, responseEngine, messageQueue, agentMap, commandHandler, channelInstances, eventBus, handoffRuntime, reloadCoordinator, agentApplicationService, agentInitializationService, enqueueConsolidation, completeAgentBootstrap, contactBinds, bindService, diagService, getProcessOwners, getControlAidState, daemonAid, triggerControlToken, triggerSchedulers, daemonTriggerOwner, primaryAgent, triggerAuthorization, }) {
21
28
  const authorizeManagedAgentOperation = createDelegationAuthorizer({
@@ -51,6 +58,137 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
51
58
  agentMap,
52
59
  isLinkedInstall: isLinkedInstall(),
53
60
  });
61
+ if (process.env.EVOLCORE_DEBUG_MODE === '1') {
62
+ ipcServer.setDebugIssueTokenExecutor(async ({ sessionId }) => {
63
+ try {
64
+ const activeContext = responseEngine.getTaskRuntimeContext(sessionId);
65
+ let runtimeContext = activeContext;
66
+ if (!runtimeContext) {
67
+ const session = await sessionManager.getSessionById(sessionId);
68
+ if (!session)
69
+ return { ok: false, error: `session not found: ${sessionId}` };
70
+ runtimeContext = buildDebugTaskContext({
71
+ session,
72
+ getTrigger: (agentAid, triggerId) => new TriggerDefinitionManager(agentAid).get(triggerId),
73
+ sessionRuntimeDir: ensureSessionRuntimeDir(session.id),
74
+ });
75
+ }
76
+ if (!runtimeContext.selfAid)
77
+ return { ok: false, error: 'debug session has no selfAid' };
78
+ const channelType = runtimeContext.channel || 'aun';
79
+ const channel = runtimeContext.channelKey || channelType;
80
+ const peerKeyId = (() => {
81
+ try {
82
+ return parsePeerKey(runtimeContext.peerKey || '').channelId;
83
+ }
84
+ catch {
85
+ return runtimeContext.peerId || runtimeContext.channelId || '';
86
+ }
87
+ })();
88
+ const chatType = runtimeContext.chatType === 'group' ? 'group' : 'private';
89
+ const conversationId = chatType === 'group'
90
+ ? (runtimeContext.channelId || peerKeyId)
91
+ : peerKeyId;
92
+ const peerRole = resolvePeerRoleDetail({
93
+ selfAid: runtimeContext.selfAid,
94
+ channelKey: channel,
95
+ channelType,
96
+ chatType,
97
+ actorId: peerKeyId,
98
+ conversationId,
99
+ });
100
+ const token = agentDelegationRegistry.issueDebug({
101
+ sessionId,
102
+ selfAid: runtimeContext.selfAid,
103
+ channel,
104
+ channelType,
105
+ chatType,
106
+ peerKey: runtimeContext.peerKey || '',
107
+ actorId: peerKeyId || runtimeContext.selfAid,
108
+ issuedRole: peerRole.effectiveRole || 'none',
109
+ });
110
+ return { ok: true, token, runtimeContext };
111
+ }
112
+ catch (error) {
113
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
114
+ }
115
+ });
116
+ }
117
+ ipcServer.setThreadOperationExecutor(createThreadOperationExecutor({
118
+ sessionManager,
119
+ agentDelegationRegistry,
120
+ getProcessOwners,
121
+ sendSystemMsg: async (params) => {
122
+ if (ipcServer.hasAunMsgSender()) {
123
+ const sent = await ipcServer.sendAunMsg({
124
+ aid: params.from,
125
+ to: params.to,
126
+ scope: params.peerKind === 'group' ? 'group' : 'msg',
127
+ payload: { type: 'topic-created', topicName: params.topicName, threadId: params.threadId },
128
+ thread: params.threadId,
129
+ });
130
+ if (!sent?.ok)
131
+ throw new Error(sent?.error || 'Topic notification failed');
132
+ }
133
+ },
134
+ onDeleted: (aid, threadId) => {
135
+ const scheduler = triggerSchedulers.get(aid);
136
+ if (scheduler) {
137
+ const related = scheduler.list({ all: true }).filter((trigger) => trigger.origin?.threadId === threadId
138
+ || trigger.labels?.originThreadId === threadId
139
+ || trigger.labels?.targetThreadId === threadId);
140
+ for (const trigger of related) {
141
+ try {
142
+ scheduler.delete(trigger.id);
143
+ }
144
+ catch { }
145
+ }
146
+ }
147
+ },
148
+ }));
149
+ ipcServer.setSkillRefreshExecutor(async (aid, sessionId, delegationToken, delegationCommandHash) => {
150
+ const fail = (code, error) => ({ ok: false, code, error });
151
+ if (!agentRegistry.get(aid))
152
+ return fail('AGENT_NOT_FOUND', `Agent "${aid}" not found`);
153
+ if (sessionId) {
154
+ const authorization = await authorizeManagedAgentOperation({
155
+ sessionId,
156
+ delegationToken,
157
+ delegationCommandHash,
158
+ targetAid: aid,
159
+ }, 'skill.refresh');
160
+ if (!authorization.ok)
161
+ return fail(authorization.code, authorization.error);
162
+ }
163
+ try {
164
+ const { refreshSkillIndex } = await import('./skill/startup.js');
165
+ refreshSkillIndex(agentDir(aid), aid);
166
+ const skillsDir = path.join(agentDir(aid), 'skills');
167
+ return {
168
+ ok: true,
169
+ result: {
170
+ aid,
171
+ builtinIndexPath: path.join(skillsDir, '.index-builtin.md'),
172
+ localIndexPath: path.join(skillsDir, '.index-local.md'),
173
+ },
174
+ };
175
+ }
176
+ catch (error) {
177
+ return fail('INTERNAL', error?.message || String(error));
178
+ }
179
+ });
180
+ commandHandler.setSkillIndexRefresher(async (aid) => {
181
+ if (!agentRegistry.get(aid))
182
+ return { ok: false, error: `Agent "${aid}" not found` };
183
+ try {
184
+ const { refreshSkillIndex } = await import('./skill/startup.js');
185
+ refreshSkillIndex(agentDir(aid), aid);
186
+ return { ok: true };
187
+ }
188
+ catch (error) {
189
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
190
+ }
191
+ });
54
192
  registerHandoffIpc({
55
193
  ipcServer,
56
194
  agentDelegationRegistry,
@@ -60,7 +198,7 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
60
198
  handoffRuntime,
61
199
  getProcessOwners,
62
200
  });
63
- registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime });
201
+ registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime, triggerSchedulers });
64
202
  ipcServer.setAgentReloadExecutor((aid, options) => reloadCoordinator.reload(aid, options));
65
203
  registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation });
66
204
  ipcServer.setInitializationExecutor(async (command) => executeLocalInitializationCommand(agentInitializationService, command));
@@ -75,6 +213,7 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
75
213
  handoffRuntime,
76
214
  agentApplicationService,
77
215
  agentInitializationService,
216
+ executeConfigOperation: (argv, sessionId, token, commandHash) => commandHandler.handleConfigOperation(argv, sessionId, token, commandHash),
78
217
  });
79
218
  registerOperationIpcProviders({
80
219
  ipcServer,
@@ -210,7 +349,7 @@ export function registerRuntimeIpcProviders({ ipcServer, channelInstances, messa
210
349
  const session = await sessionManager.getSessionById(sessionId);
211
350
  if (!session)
212
351
  return null;
213
- const { buildDebugTaskContext } = await import('./memory/consolidation.js');
352
+ const { buildDebugTaskContext } = await import('./auth/debug-context.js');
214
353
  const { ensureSessionRuntimeDir } = await import('../cli/task-context.js');
215
354
  return buildDebugTaskContext({
216
355
  session,
@@ -220,8 +359,12 @@ export function registerRuntimeIpcProviders({ ipcServer, channelInstances, messa
220
359
  });
221
360
  logger.info('[Memory] 开发仓安装:已启用 ec memory --debug <sessionId>');
222
361
  }
223
- ipcServer.setTaskRuntimeContextAuthorizer(({ sessionId, delegationToken, delegationCommandHash }) => {
224
- const authorization = agentDelegationRegistry.validateForContext(delegationToken, sessionId, delegationCommandHash);
362
+ ipcServer.setTaskRuntimeContextAuthorizer(({ sessionId, delegationToken, delegationCommandHash, consumeApproval }) => {
363
+ if (consumeApproval && !agentDelegationRegistry.validate)
364
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Command authorization is unavailable' };
365
+ const authorization = consumeApproval
366
+ ? agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash)
367
+ : agentDelegationRegistry.validateForContext(delegationToken, sessionId, delegationCommandHash);
225
368
  return authorization.ok
226
369
  ? { ok: true }
227
370
  : { ok: false, code: authorization.code, error: authorization.reason };
@@ -383,8 +526,17 @@ export function registerOperationIpcProviders({ ipcServer, sessionManager, messa
383
526
  recover: recoverTask,
384
527
  }));
385
528
  ipcServer.setQueueOperationExecutor(async (params) => {
529
+ try {
530
+ if (!params.argv)
531
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed queue operation requires argv' };
532
+ const { parseQueueOperationArgv } = await import('../cli/queue-command.js');
533
+ params = { ...params, ...parseQueueOperationArgv(params.argv) };
534
+ }
535
+ catch (error) {
536
+ return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
537
+ }
386
538
  const operation = `ec.queue.${params.action ?? 'read'}`;
387
- const authorization = await authorizeManagedAgentOperation(params, operation);
539
+ const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.agent }, operation);
388
540
  if (!authorization.ok)
389
541
  return authorization;
390
542
  const handle = agentRegistry.get(authorization.aid);
@@ -419,10 +571,57 @@ export function registerOperationIpcProviders({ ipcServer, sessionManager, messa
419
571
  return authorization.ok ? { ok: true } : authorization;
420
572
  });
421
573
  ipcServer.setStorageAuthorizationExecutor(async (params) => {
422
- const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.aid }, params.operation);
423
- return authorization.ok ? { ok: true } : authorization;
574
+ try {
575
+ if (!params.argv)
576
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed storage requires argv' };
577
+ const command = resolveStorageCommandArgv(params.argv);
578
+ if (command.sub === 'upload')
579
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Upload requires the storage upload endpoint' };
580
+ const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: command.aid }, command.operation);
581
+ if (!authorization.ok)
582
+ return authorization;
583
+ const { storageDownload, storageLs, storageRm, storageQuota } = await import('../aun/storage/index.js');
584
+ if (command.sub === 'ls')
585
+ return storageLs(authorization.aid, command.prefix);
586
+ if (command.sub === 'quota')
587
+ return storageQuota(authorization.aid);
588
+ if (command.sub === 'rm') {
589
+ const result = await storageRm(authorization.aid, command.remotePath);
590
+ return { ...result, objectKey: command.remotePath };
591
+ }
592
+ const session = await sessionManager.getSessionById(params.sessionId);
593
+ if (!session)
594
+ return { ok: false, code: 'SESSION_NOT_FOUND', error: 'current session not found' };
595
+ const defaultName = path.basename(command.url.replace(/^https?:\/\//, '').split('/').slice(1).join('/'));
596
+ const destination = path.resolve(session.projectPath, command.localPath ?? defaultName);
597
+ const roots = [session.projectPath, authorization.sessionRuntimeDir].filter((root) => !!root).map(root => fs.realpathSync(root));
598
+ let parent = path.dirname(destination);
599
+ while (!fs.existsSync(parent) && path.dirname(parent) !== parent)
600
+ parent = path.dirname(parent);
601
+ const realParent = fs.realpathSync(parent);
602
+ if (!roots.some(root => isSameOrDescendant(destination, root) && isSameOrDescendant(realParent, root))
603
+ || isHClassPath(destination) || isLClassPath(destination)
604
+ || (fs.existsSync(destination) && (!fs.lstatSync(destination).isFile() || fs.lstatSync(destination).isSymbolicLink()))) {
605
+ return { ok: false, code: 'INVALID_LOCAL_PATH', error: 'Download destination must be an unprotected project/TMPDIR file' };
606
+ }
607
+ return storageDownload(authorization.aid, command.url, destination);
608
+ }
609
+ catch (error) {
610
+ return { ok: false, code: 'STORAGE_OPERATION_FAILED', error: error instanceof Error ? error.message : String(error) };
611
+ }
424
612
  });
425
613
  ipcServer.setStorageUploadExecutor(async (params) => {
614
+ try {
615
+ if (!params.argv)
616
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed storage upload requires argv' };
617
+ const command = resolveStorageCommandArgv(params.argv);
618
+ if (command.sub !== 'upload')
619
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Upload endpoint does not match argv' };
620
+ params = { ...params, aid: command.aid, localFile: command.localFile, remotePath: command.remotePath, isPublic: command.isPublic };
621
+ }
622
+ catch (error) {
623
+ return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
624
+ }
426
625
  const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.aid }, 'storage.upload');
427
626
  if (!authorization.ok)
428
627
  return authorization;
@@ -438,7 +637,9 @@ export function registerOperationIpcProviders({ ipcServer, sessionManager, messa
438
637
  }
439
638
  const localFile = fs.realpathSync(requestedFile);
440
639
  const stat = fs.lstatSync(localFile);
441
- if (!isSameOrDescendant(localFile, projectPath) || isHClassPath(localFile)) {
640
+ const inProject = isSameOrDescendant(localFile, projectPath);
641
+ const inRuntime = authorization.sessionRuntimeDir && isSameOrDescendant(localFile, fs.realpathSync(authorization.sessionRuntimeDir));
642
+ if ((!inProject && !inRuntime) || isHClassPath(localFile) || isLClassPath(localFile)) {
442
643
  return { ok: false, code: 'INVALID_LOCAL_PATH', error: 'Upload source must be an unprotected file inside the current session project' };
443
644
  }
444
645
  if (!stat.isFile() || stat.isSymbolicLink()) {
@@ -34,7 +34,14 @@ export const MODE_SCOPE = {
34
34
  // 超期判据集中在 staleness.ts;这里做转发,保证 trigger 与 context 用同一套规则
35
35
  export { DEFAULT_FORGET_AFTER_DAYS, LONG_TERM_REVIEW_DAYS } from './staleness.js';
36
36
  // ── Checkpoint 读写 ──────────────────────────────────────────────────────────
37
- const CHECKPOINT_FILE = 'consolidation-checkpoint.json';
37
+ const LEGACY_CHECKPOINT_FILE = 'consolidation-checkpoint.json';
38
+ /** C1: checkpoint file name per mode; forget uses scopeOverride suffix. */
39
+ function checkpointFileName(mode, scopeOverride) {
40
+ if (mode === 'forget' && scopeOverride) {
41
+ return `consolidation-checkpoint-forget-${scopeOverride}.json`;
42
+ }
43
+ return `consolidation-checkpoint-${mode}.json`;
44
+ }
38
45
  /**
39
46
  * Move the pre-0.0.29 consolidation artifact root to its new name. Existing
40
47
  * batches are user data, so conflicts are preserved under a legacy suffix
@@ -70,22 +77,37 @@ export function migrateLegacyConsolidationBatches() {
70
77
  fs.rmSync(legacyRoot, { recursive: true, force: true });
71
78
  }
72
79
  /** 读上次整理时间戳(毫秒)。文件不存在或解析失败返回 0(取全量历史)。 */
73
- export function readCheckpoint(chatDir) {
80
+ export function readCheckpoint(chatDir, mode, scopeOverride) {
81
+ const filePath = path.join(chatDir, checkpointFileName(mode, scopeOverride));
74
82
  try {
75
- const raw = fs.readFileSync(path.join(chatDir, CHECKPOINT_FILE), 'utf-8');
83
+ const raw = fs.readFileSync(filePath, 'utf-8');
76
84
  const parsed = JSON.parse(raw);
77
85
  const v = Number(parsed.lastConsolidatedAt);
78
- return Number.isFinite(v) ? v : 0;
86
+ if (Number.isFinite(v))
87
+ return v;
79
88
  }
80
- catch {
81
- return 0;
89
+ catch { /* 新文件不存在,尝试迁移旧文件 */ }
90
+ // 向后兼容: notes 模式读旧的 consolidation-checkpoint.json,成功后自动迁移
91
+ if (mode === 'notes') {
92
+ try {
93
+ const legacyPath = path.join(chatDir, LEGACY_CHECKPOINT_FILE);
94
+ const raw = fs.readFileSync(legacyPath, 'utf-8');
95
+ const parsed = JSON.parse(raw);
96
+ const v = Number(parsed.lastConsolidatedAt);
97
+ if (Number.isFinite(v)) {
98
+ writeCheckpoint(chatDir, v, mode, scopeOverride);
99
+ return v;
100
+ }
101
+ }
102
+ catch { /* ignore */ }
82
103
  }
104
+ return 0;
83
105
  }
84
106
  /** 写整理完成时间戳。由 index.ts 在整理会话完成后调用。 */
85
- export function writeCheckpoint(chatDir, lastConsolidatedAt) {
107
+ export function writeCheckpoint(chatDir, lastConsolidatedAt, mode, scopeOverride) {
86
108
  try {
87
109
  fs.mkdirSync(chatDir, { recursive: true });
88
- fs.writeFileSync(path.join(chatDir, CHECKPOINT_FILE), JSON.stringify({ lastConsolidatedAt }, null, 2) + '\n', 'utf-8');
110
+ fs.writeFileSync(path.join(chatDir, checkpointFileName(mode, scopeOverride)), JSON.stringify({ lastConsolidatedAt }, null, 2) + '\n', 'utf-8');
89
111
  }
90
112
  catch { /* 失败静默 */ }
91
113
  }
@@ -276,7 +298,7 @@ function shortTime(time) {
276
298
  *
277
299
  * 返回生成的文件路径列表(空列表表示无增量消息或读取失败)。
278
300
  */
279
- function buildThreadSnapshots(historyJsonlPath, sinceMs, threadsDir) {
301
+ function buildThreadSnapshots(historyJsonlPath, sinceMs, threadsDir, throughMs) {
280
302
  const rawLines = fs.readFileSync(historyJsonlPath, 'utf-8').split('\n');
281
303
  // 第一遍:扫全量行(不限 sinceMs)收集会话元信息
282
304
  let chatType = 'private';
@@ -312,7 +334,7 @@ function buildThreadSnapshots(historyJsonlPath, sinceMs, threadsDir) {
312
334
  try {
313
335
  const entry = JSON.parse(line);
314
336
  const ts = typeof entry.ts === 'number' ? entry.ts : 0;
315
- if (ts < sinceMs)
337
+ if (ts <= sinceMs || ts > throughMs)
316
338
  continue;
317
339
  const content = typeof entry.content === 'string' ? entry.content.trim() : '';
318
340
  if (!content)
@@ -410,26 +432,36 @@ export function serializeConsolidationContext(input) {
410
432
  const selfAid = input.taskCtx.selfAid ?? 'unknown';
411
433
  // 目录名:YYYYMMDDTHHmmss-<selfAid>-<mode>
412
434
  const prettyTs = now.toISOString().replace(/[-:]/g, '').slice(0, 15);
413
- const batchDir = path.join(batchRoot, `${prettyTs}-${selfAid}-${input.mode}`);
435
+ fs.mkdirSync(batchRoot, { recursive: true });
436
+ const batchDir = fs.mkdtempSync(path.join(batchRoot, `${prettyTs}-${selfAid}-${input.mode}-`));
414
437
  fs.mkdirSync(batchDir, { recursive: true });
415
438
  // ── 话题快照(notes / compress / session-new 模式,有 peerId 时生成)───────
416
439
  let historyJsonlPath;
417
440
  let threadFiles;
418
441
  let chatDir;
419
- if ((input.mode === 'notes' || input.triggeredBy === 'compress' || input.triggeredBy === 'session-new')
442
+ // C2: generate thread snapshots for all modes that operate on relation-level memories
443
+ const needsThreadSnapshots = (input.mode === 'notes'
444
+ || input.mode === 'generalize'
445
+ || input.mode === 'promote'
446
+ || input.mode === 'profile'
447
+ || (input.mode === 'forget' && input.scopeOverride === 'relation')
448
+ || input.triggeredBy === 'compress'
449
+ || input.triggeredBy === 'session-new');
450
+ if (needsThreadSnapshots
420
451
  && input.taskCtx.channel
421
452
  && input.taskCtx.peerId
422
453
  && input.taskCtx.selfAid) {
423
454
  try {
424
455
  const sessionsDir = resolvePaths().sessionsDir;
425
- chatDir = chatDirPath(sessionsDir, input.taskCtx.channel, input.taskCtx.peerId, input.taskCtx.selfAid);
456
+ const peerId = input.taskCtx.chatType === 'group' ? input.taskCtx.channelId || input.taskCtx.peerId : input.taskCtx.peerId;
457
+ chatDir = chatDirPath(sessionsDir, input.taskCtx.channel, peerId, input.taskCtx.selfAid, input.taskCtx.channelKey);
426
458
  const rawJsonlPath = path.join(chatDir, 'messages.jsonl');
427
459
  if (fs.existsSync(rawJsonlPath)) {
428
460
  historyJsonlPath = rawJsonlPath;
429
- const sinceMs = readCheckpoint(chatDir);
461
+ const sinceMs = readCheckpoint(chatDir, input.mode, input.scopeOverride);
430
462
  const threadsDir = path.join(batchDir, 'threads');
431
463
  try {
432
- const files = buildThreadSnapshots(rawJsonlPath, sinceMs, threadsDir);
464
+ const files = buildThreadSnapshots(rawJsonlPath, sinceMs, threadsDir, now.getTime());
433
465
  if (files.length > 0)
434
466
  threadFiles = files;
435
467
  }
@@ -494,7 +526,8 @@ export function serializeConsolidationContext(input) {
494
526
  fs.writeFileSync(contextFilePath, text, 'utf-8');
495
527
  }
496
528
  catch { /* 批次任务书写入失败不影响整理 */ }
497
- return { filePath, batchDir, contextFilePath, scopes, historyJsonlPath, threadFiles, chatDir };
529
+ return { filePath, batchDir, contextFilePath, scopes, historyJsonlPath, threadFiles, chatDir,
530
+ ...(threadFiles?.length ? { snapshotThroughTs: now.getTime() } : {}) };
498
531
  }
499
532
  /** 会话结束后回填本次产出,追加到任务书末尾。 */
500
533
  export function recordBatchOutcome(contextFilePath, outcome) {
@@ -122,3 +122,38 @@ export function readRollbackRecords(batchDir) {
122
122
  return [];
123
123
  }
124
124
  }
125
+ /**
126
+ * 统计 batchDir/threads/ 下所有快照文件里的消息条数。
127
+ * 每个 .md 文件里以 "L" 开头(后跟数字)的行就是一条消息。
128
+ * 文件不存在或为空时返回 0。
129
+ */
130
+ export function countInputMessages(batchDir) {
131
+ const threadsDir = path.join(batchDir, 'threads');
132
+ if (!fs.existsSync(threadsDir))
133
+ return 0;
134
+ let total = 0;
135
+ try {
136
+ for (const f of fs.readdirSync(threadsDir)) {
137
+ if (!f.endsWith('.md'))
138
+ continue;
139
+ const content = fs.readFileSync(path.join(threadsDir, f), 'utf-8');
140
+ for (const line of content.split('\n')) {
141
+ if (/^L\d+\s/.test(line))
142
+ total++;
143
+ }
144
+ }
145
+ }
146
+ catch { /* ignore */ }
147
+ return total;
148
+ }
149
+ /**
150
+ * 读取 batchDir/context.md 的字节数。文件不存在返回 0。
151
+ */
152
+ export function readContextBytes(batchDir) {
153
+ try {
154
+ return fs.statSync(path.join(batchDir, 'context.md')).size;
155
+ }
156
+ catch {
157
+ return 0;
158
+ }
159
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 整理请求入队时的提示文案。CLI(ec memory consolidate)和 /memory consolidate 共用。
3
+ * 不依赖其他模块,CLI 侧引入不会拖进 daemon 代码。
4
+ */
5
+ /** 入队原因:本 agent 已有整理会话在跑,或全局并发已满 */
6
+ export function queueReason(q) {
7
+ if (q.agentBusy)
8
+ return '当前 agent 已有整理会话在运行';
9
+ if (q.activeCount >= q.maxConcurrent)
10
+ return `全局并发已满(${q.activeCount}/${q.maxConcurrent})`;
11
+ return '前面还有待处理的整理请求';
12
+ }
13
+ /** 多行明细,每行一个字段 */
14
+ export function formatQueueDetails(q) {
15
+ return [
16
+ `原因: ${queueReason(q)}`,
17
+ `本 agent 排队: 第 ${q.position} 位 / 共 ${q.agentQueueLength} 个`,
18
+ `全局运行中: ${q.activeCount}/${q.maxConcurrent}`,
19
+ `全局排队: ${q.totalQueued} 个`,
20
+ ];
21
+ }