evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -0,0 +1,170 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ const RUNNER_TASK_EVENT_TYPES = new Set([
4
+ 'runner:task-started',
5
+ 'runner:task-progress',
6
+ 'runner:task-notification',
7
+ ]);
8
+ export const RUNNER_TASK_LOG_RE = /^events(?:-.+)?\.log$/;
9
+ const runnerLogCache = new Map();
10
+ function parseRunnerTaskLines(raw) {
11
+ const events = [];
12
+ for (const line of raw.split(/\r?\n/)) {
13
+ if (!line.includes('runner:task-'))
14
+ continue;
15
+ try {
16
+ const event = JSON.parse(line);
17
+ if (event && RUNNER_TASK_EVENT_TYPES.has(event.type) && typeof event.sessionId === 'string')
18
+ events.push(event);
19
+ }
20
+ catch { }
21
+ }
22
+ return events;
23
+ }
24
+ function readRunnerLogFile(file) {
25
+ let stat;
26
+ try {
27
+ stat = fs.statSync(file);
28
+ }
29
+ catch {
30
+ runnerLogCache.delete(file);
31
+ return [];
32
+ }
33
+ let cached = runnerLogCache.get(file);
34
+ if (!cached || stat.size < cached.offset || stat.ino !== cached.inode || stat.dev !== cached.device
35
+ || (stat.size === cached.offset && stat.mtimeMs !== cached.mtimeMs)) {
36
+ cached = { device: stat.dev, inode: stat.ino, offset: 0, mtimeMs: 0, events: [] };
37
+ }
38
+ if (stat.size === cached.offset) {
39
+ runnerLogCache.set(file, cached);
40
+ return cached.events;
41
+ }
42
+ const bytes = stat.size - cached.offset;
43
+ const buffer = Buffer.allocUnsafe(bytes);
44
+ let fd = null;
45
+ try {
46
+ fd = fs.openSync(file, 'r');
47
+ const read = fs.readSync(fd, buffer, 0, bytes, cached.offset);
48
+ if (read !== bytes)
49
+ return cached.events;
50
+ }
51
+ catch {
52
+ return cached.events;
53
+ }
54
+ finally {
55
+ if (fd != null)
56
+ try {
57
+ fs.closeSync(fd);
58
+ }
59
+ catch { }
60
+ }
61
+ const lastBreak = buffer.lastIndexOf(0x0a);
62
+ if (lastBreak < 0) {
63
+ runnerLogCache.set(file, cached);
64
+ return cached.events;
65
+ }
66
+ cached = {
67
+ device: stat.dev,
68
+ inode: stat.ino,
69
+ offset: cached.offset + lastBreak + 1,
70
+ mtimeMs: stat.mtimeMs,
71
+ events: cached.events.concat(parseRunnerTaskLines(buffer.subarray(0, lastBreak + 1).toString('utf8'))),
72
+ };
73
+ runnerLogCache.set(file, cached);
74
+ return cached.events;
75
+ }
76
+ function eventTimestamp(event) {
77
+ const numeric = Number(event.timestamp);
78
+ if (Number.isFinite(numeric) && numeric > 0)
79
+ return numeric;
80
+ const parsed = event.ts ? Date.parse(event.ts) : 0;
81
+ return Number.isFinite(parsed) ? parsed : 0;
82
+ }
83
+ function eventTaskId(event, aliases) {
84
+ if (typeof event.taskId === 'string' && event.taskId)
85
+ return event.taskId;
86
+ const useId = typeof event.toolUseId === 'string' ? event.toolUseId.trim() : '';
87
+ return useId ? aliases.get(useId) ?? `tool:${useId}` : null;
88
+ }
89
+ export function readRunnerTaskHistory(logsDir, evolSessionId) {
90
+ let files;
91
+ try {
92
+ files = fs.readdirSync(logsDir)
93
+ .filter(name => RUNNER_TASK_LOG_RE.test(name))
94
+ .sort()
95
+ .map(name => path.join(logsDir, name));
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ const validFiles = new Set(files);
101
+ for (const cachedFile of runnerLogCache.keys()) {
102
+ if (path.dirname(cachedFile) === logsDir && !validFiles.has(cachedFile))
103
+ runnerLogCache.delete(cachedFile);
104
+ }
105
+ const events = files.flatMap(readRunnerLogFile)
106
+ .filter(event => event.sessionId === evolSessionId)
107
+ .sort((a, b) => eventTimestamp(a) - eventTimestamp(b));
108
+ const tasks = new Map();
109
+ const aliases = new Map();
110
+ for (const event of events) {
111
+ if (typeof event.taskId === 'string' && event.taskId && typeof event.toolUseId === 'string' && event.toolUseId) {
112
+ aliases.set(event.toolUseId, event.taskId);
113
+ }
114
+ }
115
+ const hidden = new Set();
116
+ const terminal = new Set();
117
+ for (const event of events) {
118
+ const taskId = eventTaskId(event, aliases);
119
+ if (!taskId)
120
+ continue;
121
+ if (event.skipTranscript)
122
+ hidden.add(taskId);
123
+ const at = eventTimestamp(event);
124
+ let task = tasks.get(taskId);
125
+ if (!task) {
126
+ task = {
127
+ taskId,
128
+ taskKind: event.taskKind || 'agent',
129
+ toolUseId: event.toolUseId || null,
130
+ description: event.description || taskId,
131
+ subagentType: event.subagentType || null,
132
+ status: 'running',
133
+ terminalReason: '',
134
+ progress: '',
135
+ lastToolName: '',
136
+ totalTokens: 0,
137
+ toolUses: 0,
138
+ durationMs: 0,
139
+ startedAt: at,
140
+ updatedAt: at,
141
+ };
142
+ tasks.set(taskId, task);
143
+ }
144
+ if (event.type === 'runner:task-started') {
145
+ task.startedAt = task.startedAt ? Math.min(task.startedAt, at) : at;
146
+ if (!terminal.has(taskId))
147
+ task.status = 'running';
148
+ }
149
+ else if (event.type === 'runner:task-progress') {
150
+ task.progress = event.summary || event.description || task.progress;
151
+ }
152
+ else if (event.type === 'runner:task-notification') {
153
+ terminal.add(taskId);
154
+ task.status = event.status || 'completed';
155
+ task.terminalReason = event.terminalReason || task.terminalReason;
156
+ }
157
+ task.updatedAt = Math.max(task.updatedAt, at);
158
+ task.taskKind = event.taskKind || task.taskKind;
159
+ task.toolUseId = event.toolUseId || task.toolUseId;
160
+ if (event.type !== 'runner:task-progress')
161
+ task.description = event.description || task.description;
162
+ task.subagentType = event.subagentType || task.subagentType;
163
+ task.lastToolName = event.lastToolName || task.lastToolName;
164
+ task.totalTokens = event.usage?.totalTokens ?? event.totalTokens ?? task.totalTokens;
165
+ task.toolUses = event.usage?.toolUses ?? event.toolUses ?? task.toolUses;
166
+ task.durationMs = event.usage?.durationMs ?? event.durationMs ?? task.durationMs;
167
+ }
168
+ return [...tasks.values()].filter(task => !hidden.has(task.taskId))
169
+ .sort((a, b) => a.startedAt - b.startedAt || a.taskId.localeCompare(b.taskId));
170
+ }
@@ -1,8 +1,10 @@
1
1
  import { ensureDir } from '../../utils/atomic-write.js';
2
+ import { resolvePaths } from '../../paths.js';
2
3
  import { logger } from '../../utils/logger.js';
3
4
  import { encodePath } from '../../utils/cross-platform.js';
5
+ import { readRunnerTaskHistory } from './runner-task-history.js';
4
6
  import { chatDirPath, generateSessionId, formatTimestamp, atomicWriteJson, atomicWriteText, appendJsonl, appendJsonlDurable, fsyncJsonlFile, readJsonFile, readLastJsonlLine, readLastJsonlLineStrict, readAllJsonlLines, scanChatDirs, scanMetaFiles, ensureChatDir, readThreadIndex, writeThreadIndex, } from './session-fs-store.js';
5
- import { sessionToFile, fileToSession, formatSessionKey, DEFAULT_THREAD_ID } from './session-mapper.js';
7
+ import { sessionToFile, fileToSession, formatSessionKey, DEFAULT_THREAD_ID, isArchivedTopicLable, normalizeTopicLable, } from './session-mapper.js';
6
8
  import { tryParseChannelKey } from '../channel-loader.js';
7
9
  import { isSystemControlChannel } from '../system-channels.js';
8
10
  import path from 'path';
@@ -330,6 +332,9 @@ export class SessionManager {
330
332
  // backend boundary itself. Always take the latest binding and owner.
331
333
  session.agentSessionId = latestId;
332
334
  session.baseagent = latest.baseagent;
335
+ // Classification is owned by setTopicLable, not stale turn snapshots.
336
+ if (!opts?.lableMutation)
337
+ session.lable = latest.lable;
333
338
  // Merge metadata at field level as well. A caller can hold a stale
334
339
  // Session object across an await; preserving keys from the latest
335
340
  // snapshot prevents unrelated turn/route updates from being erased.
@@ -725,7 +730,7 @@ export class SessionManager {
725
730
  return result;
726
731
  }
727
732
  // ─── Session lifecycle ───
728
- async getOrCreateSession(channel, channelId, defaultProjectPath, threadId, metadata, name, userId, chatType, baseagent, selfAID, channelType, peerType, identity) {
733
+ async getOrCreateSession(channel, channelId, defaultProjectPath, threadId, metadata, name, userId, chatType, baseagent, selfAID, channelType, peerType, identity, lable) {
729
734
  if (isSystemControlChannel(channel) || isSystemControlChannel(channelType)) {
730
735
  throw new Error(`[SessionManager] system control channel is sessionless. channel="${channel}"`);
731
736
  }
@@ -739,7 +744,7 @@ export class SessionManager {
739
744
  // 用于 resolveIdentity fallback 正确命中群成员角色表(见 resolvePeerRoleDetail)。
740
745
  const identityConversationId = chatType === 'group' ? (metadata?.groupId || channelId) : userId;
741
746
  if (threadId) {
742
- const session = this.getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType);
747
+ const session = this.getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType, lable);
743
748
  session.identity = identity ?? this.resolveIdentity(channel, userId, chatType, identityConversationId);
744
749
  return session;
745
750
  }
@@ -960,7 +965,7 @@ export class SessionManager {
960
965
  current.metadata = metadata;
961
966
  this.persistSession(current, 'sync', { turnStateMutation: true });
962
967
  }
963
- getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType) {
968
+ getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType, lable) {
964
969
  // 使用精确路径(channelType + selfAID)
965
970
  const chatDir = (channelType && selfAID)
966
971
  ? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
@@ -1023,6 +1028,7 @@ export class SessionManager {
1023
1028
  chatType: effectiveChatType,
1024
1029
  metadata,
1025
1030
  name: name || '话题会话',
1031
+ lable: normalizeTopicLable(lable),
1026
1032
  createdAt: Date.now(),
1027
1033
  updatedAt: Date.now(),
1028
1034
  };
@@ -1286,7 +1292,7 @@ export class SessionManager {
1286
1292
  const validSessionId = normalizeBackendSessionId(session.agentSessionId);
1287
1293
  return { ...session, agentSessionId: validSessionId };
1288
1294
  }
1289
- async listSessions(channel, channelId) {
1295
+ async listSessions(channel, channelId, options = {}) {
1290
1296
  let chatDir;
1291
1297
  try {
1292
1298
  chatDir = this.resolveChatDirSafe(channel, channelId);
@@ -1294,7 +1300,16 @@ export class SessionManager {
1294
1300
  catch {
1295
1301
  return [];
1296
1302
  }
1297
- const sessions = this.findAllSessionsInChat(chatDir, true);
1303
+ const archiveFilter = options.archiveFilter ?? 'active';
1304
+ const sessions = this.findAllSessionsInChat(chatDir, true)
1305
+ .filter(session => {
1306
+ if (archiveFilter === 'all')
1307
+ return true;
1308
+ if (!session.threadId)
1309
+ return archiveFilter !== 'archived';
1310
+ const archived = isArchivedTopicLable(session.lable);
1311
+ return archiveFilter === 'archived' ? archived : !archived;
1312
+ });
1298
1313
  return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
1299
1314
  }
1300
1315
  async getSessionByProjectPath(channel, channelId, projectPath) {
@@ -1361,6 +1376,15 @@ export class SessionManager {
1361
1376
  this.persistSession(current, 'sync');
1362
1377
  return true;
1363
1378
  }
1379
+ async setTopicLable(sessionId, lable) {
1380
+ const normalized = normalizeTopicLable(lable);
1381
+ const loaded = this.loadSessionForUpdate(sessionId);
1382
+ if (!loaded?.current.threadId)
1383
+ return false;
1384
+ loaded.current.lable = normalized;
1385
+ this.persistSession(loaded.current, 'sync', { lableMutation: true });
1386
+ return true;
1387
+ }
1364
1388
  async unbindSession(sessionId) {
1365
1389
  const found = this.findSessionFileById(sessionId);
1366
1390
  if (!found)
@@ -1598,6 +1622,7 @@ export class SessionManager {
1598
1622
  agentSessionId: forkedAgentSessionId,
1599
1623
  metadata,
1600
1624
  name: options.name || `${sourceSession.name || '会话'} · 第 ${options.sourceTurn} 轮`,
1625
+ lable: normalizeTopicLable(options.lable === undefined ? sourceSession.lable : options.lable),
1601
1626
  createdAt: now,
1602
1627
  updatedAt: now,
1603
1628
  };
@@ -1740,6 +1765,9 @@ export class SessionManager {
1740
1765
  }
1741
1766
  return adapter.getFileInfo(projectPath, agentSessionId, agentAid);
1742
1767
  }
1768
+ getRunnerTaskHistory(sessionId) {
1769
+ return readRunnerTaskHistory(resolvePaths().logs, sessionId);
1770
+ }
1743
1771
  async listSdkSessions(projectPath, baseagent, agentAid) {
1744
1772
  const adapter = this.getFileAdapter(baseagent);
1745
1773
  if (!adapter?.listSdkSessions)
@@ -1,6 +1,45 @@
1
1
  import { formatTimestamp } from './session-fs-store.js';
2
2
  /** Default topic component used by the stable session routing key. */
3
3
  export const DEFAULT_THREAD_ID = 'main';
4
+ /** Normalize the optional topic category. The public field is spelled `lable`. */
5
+ export function normalizeTopicLable(value) {
6
+ if (value === undefined || value === null)
7
+ return undefined;
8
+ if (typeof value !== 'string')
9
+ throw new Error('lable 必须是字符串或 null');
10
+ const lable = value.trim();
11
+ if (lable.length > 100 || /[\x00-\x1F\x7F]/.test(lable)) {
12
+ throw new Error('lable 最多 100 个字符,且不能包含控制字符');
13
+ }
14
+ return lable || undefined;
15
+ }
16
+ /** The reserved category used to hide a topic from the default topic list. */
17
+ export const ARCHIVED_TOPIC_LABLE = '归档';
18
+ export function isArchivedTopicLable(lable) {
19
+ return lable === ARCHIVED_TOPIC_LABLE;
20
+ }
21
+ /** Resolve the public archived query flag while keeping omitted values backward compatible. */
22
+ export function resolveTopicArchiveFilter(value) {
23
+ if (value === undefined || value === false)
24
+ return 'active';
25
+ if (value === true)
26
+ return 'archived';
27
+ if (value === 'all')
28
+ return 'all';
29
+ throw new Error('archived 必须是布尔值或 "all"');
30
+ }
31
+ /** Ordinary messages may initialize a category; malformed metadata is ignored. */
32
+ export function readTopicLable(payload) {
33
+ if (!payload || typeof payload !== 'object')
34
+ return undefined;
35
+ const fields = payload;
36
+ try {
37
+ return normalizeTopicLable(Object.hasOwn(fields, 'lable') ? fields.lable : fields.label);
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
4
43
  export function formatSessionKey(channelType, channelId, threadId) {
5
44
  const tid = threadId || DEFAULT_THREAD_ID;
6
45
  return `${channelType}#${encodeURIComponent(channelId)}#${encodeURIComponent(tid)}`;
@@ -64,6 +103,7 @@ export function sessionToFile(session) {
64
103
  projectPath: session.projectPath,
65
104
  agentSessionId: session.agentSessionId ?? null,
66
105
  name: session.name ?? null,
106
+ ...(session.lable ? { lable: session.lable } : {}),
67
107
  activeTask: session.processingState ?? null,
68
108
  metadata,
69
109
  createdAt: session.createdAt,
@@ -113,6 +153,7 @@ export function fileToSession(file) {
113
153
  projectPath: file.projectPath,
114
154
  agentSessionId: file.agentSessionId ?? undefined,
115
155
  name: file.name ?? undefined,
156
+ ...(file.lable ? { lable: file.lable } : {}),
116
157
  processingState: file.activeTask ?? undefined,
117
158
  metadata,
118
159
  createdAt: file.createdAt,
@@ -13,6 +13,19 @@ function cloneTurnMetadata(value) {
13
13
  export function readTurnMetadata(session) {
14
14
  return cloneTurnMetadata(session.metadata?.turnState);
15
15
  }
16
+ function terminalRecoveryContext(session, context) {
17
+ if (!context)
18
+ return undefined;
19
+ return {
20
+ ...context,
21
+ baseagent: context.baseagent ?? session.baseagent,
22
+ // A newly allocated backend becomes known only after begin(). Preserve
23
+ // that binding at the terminal boundary, without adopting a backend switch.
24
+ agentSessionId: (!context.baseagent || context.baseagent === session.baseagent)
25
+ ? session.agentSessionId ?? context.agentSessionId
26
+ : context.agentSessionId,
27
+ };
28
+ }
16
29
  export class SessionTurnCoordinator {
17
30
  sessionManager;
18
31
  states = new Map();
@@ -69,7 +82,7 @@ export class SessionTurnCoordinator {
69
82
  this.states.set(sessionId, cloneTurnMetadata(next));
70
83
  return cloneTurnMetadata(next);
71
84
  }
72
- async begin(session, taskId, inputId = crypto.randomUUID()) {
85
+ async begin(session, taskId, inputId = crypto.randomUUID(), recoveryContext) {
73
86
  const state = this.hydrate(session);
74
87
  const generation = state.generation + 1;
75
88
  const turnId = `turn-${crypto.randomUUID()}`;
@@ -82,6 +95,7 @@ export class SessionTurnCoordinator {
82
95
  status: 'starting',
83
96
  startedAt,
84
97
  interactions: [],
98
+ recoveryContext,
85
99
  };
86
100
  const next = { ...state, schemaVersion: 1, generation, active };
87
101
  await this.persist(session, next);
@@ -96,20 +110,34 @@ export class SessionTurnCoordinator {
96
110
  async completeInterrupt(session, reason) {
97
111
  const state = this.hydrate(session);
98
112
  const active = state.active;
99
- if (!active || active.status !== 'interrupting')
113
+ if (!active) {
114
+ // A delayed task:error must not resurrect work after an idle /stop or a
115
+ // replacement message. Keep completed tasks completed.
116
+ const terminal = state.lastTerminal;
117
+ const cancelled = reason === 'stop' || reason === 'explicit_cancel';
118
+ const superseded = reason === 'new_message' || reason === 'recalled' || reason === 'restart';
119
+ await this.persist(session, terminal && terminal.status !== 'committed' && (cancelled || superseded)
120
+ ? { ...state, lastTerminal: { ...terminal, status: cancelled ? 'cancelled' : 'superseded', reason } }
121
+ : state);
122
+ return undefined;
123
+ }
124
+ if (active.status !== 'interrupting')
100
125
  return undefined;
101
126
  const next = {
102
127
  ...state,
103
128
  active: undefined,
104
129
  lastTerminal: {
105
130
  turnId: active.turnId,
131
+ recoveryContext: terminalRecoveryContext(session, active.recoveryContext),
106
132
  taskId: active.taskId,
107
133
  generation: active.generation,
108
- status: reason === 'daemon_restart'
109
- ? 'interrupted'
110
- : reason === 'stop' || reason === 'explicit_cancel'
111
- ? 'cancelled'
112
- : 'superseded',
134
+ status: reason === 'timeout' || reason === 'execution_error'
135
+ ? 'failed'
136
+ : reason === 'daemon_restart'
137
+ ? 'interrupted'
138
+ : reason === 'stop' || reason === 'explicit_cancel'
139
+ ? 'cancelled'
140
+ : 'superseded',
113
141
  reason,
114
142
  finishedAt: Date.now(),
115
143
  },
@@ -178,6 +206,7 @@ export class SessionTurnCoordinator {
178
206
  taskId: lease.taskId,
179
207
  generation: lease.generation,
180
208
  status: 'committed',
209
+ recoveryContext: terminalRecoveryContext(session, state.active?.recoveryContext),
181
210
  finishedAt: Date.now(),
182
211
  },
183
212
  };
@@ -196,6 +225,7 @@ export class SessionTurnCoordinator {
196
225
  taskId: lease.taskId,
197
226
  generation: lease.generation,
198
227
  status,
228
+ recoveryContext: terminalRecoveryContext(session, state.active?.recoveryContext),
199
229
  reason,
200
230
  finishedAt: Date.now(),
201
231
  },
@@ -1,4 +1,23 @@
1
- export function extractSessionUserContent(message) {
1
+ import { createHash } from 'node:crypto';
2
+ // These prompts are steered into the active base-agent turn by EvolCore. The
3
+ // Codex history adapter exposes steered input as a regular `user` item, so it
4
+ // must not split the user's actual turn when reconstructing fork anchors.
5
+ const TASK_RECOVERY_PROMPT = '继续当前会话尚未完成的任务。先核对最新用户指令、已有结果和任务状态;若已完成、用户已停止或正在等待必要输入/审批,则不要重复执行。对可能已产生副作用的步骤先核实结果,再继续未完成的工作。';
6
+ function isInternalInjectedUserMessage(message) {
7
+ const text = extractUserText(message);
8
+ if (!text)
9
+ return false;
10
+ if (text === TASK_RECOVERY_PROMPT)
11
+ return true;
12
+ if (text.startsWith('⚠️ 策略拒绝')
13
+ && text.endsWith('命令未执行,请按提示重新执行正确的命令。'))
14
+ return true;
15
+ if (text.startsWith('⚠️ proactive 模式违规:')
16
+ && text.endsWith('请重新执行正确的命令。'))
17
+ return true;
18
+ return false;
19
+ }
20
+ function extractText(message) {
2
21
  const value = message;
3
22
  let text = '';
4
23
  if (typeof value?.content === 'string') {
@@ -10,8 +29,12 @@ export function extractSessionUserContent(message) {
10
29
  .map((block) => block.text)
11
30
  .join(' ');
12
31
  }
13
- text = text.trim();
14
- text = text.replace(/^【新消息插入】\s*/, '').replace(/\s*【请无视之前中断继续处理】$/, '').trim();
32
+ return text.trim();
33
+ }
34
+ function extractUserText(message) {
35
+ let text = extractText(message);
36
+ text = text.replace(/^【新消息插入】\s*/, '')
37
+ .replace(/\s*【(?:请无视之前中断继续处理|请根据前后消息酌情处理)】$/, '').trim();
15
38
  if (text.startsWith('‹')) {
16
39
  const newline = text.indexOf('\n');
17
40
  if (newline !== -1)
@@ -27,10 +50,15 @@ export function extractSessionUserContent(message) {
27
50
  text = parts.join(' ');
28
51
  }
29
52
  text = text.replace(/\s+/g, ' ').trim();
30
- if (!text)
31
- return '';
53
+ return text;
54
+ }
55
+ export function extractSessionUserContent(message) {
56
+ const text = extractUserText(message);
32
57
  return text.length > 50 ? `${text.slice(0, 50)}…` : text;
33
58
  }
59
+ function textHash(text) {
60
+ return createHash('sha256').update(text.replace(/\s+/g, ' ').trim()).digest('hex');
61
+ }
34
62
  function isToolResultMessage(message) {
35
63
  const content = message?.content;
36
64
  return Array.isArray(content)
@@ -48,19 +76,32 @@ export function buildSessionTurnList(messages) {
48
76
  userContent: pending.userContent,
49
77
  userUuid: pending.userUuid,
50
78
  assistantUuid: pending.assistantUuid,
79
+ assistantUuids: [...(pending.assistantUuids ?? [])],
80
+ userTextHash: pending.userTextHash,
81
+ assistantTextHashes: [...pending.assistantTextHashes],
51
82
  });
52
83
  };
53
84
  for (const message of messages) {
54
85
  if (message.type === 'user') {
55
86
  if (isToolResultMessage(message.message))
56
87
  continue;
88
+ if (isInternalInjectedUserMessage(message.message))
89
+ continue;
57
90
  finishPending();
58
91
  const userContent = extractSessionUserContent(message.message);
59
- pending = userContent ? { userContent, userUuid: message.uuid } : undefined;
92
+ pending = userContent ? {
93
+ userContent, userUuid: message.uuid, assistantUuids: [],
94
+ userTextHash: textHash(extractUserText(message.message)), assistantTextHashes: [],
95
+ } : undefined;
60
96
  continue;
61
97
  }
62
- if (message.type === 'assistant' && pending)
98
+ if (message.type === 'assistant' && pending) {
63
99
  pending.assistantUuid = message.uuid;
100
+ pending.assistantUuids.push(message.uuid);
101
+ const text = extractText(message.message);
102
+ if (text)
103
+ pending.assistantTextHashes.push(textHash(text));
104
+ }
64
105
  }
65
106
  finishPending();
66
107
  return turns;