evolcore 0.0.32 → 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 (150) hide show
  1. package/CHANGELOG.md +24 -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 +23 -19
  7. package/dist/agents/ecagent-runner.js +5 -13
  8. package/dist/aun/group/operations.js +0 -1
  9. package/dist/aun/msg/history.js +1 -0
  10. package/dist/aun/msg/p2p.js +0 -1
  11. package/dist/aun/outbox.js +50 -1
  12. package/dist/channels/aun.js +28 -2
  13. package/dist/channels/daemon.js +257 -2
  14. package/dist/cli/agent-command.js +58 -24
  15. package/dist/cli/agent.js +1 -1
  16. package/dist/cli/aun-commands.js +365 -29
  17. package/dist/cli/contact.js +8 -2
  18. package/dist/cli/daemon-commands.js +81 -12
  19. package/dist/cli/debug-mode.js +154 -0
  20. package/dist/cli/handoff-command.js +3 -0
  21. package/dist/cli/index.js +81 -64
  22. package/dist/cli/init.js +2 -2
  23. package/dist/cli/managed-command-guard.js +5 -0
  24. package/dist/cli/memory-command.js +8 -1
  25. package/dist/cli/queue-command.js +9 -4
  26. package/dist/cli/session-command.js +194 -0
  27. package/dist/cli/skill-command.js +78 -2
  28. package/dist/cli/task-context.js +1 -0
  29. package/dist/cli/thread-command.js +620 -0
  30. package/dist/cli/trigger-command.js +4 -2
  31. package/dist/config/builtin-roles.js +1 -0
  32. package/dist/config/config-field-policy.js +2 -3
  33. package/dist/config/config-manager.js +7 -2
  34. package/dist/config/resolved-config-op.js +3 -0
  35. package/dist/config-store.js +15 -0
  36. package/dist/core/auth/agent-delegation.js +25 -1
  37. package/dist/core/auth/debug-context.js +139 -0
  38. package/dist/core/auth/operation-authorizer.js +24 -1
  39. package/dist/core/auth/operation-catalog.js +33 -0
  40. package/dist/core/background-session/session-cleanup.js +212 -0
  41. package/dist/core/background-session/types.js +10 -0
  42. package/dist/core/bootstrap-messages.js +1 -1
  43. package/dist/core/bootstrap-service.js +6 -3
  44. package/dist/core/command/command-handler.js +60 -24
  45. package/dist/core/command/memory-slash.js +6 -2
  46. package/dist/core/command/menu-catalog.js +48 -1
  47. package/dist/core/command/menu-handler.js +92 -16
  48. package/dist/core/command/menu-protocol.js +24 -1
  49. package/dist/core/command/skill-slash.js +322 -0
  50. package/dist/core/command/slash-gate.js +6 -4
  51. package/dist/core/command/slash-handler.js +193 -13
  52. package/dist/core/command/topic-slash.js +528 -0
  53. package/dist/core/evolagent-registry.js +20 -5
  54. package/dist/core/handoff/runtime.js +5 -11
  55. package/dist/core/handoff/store.js +5 -0
  56. package/dist/core/ipc-registration.js +139 -2
  57. package/dist/core/memory/consolidation-context.js +49 -16
  58. package/dist/core/memory/consolidation-ops-records.js +35 -0
  59. package/dist/core/memory/consolidation-queue-format.js +21 -0
  60. package/dist/core/memory/consolidation.js +174 -180
  61. package/dist/core/message/aun-ipc-sender.js +161 -6
  62. package/dist/core/message/im-renderer.js +149 -6
  63. package/dist/core/message/message-bridge.js +13 -3
  64. package/dist/core/message/message-log.js +1 -0
  65. package/dist/core/message/peer-mode.js +6 -1
  66. package/dist/core/message/response-engine.js +349 -67
  67. package/dist/core/message/subtask-tool-projection.js +140 -0
  68. package/dist/core/model/catalog-service.js +17 -12
  69. package/dist/core/permission/ec-command-parser.js +126 -7
  70. package/dist/core/session/session-idle-monitor.js +249 -22
  71. package/dist/core/session/session-manager.js +54 -9
  72. package/dist/core/skill/command-parser.js +13 -0
  73. package/dist/core/skill/index-generator.js +4 -1
  74. package/dist/core/skill/startup.js +41 -0
  75. package/dist/core/skill/switch-resolver.js +55 -29
  76. package/dist/core/thread/command-parser.js +125 -0
  77. package/dist/core/thread/index.js +1 -0
  78. package/dist/core/thread/ipc-operation.js +95 -0
  79. package/dist/core/thread/thread-context-storage.js +131 -0
  80. package/dist/core/thread/thread-operation.js +258 -0
  81. package/dist/core/thread/thread-route.js +33 -0
  82. package/dist/core/thread/thread-service.js +777 -0
  83. package/dist/core/thread/types.js +1 -0
  84. package/dist/eck/message-renderer.js +35 -0
  85. package/dist/index.js +8 -5
  86. package/dist/ipc.js +89 -10
  87. package/dist/paths.js +23 -0
  88. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  89. package/dist/trigger/authorization.js +57 -28
  90. package/dist/trigger/feedback.js +7 -1
  91. package/dist/trigger/parser.js +63 -1
  92. package/dist/trigger/validation.js +14 -0
  93. package/dist/utils/error-utils.js +22 -1
  94. package/kits/docs/context-assembly.md +1 -0
  95. package/kits/docs/evolcore/INDEX.md +1 -0
  96. package/kits/docs/evolcore/agent-create.md +39 -28
  97. package/kits/docs/evolcore/agent.md +19 -4
  98. package/kits/docs/evolcore/ec-capabilities.md +4 -0
  99. package/kits/docs/evolcore/group-collaboration.md +3 -5
  100. package/kits/docs/evolcore/group.md +3 -4
  101. package/kits/docs/evolcore/memory-consolidation.md +1 -1
  102. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  103. package/kits/docs/evolcore/menu-protocol.md +7 -0
  104. package/kits/docs/evolcore/msg.md +5 -8
  105. package/kits/docs/evolcore/thread-chain.md +267 -0
  106. package/kits/docs/prompt-loading-architecture.md +1 -0
  107. package/kits/eck_manifest.json +61 -26
  108. package/kits/eck_message_manifest.json +169 -10
  109. package/kits/rules/05-venue.md +1 -1
  110. package/kits/rules/07-memory-and-thread.md +202 -0
  111. package/kits/schemas/relation-config.schema.9.json +18 -3
  112. package/kits/schemas/role-config.schema.1.json +6 -6
  113. package/kits/schemas/single-session.schema.1.json +6 -6
  114. package/kits/schemas/single-session.schema.2.json +6 -6
  115. package/kits/schemas/single-session.schema.3.json +6 -6
  116. package/kits/skills/.switches.json +12 -1
  117. package/kits/skills/ec-group/SKILL.md +234 -0
  118. package/kits/skills/ec-skill/SKILL.md +10 -1
  119. package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
  120. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  121. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  122. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  123. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  124. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  125. package/kits/skills/thread-ops/SKILL.md +492 -0
  126. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  127. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  128. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  129. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  130. package/kits/templates/bootstrap-welcome.md +7 -7
  131. package/kits/templates/memory-consolidation/forget.md +5 -0
  132. package/kits/templates/memory-consolidation/generalize.md +5 -0
  133. package/kits/templates/memory-consolidation/profile.md +5 -0
  134. package/kits/templates/memory-consolidation/promote.md +5 -0
  135. package/kits/templates/message-fragments/item.md +2 -1
  136. package/kits/templates/message-fragments/thread-context.md +6 -0
  137. package/kits/templates/message-fragments/trigger-session.md +2 -0
  138. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  139. package/kits/templates/roles/member.json +2 -0
  140. package/kits/templates/roles/visitor.json +2 -0
  141. package/kits/templates/system-fragments/bootstrap.md +66 -72
  142. package/kits/templates/system-fragments/channel.md +3 -6
  143. package/kits/templates/system-fragments/commands.md +1 -0
  144. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  145. package/kits/templates/system-fragments/reply.md +3 -2
  146. package/kits/templates/system-fragments/session.md +5 -14
  147. package/package.json +3 -1
  148. package/kits/docs/evolcore/group-fs.md +0 -17
  149. package/kits/docs/venues/aun-group.md +0 -45
  150. package/kits/docs/venues/group.md +0 -43
