lightclawbot 1.2.6 → 1.2.8

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 (102) hide show
  1. package/dist/src/config.js +30 -3
  2. package/dist/src/gateway.js +58 -12
  3. package/dist/src/group/constants/index.js +20 -0
  4. package/dist/src/group/inbound/index.js +254 -0
  5. package/dist/src/group/index.js +15 -0
  6. package/dist/src/group/orchestrator/execution/agent-runner.js +299 -0
  7. package/dist/src/group/orchestrator/execution/index.js +7 -0
  8. package/dist/src/group/orchestrator/execution/prompt-builder.js +288 -0
  9. package/dist/src/group/orchestrator/execution/soul-resolver.js +38 -0
  10. package/dist/src/group/orchestrator/execution/types.js +7 -0
  11. package/dist/src/group/orchestrator/index.js +14 -0
  12. package/dist/src/group/orchestrator/lifecycle/conversation-state.js +162 -0
  13. package/dist/src/group/orchestrator/lifecycle/index.js +7 -0
  14. package/dist/src/group/orchestrator/lifecycle/ledger-writer.js +96 -0
  15. package/dist/src/group/orchestrator/lifecycle/run-registry.js +174 -0
  16. package/dist/src/group/orchestrator/orchestrator.js +265 -0
  17. package/dist/src/group/orchestrator/planning/index.js +13 -0
  18. package/dist/src/group/orchestrator/planning/plan-validator.js +233 -0
  19. package/dist/src/group/orchestrator/planning/planning-parser.js +207 -0
  20. package/dist/src/group/orchestrator/planning/subtask-executor.js +345 -0
  21. package/dist/src/group/orchestrator/planning/summarizer-runner.js +224 -0
  22. package/dist/src/group/orchestrator/routes/index.js +9 -0
  23. package/dist/src/group/orchestrator/routes/leader-dispatch.js +229 -0
  24. package/dist/src/group/orchestrator/routes/leader-orchestration-route.js +179 -0
  25. package/dist/src/group/orchestrator/routes/leader-planning.js +92 -0
  26. package/dist/src/group/orchestrator/routes/leader-self-answer.js +223 -0
  27. package/dist/src/group/orchestrator/routes/mention-concurrent-route.js +226 -0
  28. package/dist/src/group/orchestrator/routes/route-helpers.js +186 -0
  29. package/dist/src/group/orchestrator/routes/types.js +8 -0
  30. package/dist/src/group/services/group-cleanup-service.js +183 -0
  31. package/dist/src/group/services/group-creation-service.js +122 -0
  32. package/dist/src/group/services/group-deletion-service.js +111 -0
  33. package/dist/src/group/services/group-history-service.js +73 -0
  34. package/dist/src/group/services/group-member-service.js +169 -0
  35. package/dist/src/group/services/group-query-service.js +133 -0
  36. package/dist/src/group/services/group-update-service.js +144 -0
  37. package/dist/src/group/services/index.js +20 -0
  38. package/dist/src/group/storage/concurrency-manager.js +119 -0
  39. package/dist/src/group/storage/group-storage-core.js +227 -0
  40. package/dist/src/group/storage/index.js +12 -0
  41. package/dist/src/group/storage/message-reader.js +213 -0
  42. package/dist/src/group/storage/message-writer.js +229 -0
  43. package/dist/src/group/storage/slice-manager.js +165 -0
  44. package/dist/src/group/types/common.js +5 -0
  45. package/dist/src/group/types/index.js +5 -0
  46. package/dist/src/group/types/message.js +5 -0
  47. package/dist/src/group/types/orchestrator.js +5 -0
  48. package/dist/src/group/types/storage.js +5 -0
  49. package/dist/src/group/utils/id-generator.js +15 -0
  50. package/dist/src/group/utils/index.js +12 -0
  51. package/dist/src/group/utils/mime.js +36 -0
  52. package/dist/src/group/utils/normalize.js +32 -0
  53. package/dist/src/group/utils/run-helpers.js +36 -0
  54. package/dist/src/history/session-reader.js +8 -2
  55. package/dist/src/outbound.js +12 -19
  56. package/dist/src/shared.js +4 -3
  57. package/dist/src/socket/events/agents-request.js +147 -0
  58. package/dist/src/socket/events/chat-request.js +67 -0
  59. package/dist/src/socket/events/file-download.js +121 -0
  60. package/dist/src/socket/events/group-abort.js +59 -0
  61. package/dist/src/socket/events/group-history.js +59 -0
  62. package/dist/src/socket/events/group-member.js +83 -0
  63. package/dist/src/socket/events/group-request.js +91 -0
  64. package/dist/src/socket/events/history-request.js +95 -0
  65. package/dist/src/socket/events/index.js +39 -0
  66. package/dist/src/socket/events/message-private.js +82 -0
  67. package/dist/src/socket/handlers.js +53 -568
  68. package/dist/src/socket/native-socket.js +21 -20
  69. package/dist/src/socket/registry.js +6 -3
  70. package/dist/src/socket/reliable-emitter.js +16 -13
  71. package/dist/src/socket/service/chat-common.js +36 -0
  72. package/dist/src/socket/service/chat-create.js +75 -0
  73. package/dist/src/socket/service/chat-delete.js +94 -0
  74. package/dist/src/socket/service/chat-list.js +82 -0
  75. package/dist/src/socket/service/chat-update.js +83 -0
  76. package/dist/src/socket/service/group-abort.js +104 -0
  77. package/dist/src/socket/service/group-history.js +140 -0
  78. package/dist/src/socket/service/group-member.js +209 -0
  79. package/dist/src/socket/service/group.js +233 -0
  80. package/dist/src/socket/service/history.js +102 -0
  81. package/dist/src/socket/service/index.js +14 -0
  82. package/dist/src/socket/types/index.js +7 -0
  83. package/dist/src/socket/types/request.js +8 -0
  84. package/dist/src/socket/types/service.js +8 -0
  85. package/dist/src/socket/utils/agent-soul.js +95 -0
  86. package/dist/src/socket/utils/index.js +8 -0
  87. package/dist/src/socket/utils/message.js +83 -0
  88. package/dist/src/socket/utils/validate.js +42 -0
  89. package/dist/src/streaming/index.js +1 -0
  90. package/dist/src/streaming/stream-reply-sink.js +367 -20
  91. package/dist/src/streaming/thinking-formatter.js +325 -0
  92. package/dist/src/streaming/types.js +20 -1
  93. package/dist/src/{download-tool.js → tools/download-tool.js} +41 -35
  94. package/dist/src/tools/group-history-tool.js +172 -0
  95. package/dist/src/{upload-tool.js → tools/upload-tool.js} +2 -2
  96. package/dist/src/tools.js +4 -3
  97. package/dist/src/utils/index.js +1 -0
  98. package/dist/src/utils/logger.js +38 -0
  99. package/openclaw.plugin.json +2 -1
  100. package/package.json +1 -1
  101. package/dist/src/socket/agent-soul.js +0 -41
  102. package/dist/src/socket/chat.js +0 -257
@@ -0,0 +1,162 @@
1
+ /**
2
+ * 回合状态机(ConversationStateMachine)
3
+ *
4
+ * 单次回合(一次"用户发问 → 系统回完所有内容")对应一个状态机实例。
5
+ * 由 Orchestrator 在 dispatch() 入口创建,贯穿三种路径(A/B/C)和 abort/失败兜底。
6
+ *
7
+ * 状态转移表(与 types.ts ConversationStatus 注释一致):
8
+ *
9
+ * PENDING → ROUTING
10
+ * ROUTING → RUNNING_LEADER | RUNNING_CHILDREN
11
+ * RUNNING_LEADER → DONE | PLANNING | ABORTED | FAILED
12
+ * PLANNING → RUNNING_CHILDREN | FAILED
13
+ * RUNNING_CHILDREN → DONE | SUMMARIZING | ABORTED | FAILED
14
+ * SUMMARIZING → DONE | ABORTED | FAILED
15
+ * DONE / ABORTED / FAILED 终态,不可再转移
16
+ *
17
+ * 设计要点:
18
+ * 1. 非法转移立刻抛错,避免静默错乱;
19
+ * 2. 状态变更可订阅(onChange),方便接日志 / metrics;
20
+ * 3. 提供 isTerminal() / isRunning() 等便捷判断。
21
+ */
22
+ /**
23
+ * 合法状态转移表(key → 允许进入的状态集合)
24
+ */
25
+ const TRANSITIONS = {
26
+ PENDING: ['ROUTING', 'ABORTED', 'FAILED'],
27
+ ROUTING: ['RUNNING_LEADER', 'RUNNING_CHILDREN', 'ABORTED', 'FAILED'],
28
+ RUNNING_LEADER: ['DONE', 'PLANNING', 'ABORTED', 'FAILED'],
29
+ PLANNING: ['RUNNING_CHILDREN', 'ABORTED', 'FAILED'],
30
+ RUNNING_CHILDREN: ['DONE', 'SUMMARIZING', 'ABORTED', 'FAILED'],
31
+ SUMMARIZING: ['DONE', 'ABORTED', 'FAILED'],
32
+ DONE: [],
33
+ ABORTED: [],
34
+ FAILED: [],
35
+ };
36
+ /**
37
+ * 终态集合
38
+ */
39
+ const TERMINAL_STATES = new Set([
40
+ 'DONE',
41
+ 'ABORTED',
42
+ 'FAILED',
43
+ ]);
44
+ /**
45
+ * 运行中状态集合(未进入终态、且已离开 PENDING/ROUTING)
46
+ */
47
+ const RUNNING_STATES = new Set([
48
+ 'RUNNING_LEADER',
49
+ 'PLANNING',
50
+ 'RUNNING_CHILDREN',
51
+ 'SUMMARIZING',
52
+ ]);
53
+ /**
54
+ * 回合状态机
55
+ */
56
+ export class ConversationStateMachine {
57
+ /** 关联的 conversationId */
58
+ conversationId;
59
+ /** 当前状态 */
60
+ current;
61
+ /** 状态变更监听器列表 */
62
+ listeners = [];
63
+ /**
64
+ * 构造一个新的状态机
65
+ *
66
+ * @param conversationId - 回合唯一 ID
67
+ * @param initial - 初始状态,默认为 PENDING
68
+ */
69
+ constructor(conversationId, initial = 'PENDING') {
70
+ this.conversationId = conversationId;
71
+ this.current = initial;
72
+ }
73
+ /**
74
+ * 获取当前状态
75
+ */
76
+ getStatus() {
77
+ return this.current;
78
+ }
79
+ /**
80
+ * 是否处于终态
81
+ */
82
+ isTerminal() {
83
+ return TERMINAL_STATES.has(this.current);
84
+ }
85
+ /**
86
+ * 是否处于运行中状态
87
+ */
88
+ isRunning() {
89
+ return RUNNING_STATES.has(this.current);
90
+ }
91
+ /**
92
+ * 判断从当前状态能否转移到目标状态
93
+ *
94
+ * @param next - 目标状态
95
+ */
96
+ canTransitionTo(next) {
97
+ return TRANSITIONS[this.current].includes(next);
98
+ }
99
+ /**
100
+ * 执行状态转移
101
+ *
102
+ * @param next - 目标状态
103
+ * @throws 当目标状态不在合法转移表中时抛错
104
+ */
105
+ transitionTo(next) {
106
+ if (this.current === next) {
107
+ // 同状态自循环视为 no-op,避免上层重复调用时报错
108
+ return;
109
+ }
110
+ if (!this.canTransitionTo(next)) {
111
+ throw new Error(`[Orchestrator] 非法状态转移: ${this.current} → ${next} (conversationId=${this.conversationId})`);
112
+ }
113
+ const prev = this.current;
114
+ this.current = next;
115
+ this.notify(next, prev);
116
+ }
117
+ /**
118
+ * 强制转移到终态(ABORTED / FAILED)
119
+ * 用于级联 abort 或全局兜底,绕过普通转移表
120
+ *
121
+ * @param next - 必须是终态
122
+ * @throws 当 next 不是终态时抛错
123
+ */
124
+ forceTerminal(next) {
125
+ if (this.current === next)
126
+ return;
127
+ if (this.isTerminal()) {
128
+ // 已在终态,幂等返回
129
+ return;
130
+ }
131
+ const prev = this.current;
132
+ this.current = next;
133
+ this.notify(next, prev);
134
+ }
135
+ /**
136
+ * 注册状态变更监听器
137
+ *
138
+ * @param listener - 回调
139
+ * @returns 取消订阅函数
140
+ */
141
+ onChange(listener) {
142
+ this.listeners.push(listener);
143
+ return () => {
144
+ this.listeners = this.listeners.filter((l) => l !== listener);
145
+ };
146
+ }
147
+ /**
148
+ * 内部:通知所有监听器
149
+ */
150
+ notify(next, prev) {
151
+ for (const listener of this.listeners) {
152
+ try {
153
+ listener(next, prev);
154
+ }
155
+ catch (err) {
156
+ // 监听器异常不影响状态机本身
157
+ // 后续接入统一日志后改为 logger.warn
158
+ console.warn('[ConversationStateMachine] listener error:', err);
159
+ }
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * lifecycle 子模块出口
3
+ * 统一导出回合状态机、Run 注册表、群账本写入器
4
+ */
5
+ export { ConversationStateMachine } from './conversation-state.js';
6
+ export { RunRegistry } from './run-registry.js';
7
+ export { LedgerWriter } from './ledger-writer.js';
@@ -0,0 +1,96 @@
1
+ /**
2
+ * LedgerWriter —— 群账本写入器
3
+ *
4
+ * 调度器内 **唯一** 写群账本的入口。
5
+ * 内部复用 `GroupStorageCore.appendTranscriptEntry`(已带分片 + 锁),
6
+ * 对外暴露三类语义化 API:
7
+ *
8
+ * - appendUser(...) 写一条 role=user 条目
9
+ * - appendAssistant(...) 写一条 role=assistant 条目
10
+ * - appendSystem(...) 写一条 role=system 条目
11
+ *
12
+ * 设计要点:
13
+ * 1. 不直接调用 fs,所有 IO 通过 GroupStorageCore;
14
+ * 2. 屏蔽底层 TranscriptEntry 字段拼装细节,调用方传业务参数即可;
15
+ * 3. 写入失败抛错,由上层(Orchestrator)决定降级策略。
16
+ */
17
+ import { generateMsgId } from '../../../dedup.js';
18
+ /**
19
+ * 群账本写入器
20
+ */
21
+ export class LedgerWriter {
22
+ storage;
23
+ /**
24
+ * @param storage - 群存储核心实例(建议外部单例传入)
25
+ */
26
+ constructor(storage) {
27
+ this.storage = storage;
28
+ }
29
+ /**
30
+ * 追加用户消息条目
31
+ *
32
+ * @param input - 用户消息入参
33
+ */
34
+ async appendUser(input) {
35
+ const { groupId, userId, content, mentions, files, msgId } = input;
36
+ await this.storage.appendTranscriptEntry(groupId, {
37
+ role: 'user',
38
+ userId,
39
+ content,
40
+ // 仅在显式提供时写入,避免给历史数据兜出 undefined 字段
41
+ ...(msgId ? { msgId } : {}),
42
+ // 文件附件放在顶层(与前端 TranscriptEntry 对齐)
43
+ ...(files && files.length > 0 ? { files } : {}),
44
+ extra: this.buildExtra({ mentions }),
45
+ });
46
+ }
47
+ /**
48
+ * 追加 Agent 回复条目
49
+ *
50
+ * @param input - Agent 回复入参
51
+ */
52
+ async appendAssistant(input) {
53
+ const { groupId, agentId, content, runId, parentRunId = null } = input;
54
+ await this.storage.appendTranscriptEntry(groupId, {
55
+ role: 'assistant',
56
+ agentId,
57
+ content,
58
+ runId,
59
+ parentRunId,
60
+ // 统一去重锰点:assistant 条目以 runId 作为 msgId,
61
+ // 使前端在「流式收尾本地追加 vs 后端历史回放」之间可用同一 key 去重。
62
+ msgId: runId,
63
+ });
64
+ }
65
+ /**
66
+ * 追加系统事件条目
67
+ *
68
+ * @param input - 系统事件入参
69
+ */
70
+ async appendSystem(input) {
71
+ const { groupId, event, agentId, userId, runId, content, members } = input;
72
+ await this.storage.appendTranscriptEntry(groupId, {
73
+ role: 'system',
74
+ event,
75
+ agentId,
76
+ userId,
77
+ runId,
78
+ content,
79
+ // 统一去重锚点:加 'sys:' 前缀避免与 assistant 条目(msgId=runId)冲突,
80
+ // 否则前端 mergeEntries 按 msgId 去重时会误将 system 条目当作 assistant 的重复而跳过。
81
+ // 缺省时本地生成一个唯一 ID,保证账本中的所有条目都带 msgId。
82
+ msgId: runId ? `sys:${runId}` : generateMsgId(),
83
+ extra: members && members.length > 0 ? { members } : undefined,
84
+ });
85
+ }
86
+ /**
87
+ * 内部:构造 extra 字段,过滤空值
88
+ */
89
+ buildExtra(input) {
90
+ const extra = {};
91
+ if (input.mentions && input.mentions.length > 0) {
92
+ extra.mentions = input.mentions;
93
+ }
94
+ return Object.keys(extra).length > 0 ? extra : undefined;
95
+ }
96
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * RunRegistry —— Run 注册表 / 级联 Abort 控制器
3
+ *
4
+ * 调度器维护的核心数据结构之一:
5
+ * Map<conversationId, Map<runId, AbortController>>
6
+ *
7
+ * 用途:
8
+ * 1. 注册某次回合下启动的所有 run(leader / mention / child / summarizer);
9
+ * 2. 用户点停止时,按 conversationId 一次性级联 abort 整棵 run 树;
10
+ * 3. 单 run 异常时,定向 abort 单条 run;
11
+ * 4. 提供 conversation 级 AbortSignal,供调度器直接传给 dispatch 流程。
12
+ *
13
+ * 设计要点:
14
+ * - 完全内存态,不做持久化;
15
+ * - 所有写操作做幂等处理,重复 register / abort 不抛错;
16
+ * - abortConversation 返回实际被中止的 runId 列表,方便接 group:abort:response。
17
+ */
18
+ /**
19
+ * Run 注册表(单例由 Orchestrator 持有)
20
+ */
21
+ export class RunRegistry {
22
+ /** conversationId → 桶 */
23
+ conversations = new Map();
24
+ /**
25
+ * 创建(或复用)一个回合桶,返回其级联 abort 信号
26
+ *
27
+ * @param conversationId - 回合 ID
28
+ */
29
+ ensureConversation(conversationId) {
30
+ let bucket = this.conversations.get(conversationId);
31
+ if (!bucket) {
32
+ bucket = {
33
+ rootController: new AbortController(),
34
+ runs: new Map(),
35
+ };
36
+ this.conversations.set(conversationId, bucket);
37
+ }
38
+ return bucket.rootController.signal;
39
+ }
40
+ /**
41
+ * 注册一次具体的 Agent 执行
42
+ * 自动监听回合级 abort,触发后立即 abort 本 run
43
+ *
44
+ * @param conversationId - 所属回合
45
+ * @param runId - run 唯一 ID
46
+ * @param agentId - 执行 Agent
47
+ * @returns 该 run 的 AbortSignal(传给 subagent.run)
48
+ */
49
+ registerRun(conversationId, runId, agentId) {
50
+ const bucket = this.getOrCreateBucket(conversationId);
51
+ // 幂等:重复注册同 runId 直接返回原 signal
52
+ const existed = bucket.runs.get(runId);
53
+ if (existed) {
54
+ return existed.controller.signal;
55
+ }
56
+ const controller = new AbortController();
57
+ const registered = {
58
+ runId,
59
+ agentId,
60
+ controller,
61
+ aborted: false,
62
+ };
63
+ bucket.runs.set(runId, registered);
64
+ // 回合级 abort 触发时,级联 abort 本 run
65
+ const onRootAbort = () => {
66
+ if (!registered.aborted) {
67
+ registered.aborted = true;
68
+ controller.abort();
69
+ }
70
+ };
71
+ if (bucket.rootController.signal.aborted) {
72
+ onRootAbort();
73
+ }
74
+ else {
75
+ bucket.rootController.signal.addEventListener('abort', onRootAbort, { once: true });
76
+ }
77
+ return controller.signal;
78
+ }
79
+ /**
80
+ * 解除注册(run 正常结束 / 失败时调用,避免泄漏)
81
+ *
82
+ * @param conversationId - 回合 ID
83
+ * @param runId - run ID
84
+ */
85
+ unregisterRun(conversationId, runId) {
86
+ const bucket = this.conversations.get(conversationId);
87
+ if (!bucket)
88
+ return;
89
+ bucket.runs.delete(runId);
90
+ }
91
+ /**
92
+ * 中止单条 run(不影响同回合其他 run)
93
+ *
94
+ * @param conversationId - 回合 ID
95
+ * @param runId - run ID
96
+ * @returns 是否真的执行了 abort(false 表示不存在或已 abort)
97
+ */
98
+ abortRun(conversationId, runId) {
99
+ const bucket = this.conversations.get(conversationId);
100
+ if (!bucket)
101
+ return false;
102
+ const run = bucket.runs.get(runId);
103
+ if (!run || run.aborted)
104
+ return false;
105
+ run.aborted = true;
106
+ run.controller.abort();
107
+ return true;
108
+ }
109
+ /**
110
+ * 级联中止整个回合
111
+ * 触发 rootController.abort(),所有已注册 run 自动级联 abort
112
+ *
113
+ * @param conversationId - 回合 ID
114
+ * @returns 实际被中止的 runId 列表(已 abort 的不计入)
115
+ */
116
+ abortConversation(conversationId) {
117
+ const bucket = this.conversations.get(conversationId);
118
+ if (!bucket)
119
+ return [];
120
+ const aborted = [];
121
+ // 先触发回合级 abort,让 onRootAbort 监听器统一处理
122
+ if (!bucket.rootController.signal.aborted) {
123
+ bucket.rootController.abort();
124
+ }
125
+ // 兜底遍历:确保所有 run 都被标记并收集 ID
126
+ for (const run of bucket.runs.values()) {
127
+ if (!run.aborted) {
128
+ run.aborted = true;
129
+ run.controller.abort();
130
+ }
131
+ aborted.push(run.runId);
132
+ }
133
+ return aborted;
134
+ }
135
+ /**
136
+ * 清理已完成的回合(应在终态后调用,避免内存泄漏)
137
+ *
138
+ * @param conversationId - 回合 ID
139
+ */
140
+ disposeConversation(conversationId) {
141
+ this.conversations.delete(conversationId);
142
+ }
143
+ /**
144
+ * 获取某回合下所有 runId(调试 / metrics 用)
145
+ *
146
+ * @param conversationId - 回合 ID
147
+ */
148
+ listRunIds(conversationId) {
149
+ const bucket = this.conversations.get(conversationId);
150
+ if (!bucket)
151
+ return [];
152
+ return Array.from(bucket.runs.keys());
153
+ }
154
+ /**
155
+ * 当前在跑的回合数(监控用)
156
+ */
157
+ size() {
158
+ return this.conversations.size;
159
+ }
160
+ /**
161
+ * 内部:取桶;不存在时创建
162
+ */
163
+ getOrCreateBucket(conversationId) {
164
+ let bucket = this.conversations.get(conversationId);
165
+ if (!bucket) {
166
+ bucket = {
167
+ rootController: new AbortController(),
168
+ runs: new Map(),
169
+ };
170
+ this.conversations.set(conversationId, bucket);
171
+ }
172
+ return bucket;
173
+ }
174
+ }