@@ -0,0 +1,620 @@
1
+ import { resolvePaths } from '../paths.js';
2
+ import { ipcQuery } from '../ipc.js';
3
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
+ import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
5
+ const TOPIC_STATUSES = ['pending', 'in_progress', 'completed', 'archived'];
6
+ const SORT_ORDERS = ['recent', 'created'];
7
+ const HELP_TEXT = `
8
+ ec thread <command> [options]
9
+
10
+ 命令:
11
+ peers 列出有话题记录的所有对端(先用这个选对端)
12
+ list <peer> 列出与某对端的所有话题
13
+ show <ref> 查看单个话题的元数据
14
+ history <ref> 读取话题历史消息
15
+ create <peer> --name 新建话题
16
+ rename <ref> <名称> 修改话题名称(仅本端)
17
+ label <ref> <标签> 设置话题标签
18
+ status <ref> <状态> 更新话题业务状态
19
+ delete <ref> 删除话题(session 文件移入 _trash)
20
+ summary show <ref> 查看当前 summary
21
+ summary set <ref> <内容> --name <新标题|-> 写入 summary(--name 必填;- 表示不改名)
22
+ summary log <ref> 查看 summary 历史版本
23
+ summary clear <ref> 清空当前 summary(历史版本保留)
24
+ rebuild-index 重建话题索引(丢失/损坏时使用)
25
+ rebuild-index --check 检查索引健康状态(只报告不写盘,区分严重/轻微)
26
+ check-index 同 rebuild-index --check
27
+
28
+ 典型工作流:
29
+ 1. ec agent list # 查看本机所有 agent
30
+ 2. ec thread peers --self <selfAid> # 查看该 agent 的所有对端
31
+ 3. ec thread list <peer> --self <selfAid> # 查看与该对端的话题列表
32
+
33
+ <ref> 格式(所有命令统一支持):
34
+ current 当前任务所在话题(托管会话自动注入)
35
+ <peer> 该对端的主会话
36
+ <peer>/<threadId> 指定具体话题
37
+
38
+ list 选项:
39
+ --status <s> 过滤状态 (pending|in_progress|completed|archived)
40
+ --label <l> 过滤标签
41
+ --unlabeled 仅未标记话题
42
+ --sort recent|created 排序方式(默认 recent)
43
+ --page <n> 页码
44
+ --limit <n> 每页条数
45
+
46
+ history 选项:
47
+ --limit <n> 消息条数上限(默认 50)
48
+ --before <timestamp> 截止时间戳(ms)
49
+ --after <timestamp> 起始时间戳(ms)
50
+
51
+ summary 与增量总结:
52
+ summary show <ref> 会打印 coveredThroughTs(本条摘要覆盖到的时间戳)。
53
+ 下次总结时用它作起点读增量:
54
+ ec thread history <ref> --after <coveredThroughTs>
55
+ 读完增量后 summary set 会自动把游标推进到话题最新一条。
56
+
57
+ summary set 选项:
58
+ --name <新标题|-> 必填。顺带把话题改名为该值;传 - 表示保持原标题不改。
59
+ 总结时正好把主题想清楚,一次命令写完摘要 + 标题。
60
+ --if-rev <n> CAS 版本号(可选,多 agent 协作时防并发覆盖)
61
+
62
+ create 选项:
63
+ --name <名称> 话题名称(必填)
64
+ --label <标签> 分类标签
65
+ --status <状态> 初始状态(默认 pending)
66
+ --group 群话题(默认私聊)
67
+
68
+ label 选项:
69
+ --clear 清除标签
70
+
71
+ 状态值: pending | in_progress | completed | archived
72
+
73
+ --format json 以 JSON 格式输出
74
+ --self <selfAid> 宿主终端必填,指定本端 AID
75
+ --help 显示帮助
76
+ `.trim();
77
+ function argValue(args, flag) {
78
+ const i = args.indexOf(flag);
79
+ return i >= 0 ? args[i + 1] : undefined;
80
+ }
81
+ function hasFlag(args, flag) {
82
+ return args.includes(flag);
83
+ }
84
+ async function sendThreadOp(req, commandArgv) {
85
+ const cred = readDelegationCredential();
86
+ const { socket } = resolvePaths();
87
+ const invokingSessionId = process.env.EVOLCORE_SESSION_ID;
88
+ const result = await ipcQuery(socket, {
89
+ ...req,
90
+ type: 'thread.op',
91
+ argv: commandArgv,
92
+ delegationToken: cred ?? undefined,
93
+ sessionId: invokingSessionId,
94
+ }, 10000);
95
+ if (!result)
96
+ return { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'daemon 暂时不可用' };
97
+ return result;
98
+ }
99
+ function printResult(data, jsonFormat) {
100
+ if (jsonFormat) {
101
+ console.log(JSON.stringify(data, null, 2));
102
+ }
103
+ else if (Array.isArray(data)) {
104
+ for (const item of data) {
105
+ printItem(item);
106
+ }
107
+ }
108
+ else {
109
+ printItem(data);
110
+ }
111
+ }
112
+ function printItem(item) {
113
+ if (typeof item !== 'object' || !item) {
114
+ console.log(String(item));
115
+ return;
116
+ }
117
+ const obj = item;
118
+ // ThreadHistoryResult — 必须在 ThreadIndexEntry 之前检查,两者都含 threadId
119
+ if ('messages' in obj && Array.isArray(obj.messages)) {
120
+ const result = obj;
121
+ console.log(`话题 ${result.threadId}(与 ${result.peer})最近 ${result.count} 条消息:`);
122
+ for (const m of result.messages)
123
+ printItem(m);
124
+ return;
125
+ }
126
+ // ThreadIndexEntry
127
+ if ('threadId' in obj) {
128
+ const running = obj.running ? ' [运行中]' : '';
129
+ // 内部话题(daemon 会话记录)不是真实联系人,标注出来避免误认
130
+ const internal = obj.internal ? ' [内部会话记录·无消息]' : '';
131
+ console.log(`${obj.peer}/${obj.threadId}${running}${internal}`);
132
+ // 名称只放名称本身:状态、标签各自单独一行,避免污染标题(标题会被 summary set --name 引用)
133
+ console.log(` 名称: ${obj.name}`);
134
+ if (obj.topicStatus)
135
+ console.log(` 状态: ${obj.topicStatus}`);
136
+ if (obj.label)
137
+ console.log(` 标签: ${obj.label}`);
138
+ if (obj.createdAt)
139
+ console.log(` 创建: ${new Date(obj.createdAt).toLocaleString('zh-CN')}`);
140
+ // list 场景下 lastActiveAt 已由消息日志派生,故称"活跃";show 场景下它仍是索引里的
141
+ // 管理动作时间,故称"最近变更"。
142
+ if (obj.lastActiveAt) {
143
+ const at = new Date(obj.lastActiveAt).toLocaleString('zh-CN');
144
+ console.log(obj.msgTotal !== undefined ? ` 活跃: ${at}` : ` 最近变更: ${at}`);
145
+ }
146
+ if (obj.msgTotal !== undefined) {
147
+ console.log(` 消息(24h/总计): ${obj.msg24h ?? 0}/${obj.msgTotal}`);
148
+ }
149
+ if (obj.lastMsgSnippet)
150
+ console.log(` 最后消息: ${obj.lastMsgSnippet}`);
151
+ if (obj.msgTotal !== undefined)
152
+ console.log(` 摘要: ${obj.summarySnippet ?? '未总结'}`);
153
+ console.log('');
154
+ return;
155
+ }
156
+ // ThreadHistoryMessage
157
+ if ('dir' in obj && 'content' in obj) {
158
+ const dir = obj.dir === 'in' ? '←' : '→';
159
+ console.log(`[${obj.time}] ${dir} ${obj.from}: ${String(obj.content).slice(0, 120)}`);
160
+ return;
161
+ }
162
+ // CreateThreadResult
163
+ if ('createdAt' in obj && 'sessionId' in obj) {
164
+ console.log(`✓ 话题创建成功: ${obj.threadId}`);
165
+ console.log(` 名称: ${obj.name}`);
166
+ console.log(` 对端: ${obj.peer}`);
167
+ return;
168
+ }
169
+ // RenameThreadResult
170
+ if ('changed' in obj) {
171
+ console.log(obj.changed ? `✓ 改名成功: ${obj.name}` : '✓ 名称未变更');
172
+ return;
173
+ }
174
+ // ListThreadsResult
175
+ if ('threads' in obj && Array.isArray(obj.threads)) {
176
+ const result = obj;
177
+ for (const t of result.threads)
178
+ printItem(t);
179
+ const shown = result.threads.length;
180
+ const totalPages = Math.ceil(result.total / (result.limit || 20));
181
+ console.log(`─────────────────────────────────────────────`);
182
+ console.log(`话题总数: ${result.total} 本页: ${shown} 条 第 ${result.page}/${totalPages} 页 每页: ${result.limit} 条`);
183
+ if (result.totalMsgCount !== undefined) {
184
+ console.log(`本页消息总数: ${result.totalMsgCount}`);
185
+ }
186
+ if (result.hasMore) {
187
+ const nextPage = result.page + 1;
188
+ const peerArg = result.peer ?? '<peer>';
189
+ const selfArg = result.selfAid ? `--self ${result.selfAid}` : '--self <selfAid>';
190
+ const sortArg = result.sort ? ` --sort ${result.sort}` : ' --sort recent';
191
+ const statusArg = result.status ? ` --status ${result.status}` : '';
192
+ const labelArg = result.label ? ` --label "${result.label}"` : '';
193
+ console.log(`下一页: ec thread list ${peerArg} ${selfArg} --page ${nextPage} --limit ${result.limit}${sortArg}${statusArg}${labelArg}`);
194
+ }
195
+ return;
196
+ }
197
+ // rebuild-index result
198
+ if ('rebuilt' in obj) {
199
+ if (obj.dryRun) {
200
+ // dry-run: handled in rebuild-index case directly, should not reach here
201
+ console.log(JSON.stringify(obj, null, 2));
202
+ }
203
+ else {
204
+ console.log(`✓ 索引重建完成,共 ${obj.rebuilt} 个话题`);
205
+ }
206
+ return;
207
+ }
208
+ console.log(JSON.stringify(obj, null, 2));
209
+ }
210
+ export async function runThreadCommand(args) {
211
+ const jsonFormat = hasFlag(args, '--format') && argValue(args, '--format') === 'json'
212
+ || hasFlag(args, '--json');
213
+ const sub = args[0];
214
+ if (!sub || hasFlag(args, '--help') || hasFlag(args, '-h')) {
215
+ console.log(HELP_TEXT);
216
+ return;
217
+ }
218
+ const managedSessionId = process.env.EVOLCORE_SESSION_ID;
219
+ const selfAid = managedSessionId
220
+ ? (process.env.EVOLCORE_SELF_AID ?? argValue(args, '--self'))
221
+ : argValue(args, '--self');
222
+ const commandArgv = canonicalizeEvolcoreCommandArgv(['ec', 'thread', ...args]).slice(1);
223
+ const sendOp = (req) => sendThreadOp(req, commandArgv);
224
+ // resolve <ref> → peer + threadId
225
+ function resolveRef(ref) {
226
+ if (ref === 'current')
227
+ return {};
228
+ const slash = ref.lastIndexOf('/');
229
+ if (slash < 0)
230
+ return { peer: ref };
231
+ return { peer: ref.slice(0, slash), threadId: ref.slice(slash + 1) };
232
+ }
233
+ switch (sub) {
234
+ case 'list': {
235
+ const peer = args[1]?.startsWith('-') ? undefined : args[1];
236
+ // peer 是必填参数;不带 peer 没有意义(全量话题跨对端混排,含内部 daemon 话题)
237
+ if (!peer) {
238
+ console.error('用法: ec thread list <peer> [选项]\n提示: 用 ec thread peers --self <selfAid> 查看有话题的所有对端');
239
+ process.exitCode = 1;
240
+ return;
241
+ }
242
+ const statusVal = argValue(args, '--status');
243
+ // B9: 枚举校验
244
+ if (statusVal && !TOPIC_STATUSES.includes(statusVal)) {
245
+ console.error(`✗ 无效状态: ${statusVal},可选: ${TOPIC_STATUSES.join('|')}`);
246
+ process.exitCode = 1;
247
+ return;
248
+ }
249
+ const sortVal = argValue(args, '--sort');
250
+ if (sortVal && !SORT_ORDERS.includes(sortVal)) {
251
+ console.error(`✗ 无效排序: ${sortVal},可选: ${SORT_ORDERS.join('|')}`);
252
+ process.exitCode = 1;
253
+ return;
254
+ }
255
+ const label = argValue(args, '--label');
256
+ const unlabeled = hasFlag(args, '--unlabeled');
257
+ const page = argValue(args, '--page') ? Number(argValue(args, '--page')) : undefined;
258
+ const limit = argValue(args, '--limit') ? Number(argValue(args, '--limit')) : undefined;
259
+ const resp = await sendOp({ op: 'list', selfAid, peer, status: statusVal, label, unlabeled, sort: sortVal, page, limit });
260
+ if (!resp.ok) {
261
+ console.error(`✗ ${resp.error}`);
262
+ process.exitCode = 1;
263
+ return;
264
+ }
265
+ printResult(resp.result, jsonFormat);
266
+ break;
267
+ }
268
+ case 'show': {
269
+ const ref = args[1];
270
+ if (!ref) {
271
+ console.error('用法: ec thread show <ref>');
272
+ process.exitCode = 1;
273
+ return;
274
+ }
275
+ const { peer, threadId } = resolveRef(ref);
276
+ const resp = await sendOp({ op: 'show', selfAid, peer, threadId });
277
+ if (!resp.ok) {
278
+ console.error(`✗ ${resp.error}`);
279
+ process.exitCode = 1;
280
+ return;
281
+ }
282
+ if (resp.indexWarning)
283
+ console.warn(`⚠ ${resp.indexWarning}`);
284
+ printResult(resp.result, jsonFormat);
285
+ break;
286
+ }
287
+ case 'history': {
288
+ const ref = args[1];
289
+ if (!ref) {
290
+ console.error('用法: ec thread history <ref>');
291
+ process.exitCode = 1;
292
+ return;
293
+ }
294
+ const { peer, threadId } = resolveRef(ref);
295
+ const limit = argValue(args, '--limit') ? Number(argValue(args, '--limit')) : undefined;
296
+ const before = argValue(args, '--before') ? Number(argValue(args, '--before')) : undefined;
297
+ const after = argValue(args, '--after') ? Number(argValue(args, '--after')) : undefined;
298
+ // threadId 可选:不传时读主会话(threadId=null)消息,与 show <peer> 行为对齐
299
+ const resp = await sendOp({ op: 'history', selfAid, peer, threadId, limit, before, after });
300
+ if (!resp.ok) {
301
+ console.error(`✗ ${resp.error}`);
302
+ process.exitCode = 1;
303
+ return;
304
+ }
305
+ printResult(resp.result, jsonFormat);
306
+ break;
307
+ }
308
+ case 'create': {
309
+ const peer = args[1];
310
+ if (!peer || peer.startsWith('-')) {
311
+ console.error('用法: ec thread create <peer> --name "..."');
312
+ process.exitCode = 1;
313
+ return;
314
+ }
315
+ const name = argValue(args, '--name');
316
+ if (!name) {
317
+ console.error('✗ 必须指定 --name');
318
+ process.exitCode = 1;
319
+ return;
320
+ }
321
+ const label = argValue(args, '--label');
322
+ const topicStatus = argValue(args, '--status');
323
+ const peerKind = hasFlag(args, '--group') ? 'group' : 'private';
324
+ const resp = await sendOp({ op: 'create', selfAid, peer, name, label, topicStatus, peerKind });
325
+ if (!resp.ok) {
326
+ console.error(`✗ ${resp.error}`);
327
+ process.exitCode = 1;
328
+ return;
329
+ }
330
+ printResult(resp.result, jsonFormat);
331
+ break;
332
+ }
333
+ case 'rename': {
334
+ const ref = args[1];
335
+ const newName = args[2];
336
+ if (!ref || newName === undefined) {
337
+ console.error('用法: ec thread rename <ref> "<名称>"');
338
+ process.exitCode = 1;
339
+ return;
340
+ }
341
+ // B10: 空字符串应报错而非显示用法
342
+ if (!newName) {
343
+ console.error('✗ 名称不能为空');
344
+ process.exitCode = 1;
345
+ return;
346
+ }
347
+ const { peer, threadId } = resolveRef(ref);
348
+ const resp = await sendOp({ op: 'rename', selfAid, peer, threadId, name: newName });
349
+ if (!resp.ok) {
350
+ console.error(`✗ ${resp.error}`);
351
+ process.exitCode = 1;
352
+ return;
353
+ }
354
+ printResult(resp.result, jsonFormat);
355
+ break;
356
+ }
357
+ case 'label': {
358
+ const ref = args[1];
359
+ if (!ref) {
360
+ console.error('用法: ec thread label <ref> <标签>');
361
+ process.exitCode = 1;
362
+ return;
363
+ }
364
+ const { peer, threadId } = resolveRef(ref);
365
+ const label = hasFlag(args, '--clear') ? null : args[2] ?? null;
366
+ const resp = await sendOp({ op: 'label', selfAid, peer, threadId, label });
367
+ if (!resp.ok) {
368
+ console.error(`✗ ${resp.error}`);
369
+ process.exitCode = 1;
370
+ return;
371
+ }
372
+ console.log(label === null ? '✓ 标签已清除' : `✓ 标签设为: ${label}`);
373
+ break;
374
+ }
375
+ case 'status': {
376
+ const ref = args[1];
377
+ const statusVal = args[2];
378
+ if (!ref || !statusVal) {
379
+ console.error('用法: ec thread status <ref> <状态>');
380
+ process.exitCode = 1;
381
+ return;
382
+ }
383
+ if (!TOPIC_STATUSES.includes(statusVal)) {
384
+ console.error(`✗ 无效状态: ${statusVal},可选: ${TOPIC_STATUSES.join('|')}`);
385
+ process.exitCode = 1;
386
+ return;
387
+ }
388
+ const { peer, threadId } = resolveRef(ref);
389
+ const resp = await sendOp({ op: 'status', selfAid, peer, threadId, topicStatus: statusVal });
390
+ if (!resp.ok) {
391
+ console.error(`✗ ${resp.error}`);
392
+ process.exitCode = 1;
393
+ return;
394
+ }
395
+ console.log(`✓ 状态更新为: ${statusVal}`);
396
+ break;
397
+ }
398
+ case 'rebuild-index': {
399
+ const aid = argValue(args, '--agent') ?? selfAid;
400
+ const dryRun = hasFlag(args, '--check');
401
+ const resp = await sendOp({ op: 'rebuild-index', selfAid: aid, dryRun });
402
+ if (!resp.ok) {
403
+ console.error(`✗ ${resp.error}`);
404
+ process.exitCode = 1;
405
+ return;
406
+ }
407
+ const r = resp.result;
408
+ if (dryRun) {
409
+ if (r.missing === 0 && r.stale === 0) {
410
+ console.log(`✓ 索引健康:共 ${r.rebuilt} 条话题,session 文件完整`);
411
+ }
412
+ else {
413
+ if (r.missing > 0) {
414
+ console.warn(`⚠ [严重] ${r.missing} 条话题 session 文件缺失——rebuild 后这些话题的 rename/label/status 改动将回退`);
415
+ }
416
+ if (r.stale > 0) {
417
+ console.warn(`⚠ [轻微] ${r.stale} 条话题属性与 session 文件最后一行不一致(写盘时序),rebuild-index 可修复`);
418
+ }
419
+ console.warn(` 建议执行 ec thread rebuild-index 修复`);
420
+ if (jsonFormat)
421
+ console.log(JSON.stringify(r, null, 2));
422
+ }
423
+ }
424
+ else {
425
+ printResult(resp.result, jsonFormat);
426
+ }
427
+ if (resp.indexWarning)
428
+ console.warn(`⚠ ${resp.indexWarning}`);
429
+ break;
430
+ }
431
+ case 'check-index': {
432
+ const aid = argValue(args, '--agent') ?? selfAid;
433
+ const resp = await sendOp({ op: 'check-index', selfAid: aid });
434
+ if (!resp.ok) {
435
+ console.error(`✗ ${resp.error}`);
436
+ process.exitCode = 1;
437
+ return;
438
+ }
439
+ const r = resp.result;
440
+ if (r.missing === 0 && r.stale === 0) {
441
+ console.log(`✓ 索引健康:共 ${r.total} 条话题,session 文件完整`);
442
+ }
443
+ else {
444
+ if (r.missing > 0) {
445
+ console.warn(`⚠ [严重] ${r.missing} 条话题 session 文件缺失——rebuild 后这些话题的 rename/label/status 改动将回退`);
446
+ }
447
+ if (r.stale > 0) {
448
+ console.warn(`⚠ [轻微] ${r.stale} 条话题属性与 session 文件最后一行不一致(写盘时序),rebuild-index 可修复`);
449
+ }
450
+ console.warn(` 建议执行 ec thread rebuild-index 修复`);
451
+ if (jsonFormat)
452
+ console.log(JSON.stringify(r, null, 2));
453
+ }
454
+ break;
455
+ }
456
+ case 'delete': {
457
+ const ref = args[1];
458
+ if (!ref) {
459
+ console.error('用法: ec thread delete <peer>/<threadId>');
460
+ process.exitCode = 1;
461
+ return;
462
+ }
463
+ const { peer, threadId } = resolveRef(ref);
464
+ // current ref 允许通过(daemon 侧通过 sessionId 反查);其他情况必须带 threadId
465
+ if (ref !== 'current' && !threadId) {
466
+ console.error('✗ delete 必须指定 threadId(格式: <peer>/<threadId>)');
467
+ process.exitCode = 1;
468
+ return;
469
+ }
470
+ const resp = await sendOp({ op: 'delete', selfAid, peer, threadId });
471
+ if (!resp.ok) {
472
+ console.error(`✗ ${resp.error}`);
473
+ process.exitCode = 1;
474
+ return;
475
+ }
476
+ console.log(`✓ 话题已删除: ${threadId ?? 'current'}`);
477
+ break;
478
+ }
479
+ case 'summary': {
480
+ const subCmd = args[1];
481
+ const ref = args[2];
482
+ if (!subCmd || !ref) {
483
+ console.error('用法: ec thread summary <show|set|log|clear> <ref>');
484
+ process.exitCode = 1;
485
+ return;
486
+ }
487
+ if (subCmd === 'show') {
488
+ const resp = await sendOp({ op: 'summary-show', selfAid });
489
+ if (!resp.ok) {
490
+ console.error(`✗ ${resp.error}`);
491
+ process.exitCode = 1;
492
+ return;
493
+ }
494
+ if (!resp.result) {
495
+ console.log('(暂无 summary)');
496
+ }
497
+ else {
498
+ const s = resp.result;
499
+ console.log(`rev ${s.rev} [${new Date(s.updatedAt).toLocaleString('zh-CN')}]`);
500
+ console.log(s.content);
501
+ }
502
+ }
503
+ else if (subCmd === 'set') {
504
+ // B6: extract flags first, then filter all known flags from remaining args to build content.
505
+ const ifRevRaw = args.includes('--if-rev') ? Number(args[args.indexOf('--if-rev') + 1]) : undefined;
506
+ const newName = args.includes('--name') ? argValue(args, '--name') : undefined;
507
+ const KNOWN_FLAGS = new Set(['--if-rev', '--name', '--self', '--as-session', '--format', '--json', '--help', '-h']);
508
+ const contentArgs = args.slice(3).filter((a, i, arr) => {
509
+ if (KNOWN_FLAGS.has(a))
510
+ return false;
511
+ // drop the value following a known flag
512
+ if (i > 0 && KNOWN_FLAGS.has(arr[i - 1]))
513
+ return false;
514
+ return true;
515
+ });
516
+ const content = contentArgs.join(' ');
517
+ if (!content) {
518
+ console.error('✗ 必须提供 summary 内容');
519
+ process.exitCode = 1;
520
+ return;
521
+ }
522
+ // --name 必填:显式表达"这条摘要对应什么标题",避免默认沿用陈旧名称。
523
+ // 不想改名时传 `-`(显式意图,比省略更难被忽略)。
524
+ if (newName === undefined) {
525
+ console.error('✗ 必须指定 --name <新标题>');
526
+ console.error(' 不改名请显式传 --name -');
527
+ console.error(` 例: ec thread summary set ${ref} "摘要内容" --name "话题的核心动作"`);
528
+ process.exitCode = 1;
529
+ return;
530
+ }
531
+ // 覆盖游标由 daemon 侧自动记录,无需传参
532
+ const { peer: setPeer, threadId: setThreadId } = resolveRef(ref);
533
+ const resp = await sendOp({
534
+ op: 'summary-set', selfAid, content, ifRev: ifRevRaw,
535
+ ...(setPeer ? { peer: setPeer } : {}),
536
+ ...(setThreadId ? { threadId: setThreadId } : {}),
537
+ name: newName,
538
+ });
539
+ if (!resp.ok) {
540
+ console.error(`✗ ${resp.error}`);
541
+ process.exitCode = 1;
542
+ return;
543
+ }
544
+ const setRes = resp.result;
545
+ const cursorNote = setRes?.coveredThroughTs
546
+ ? ` 覆盖至 ${new Date(setRes.coveredThroughTs).toLocaleString('zh-CN')}`
547
+ : '';
548
+ console.log(`✓ summary 已写入 rev ${setRes?.rev}${cursorNote}`);
549
+ if (setRes?.renamedTo)
550
+ console.log(`✓ 话题已改名: ${setRes.renamedTo}`);
551
+ if (setRes?.renameError)
552
+ console.log(`⚠ 改名失败(摘要已写入): ${setRes.renameError}`);
553
+ }
554
+ else if (subCmd === 'log') {
555
+ const resp = await sendOp({ op: 'summary-log', selfAid });
556
+ if (!resp.ok) {
557
+ console.error(`✗ ${resp.error}`);
558
+ process.exitCode = 1;
559
+ return;
560
+ }
561
+ const entries = resp.result;
562
+ if (!entries?.length) {
563
+ console.log('(暂无历史)');
564
+ return;
565
+ }
566
+ for (const e of entries) {
567
+ console.log(`rev ${e.rev} [${new Date(e.updatedAt).toLocaleString('zh-CN')}]`);
568
+ console.log(e.content);
569
+ console.log('');
570
+ }
571
+ }
572
+ else if (subCmd === 'clear') {
573
+ const resp = await sendOp({ op: 'summary-clear', selfAid });
574
+ if (!resp.ok) {
575
+ console.error(`✗ ${resp.error}`);
576
+ process.exitCode = 1;
577
+ return;
578
+ }
579
+ console.log('✓ summary 已清空(历史版本保留)');
580
+ }
581
+ else {
582
+ console.error(`✗ 未知 summary 子命令: ${subCmd}`);
583
+ process.exitCode = 1;
584
+ }
585
+ break;
586
+ }
587
+ default:
588
+ // peers 子命令:列出当前 agent 有话题记录的所有对端
589
+ if (sub === 'peers') {
590
+ if (!selfAid) {
591
+ console.error('用法: ec thread peers --self <selfAid>');
592
+ process.exitCode = 1;
593
+ return;
594
+ }
595
+ const resp = await sendOp({ op: 'list-peers', selfAid });
596
+ if (!resp.ok) {
597
+ console.error(`✗ ${resp.error}`);
598
+ process.exitCode = 1;
599
+ return;
600
+ }
601
+ const peers = resp.result?.peers ?? [];
602
+ if (!peers.length) {
603
+ console.log('(暂无对端话题记录)');
604
+ return;
605
+ }
606
+ if (jsonFormat) {
607
+ console.log(JSON.stringify(peers, null, 2));
608
+ return;
609
+ }
610
+ for (const p of peers) {
611
+ const kind = p.peerKind === 'group' ? ' [群]' : '';
612
+ console.log(`${p.peer}${kind} 话题数:${p.threadCount} 活跃:${new Date(p.lastActiveAt).toLocaleString('zh-CN')}`);
613
+ }
614
+ return;
615
+ }
616
+ console.error(`✗ 未知子命令: ${sub}`);
617
+ console.log(HELP_TEXT);
618
+ process.exitCode = 1;
619
+ }
620
+ }
@@ -112,7 +112,7 @@ function printHelp() {
112
112
  console.log(`用法: ec trigger <subcommand>
113
113
 
114
114
  命令:
115
- list [--agent <aid>] [--all] [--json]
115
+ list [--agent <aid>] [--all] [--thread-id <threadId>] [--json]
116
116
  show [--agent <aid>] <triggerId> [--json]
117
117
  history [--agent <aid>] [triggerId] [--limit <n>] [--json]
118
118
  create --exec <script|trigger-session|target-session> --file <trigger.json|trigger-dir> [--enable] [--json]
@@ -176,7 +176,8 @@ Update 参数:
176
176
  }
177
177
  async function listTriggers(args, json, response) {
178
178
  const agentAid = resolveAgentAid(args);
179
- const res = response ?? await request({ type: 'trigger.list', agentAid, all: hasFlag(args, '--all') });
179
+ const threadId = flagValue(args, '--thread-id');
180
+ const res = response ?? await request({ type: 'trigger.list', agentAid, all: hasFlag(args, '--all'), ...(threadId ? { threadId } : {}) });
180
181
  if (json)
181
182
  return printJson(res);
182
183
  const triggers = res.triggers;
@@ -333,6 +334,7 @@ export async function buildTriggerCreateRequest(args, fileScope, defaultAgentAid
333
334
  enabled: hasFlag(args, '--enable'),
334
335
  createdAt: now,
335
336
  updatedAt: now,
337
+ ...(parsed.labels ? { labels: parsed.labels } : {}),
336
338
  source: sourceFromParsed(parsed),
337
339
  execution: {
338
340
  type: parsed.executionType,
@@ -76,6 +76,7 @@ export function getManagementCommandPermissions(role) {
76
76
  // Reload is valid from an Agent's own channel and the daemon control plane.
77
77
  // The control-channel gate still requires a daemon owner for cross-Agent reloads.
78
78
  'agent.reload': { allow: true, dangerous: true, scopes: ['agent', 'control'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
79
+ 'skill.refresh': { allow: true, scopes: ['agent', 'control'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
79
80
  'dangerous:*': { allow: true, dangerous: true, constraints: { requireDaemonOwner: true } },
80
81
  };
81
82
  }
@@ -3,7 +3,6 @@ const BEHAVIOR_TOP_FIELDS = new Set([
3
3
  'chatmode',
4
4
  'mentionMode',
5
5
  'autoMentionQuietWindowMs',
6
- 'sessionIdleSec',
7
6
  'groupSlashRequireMentions',
8
7
  'responseMode',
9
8
  'responseModeParams',
@@ -128,8 +127,6 @@ export function resolveConfigFieldRule(fieldPath, scope) {
128
127
  return scalar(field, 'mention-mode');
129
128
  if (field === 'autoMentionQuietWindowMs')
130
129
  return scalar(field, 'nonnegative-number');
131
- if (field === 'sessionIdleSec')
132
- return scalar(field, 'finite-number');
133
130
  if (field === 'groupSlashRequireMentions')
134
131
  return scalar(field, 'boolean');
135
132
  if (field === 'show_activities')
@@ -142,6 +139,8 @@ export function resolveConfigFieldRule(fieldPath, scope) {
142
139
  return scalar('configSnapshots', 'boolean');
143
140
  if (field === 'restartRecovery.enabled')
144
141
  return scalar('restartRecovery.enabled', 'boolean');
142
+ if (field === 'skills.watch')
143
+ return scalar('skills.watch', 'boolean');
145
144
  if (top === 'chatmode') {
146
145
  if (parts.length === 1)
147
146
  return readonlyObject('chatmode');