@shareai-lab/kode-sdk 2.7.1 → 2.7.2

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 (97) hide show
  1. package/dist/core/agent/breakpoint-manager.js +36 -0
  2. package/dist/core/agent/message-queue.js +57 -0
  3. package/dist/core/agent/permission-manager.js +32 -0
  4. package/dist/core/agent/todo-manager.js +91 -0
  5. package/dist/core/agent/tool-runner.js +45 -0
  6. package/dist/core/agent.js +2035 -0
  7. package/dist/core/config.js +2 -0
  8. package/dist/core/context-manager.js +241 -0
  9. package/dist/core/errors.js +49 -0
  10. package/dist/core/events.js +329 -0
  11. package/dist/core/file-pool.d.ts +2 -0
  12. package/dist/core/file-pool.js +125 -0
  13. package/dist/core/hooks.js +71 -0
  14. package/dist/core/permission-modes.js +61 -0
  15. package/dist/core/pool.js +301 -0
  16. package/dist/core/room.js +57 -0
  17. package/dist/core/scheduler.js +58 -0
  18. package/dist/core/skills/index.js +20 -0
  19. package/dist/core/skills/management-manager.js +557 -0
  20. package/dist/core/skills/manager.js +243 -0
  21. package/dist/core/skills/operation-queue.js +113 -0
  22. package/dist/core/skills/sandbox-file-manager.js +183 -0
  23. package/dist/core/skills/types.js +9 -0
  24. package/dist/core/skills/xml-generator.js +70 -0
  25. package/dist/core/template.js +35 -0
  26. package/dist/core/time-bridge.js +100 -0
  27. package/dist/core/todo.js +89 -0
  28. package/dist/core/types.js +3 -0
  29. package/dist/index.js +148 -60461
  30. package/dist/infra/db/postgres/postgres-store.js +1073 -0
  31. package/dist/infra/db/sqlite/sqlite-store.js +800 -0
  32. package/dist/infra/e2b/e2b-fs.js +128 -0
  33. package/dist/infra/e2b/e2b-sandbox.js +156 -0
  34. package/dist/infra/e2b/e2b-template.js +105 -0
  35. package/dist/infra/e2b/index.js +9 -0
  36. package/dist/infra/e2b/types.js +2 -0
  37. package/dist/infra/provider.js +67 -0
  38. package/dist/infra/providers/anthropic.js +308 -0
  39. package/dist/infra/providers/core/errors.js +353 -0
  40. package/dist/infra/providers/core/fork.js +418 -0
  41. package/dist/infra/providers/core/index.js +76 -0
  42. package/dist/infra/providers/core/logger.js +191 -0
  43. package/dist/infra/providers/core/retry.js +189 -0
  44. package/dist/infra/providers/core/usage.js +376 -0
  45. package/dist/infra/providers/gemini.js +493 -0
  46. package/dist/infra/providers/index.js +83 -0
  47. package/dist/infra/providers/openai.js +662 -0
  48. package/dist/infra/providers/types.js +20 -0
  49. package/dist/infra/providers/utils.js +400 -0
  50. package/dist/infra/sandbox-factory.js +30 -0
  51. package/dist/infra/sandbox.js +243 -0
  52. package/dist/infra/store/factory.js +80 -0
  53. package/dist/infra/store/index.js +26 -0
  54. package/dist/infra/store/json-store.js +606 -0
  55. package/dist/infra/store/types.js +2 -0
  56. package/dist/infra/store.js +29 -0
  57. package/dist/tools/bash_kill/index.js +35 -0
  58. package/dist/tools/bash_kill/prompt.js +14 -0
  59. package/dist/tools/bash_logs/index.js +40 -0
  60. package/dist/tools/bash_logs/prompt.js +14 -0
  61. package/dist/tools/bash_run/index.js +61 -0
  62. package/dist/tools/bash_run/prompt.js +18 -0
  63. package/dist/tools/builtin.js +26 -0
  64. package/dist/tools/define.js +214 -0
  65. package/dist/tools/fs_edit/index.js +62 -0
  66. package/dist/tools/fs_edit/prompt.js +15 -0
  67. package/dist/tools/fs_glob/index.js +40 -0
  68. package/dist/tools/fs_glob/prompt.js +15 -0
  69. package/dist/tools/fs_grep/index.js +66 -0
  70. package/dist/tools/fs_grep/prompt.js +16 -0
  71. package/dist/tools/fs_multi_edit/index.js +106 -0
  72. package/dist/tools/fs_multi_edit/prompt.js +16 -0
  73. package/dist/tools/fs_read/index.js +40 -0
  74. package/dist/tools/fs_read/prompt.js +16 -0
  75. package/dist/tools/fs_write/index.js +40 -0
  76. package/dist/tools/fs_write/prompt.js +15 -0
  77. package/dist/tools/index.js +61 -0
  78. package/dist/tools/mcp.js +185 -0
  79. package/dist/tools/registry.js +26 -0
  80. package/dist/tools/scripts.js +205 -0
  81. package/dist/tools/skills.js +115 -0
  82. package/dist/tools/task_run/index.js +58 -0
  83. package/dist/tools/task_run/prompt.js +25 -0
  84. package/dist/tools/todo_read/index.js +29 -0
  85. package/dist/tools/todo_read/prompt.js +18 -0
  86. package/dist/tools/todo_write/index.js +42 -0
  87. package/dist/tools/todo_write/prompt.js +23 -0
  88. package/dist/tools/tool.js +211 -0
  89. package/dist/tools/toolkit.js +98 -0
  90. package/dist/tools/type-inference.js +207 -0
  91. package/dist/utils/agent-id.js +28 -0
  92. package/dist/utils/logger.js +44 -0
  93. package/dist/utils/session-id.js +64 -0
  94. package/package.json +7 -38
  95. package/dist/index.js.map +0 -7
  96. package/dist/index.mjs +0 -60385
  97. package/dist/index.mjs.map +0 -7
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContextManager = void 0;
4
+ /**
5
+ * ContextManager v2 - 带完整历史追踪的上下文管理器
6
+ *
7
+ * 职责:
8
+ * 1. 分析上下文使用情况(token 估算)
9
+ * 2. 压缩超限上下文并保存历史窗口
10
+ * 3. 保存压缩记录与文件快照
11
+ * 4. 发送 Monitor 事件以供审计
12
+ */
13
+ class ContextManager {
14
+ constructor(store, agentId, opts) {
15
+ this.store = store;
16
+ this.agentId = agentId;
17
+ this.maxTokens = opts?.maxTokens ?? 50000;
18
+ this.compressToTokens = opts?.compressToTokens ?? 30000;
19
+ this.compressionModel = opts?.compressionModel ?? 'claude-haiku-4-5';
20
+ this.compressionPrompt = opts?.compressionPrompt ?? 'Summarize the conversation history concisely';
21
+ const keepRecent = opts?.multimodalRetention?.keepRecent ?? 3;
22
+ this.keepRecentMultimodal = Math.max(0, Math.floor(keepRecent));
23
+ }
24
+ /**
25
+ * 分析上下文使用情况(粗略的 token 估算)
26
+ */
27
+ analyze(messages) {
28
+ const totalTokens = messages.reduce((sum, message) => {
29
+ const blocks = message.metadata?.transport === 'omit'
30
+ ? message.content
31
+ : message.metadata?.content_blocks ?? message.content;
32
+ return (sum +
33
+ blocks.reduce((inner, block) => {
34
+ if (block.type === 'text')
35
+ return inner + Math.ceil(block.text.length / 4); // 粗略估算:4 chars = 1 token
36
+ if (block.type === 'reasoning')
37
+ return inner + Math.ceil(block.reasoning.length / 4);
38
+ if (block.type === 'image' || block.type === 'audio' || block.type === 'file') {
39
+ return inner + 500; // 固定估算,避免 base64 膨胀
40
+ }
41
+ return inner + Math.ceil(JSON.stringify(block).length / 4);
42
+ }, 0));
43
+ }, 0);
44
+ return {
45
+ totalTokens,
46
+ messageCount: messages.length,
47
+ shouldCompress: totalTokens > this.maxTokens,
48
+ };
49
+ }
50
+ /**
51
+ * 压缩上下文并保存历史
52
+ *
53
+ * 流程:
54
+ * 1. 保存 HistoryWindow(压缩前的完整快照)
55
+ * 2. 执行压缩(简单版:保留后半部分 + 生成摘要)
56
+ * 3. 保存 CompressionRecord(压缩元信息)
57
+ * 4. 保存重要文件快照(如果有 FilePool)
58
+ * 5. 返回压缩结果
59
+ */
60
+ async compress(messages, events, filePool, sandbox) {
61
+ const usage = this.analyze(messages);
62
+ if (!usage.shouldCompress)
63
+ return undefined;
64
+ const timestamp = Date.now();
65
+ const windowId = `window-${timestamp}`;
66
+ const compressionId = `comp-${timestamp}`;
67
+ // 1. 保存历史窗口
68
+ const window = {
69
+ id: windowId,
70
+ messages,
71
+ events,
72
+ stats: {
73
+ messageCount: messages.length,
74
+ tokenCount: usage.totalTokens,
75
+ eventCount: events.length,
76
+ },
77
+ timestamp,
78
+ };
79
+ await this.store.saveHistoryWindow(this.agentId, window);
80
+ // 2. 执行压缩(简化版:保留 60% 消息)
81
+ const targetRatio = this.compressToTokens / usage.totalTokens;
82
+ let keepCount = Math.ceil(messages.length * Math.max(targetRatio, 0.6));
83
+ const keepFromIndex = messages.length - keepCount;
84
+ const multimodalKeepFrom = this.findKeepFromIndexForMultimodal(messages, this.keepRecentMultimodal);
85
+ const finalKeepFrom = Math.min(keepFromIndex, multimodalKeepFrom);
86
+ keepCount = messages.length - finalKeepFrom;
87
+ const retainedMessages = messages.slice(-keepCount);
88
+ const removedMessages = messages.slice(0, messages.length - keepCount);
89
+ // 生成摘要
90
+ const summaryText = this.generateSummary(removedMessages);
91
+ const summary = {
92
+ role: 'system',
93
+ content: [
94
+ {
95
+ type: 'text',
96
+ text: `<context-summary timestamp="${new Date().toISOString()}" window="${windowId}">\n${summaryText}\n</context-summary>`,
97
+ },
98
+ ],
99
+ };
100
+ // 3. 保存压缩记录
101
+ const recoveredPaths = [];
102
+ if (filePool && sandbox) {
103
+ const accessed = filePool.getAccessedFiles().slice(0, 5); // 只保存最近 5 个文件
104
+ for (const { path, mtime } of accessed) {
105
+ recoveredPaths.push(path);
106
+ try {
107
+ // 读取实际文件内容(用于上下文恢复)
108
+ const content = await sandbox.fs.read(path);
109
+ const file = {
110
+ path,
111
+ content,
112
+ mtime,
113
+ timestamp,
114
+ };
115
+ await this.store.saveRecoveredFile(this.agentId, file);
116
+ }
117
+ catch (err) {
118
+ // 如果读取失败,保存错误信息
119
+ const file = {
120
+ path,
121
+ content: `// Failed to read file: ${err instanceof Error ? err.message : String(err)}`,
122
+ mtime,
123
+ timestamp,
124
+ };
125
+ await this.store.saveRecoveredFile(this.agentId, file);
126
+ }
127
+ }
128
+ }
129
+ const ratio = retainedMessages.length / messages.length;
130
+ const record = {
131
+ id: compressionId,
132
+ windowId,
133
+ config: {
134
+ model: this.compressionModel,
135
+ prompt: this.compressionPrompt,
136
+ threshold: this.maxTokens,
137
+ },
138
+ summary: summaryText.slice(0, 500), // 保存摘要前 500 字符
139
+ ratio,
140
+ recoveredFiles: recoveredPaths,
141
+ timestamp,
142
+ };
143
+ await this.store.saveCompressionRecord(this.agentId, record);
144
+ return {
145
+ summary,
146
+ removedMessages,
147
+ retainedMessages,
148
+ windowId,
149
+ compressionId,
150
+ ratio,
151
+ };
152
+ }
153
+ /**
154
+ * 生成压缩摘要
155
+ */
156
+ generateSummary(messages) {
157
+ return messages
158
+ .map((msg, idx) => {
159
+ const header = `${idx + 1}. [${msg.role}]`;
160
+ const blocks = msg.metadata?.transport === 'omit'
161
+ ? msg.content
162
+ : msg.metadata?.content_blocks ?? msg.content;
163
+ const content = blocks
164
+ .map((block) => {
165
+ if (block.type === 'text')
166
+ return block.text.slice(0, 200);
167
+ if (block.type === 'reasoning')
168
+ return `[reasoning] ${block.reasoning.slice(0, 200)}`;
169
+ if (block.type === 'image') {
170
+ const source = block.base64 ? 'base64' : block.url ? 'url' : block.file_id ? 'file_id' : 'unknown';
171
+ const id = block.file_id || block.url || 'base64';
172
+ return `[image-summary id=${id} mime=${block.mime_type || 'unknown'} note="source=${source}"]`;
173
+ }
174
+ if (block.type === 'audio') {
175
+ const source = block.base64 ? 'base64' : block.url ? 'url' : block.file_id ? 'file_id' : 'unknown';
176
+ const id = block.file_id || block.url || 'base64';
177
+ return `[audio-summary id=${id} mime=${block.mime_type || 'unknown'} note="source=${source}"]`;
178
+ }
179
+ if (block.type === 'file') {
180
+ const source = block.base64 ? 'base64' : block.url ? 'url' : block.file_id ? 'file_id' : 'unknown';
181
+ const id = block.file_id || block.url || block.filename || 'base64';
182
+ return `[file-summary id=${id} mime=${block.mime_type || 'unknown'} note="source=${source}"]`;
183
+ }
184
+ if (block.type === 'tool_use')
185
+ return `[tool] ${block.name}(...)`;
186
+ if (block.type === 'tool_result') {
187
+ const preview = JSON.stringify(block.content).slice(0, 100);
188
+ return `[result] ${preview}`;
189
+ }
190
+ return '';
191
+ })
192
+ .join('\n');
193
+ return `${header}\n${content}`;
194
+ })
195
+ .join('\n\n');
196
+ }
197
+ findKeepFromIndexForMultimodal(messages, keepRecent) {
198
+ if (keepRecent <= 0)
199
+ return messages.length;
200
+ let remaining = keepRecent;
201
+ let earliestIndex = messages.length;
202
+ for (let i = messages.length - 1; i >= 0; i--) {
203
+ const msg = messages[i];
204
+ const blocks = msg.metadata?.transport === 'omit'
205
+ ? msg.content
206
+ : msg.metadata?.content_blocks ?? msg.content;
207
+ for (const block of blocks) {
208
+ if (block.type === 'image' || block.type === 'audio' || block.type === 'file') {
209
+ remaining -= 1;
210
+ earliestIndex = i;
211
+ if (remaining <= 0) {
212
+ return i;
213
+ }
214
+ }
215
+ }
216
+ }
217
+ if (earliestIndex === messages.length) {
218
+ return messages.length;
219
+ }
220
+ return earliestIndex;
221
+ }
222
+ /**
223
+ * 恢复历史窗口(用于审计或调试)
224
+ */
225
+ async loadHistory() {
226
+ return await this.store.loadHistoryWindows(this.agentId);
227
+ }
228
+ /**
229
+ * 加载压缩记录
230
+ */
231
+ async loadCompressions() {
232
+ return await this.store.loadCompressionRecords(this.agentId);
233
+ }
234
+ /**
235
+ * 加载恢复的文件
236
+ */
237
+ async loadRecoveredFiles() {
238
+ return await this.store.loadRecoveredFiles(this.agentId);
239
+ }
240
+ }
241
+ exports.ContextManager = ContextManager;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProviderCapabilityError = exports.UnsupportedProviderError = exports.UnsupportedContentBlockError = exports.MultimodalValidationError = exports.ResumeError = void 0;
4
+ exports.assert = assert;
5
+ class ResumeError extends Error {
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = 'ResumeError';
10
+ }
11
+ }
12
+ exports.ResumeError = ResumeError;
13
+ function assert(condition, code, message) {
14
+ if (!condition) {
15
+ throw new ResumeError(code, message);
16
+ }
17
+ }
18
+ class MultimodalValidationError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.code = 'ERR_MULTIMODAL_INVALID';
22
+ this.name = 'MultimodalValidationError';
23
+ }
24
+ }
25
+ exports.MultimodalValidationError = MultimodalValidationError;
26
+ class UnsupportedContentBlockError extends Error {
27
+ constructor(message) {
28
+ super(message);
29
+ this.code = 'ERR_CONTENTBLOCK_UNSUPPORTED';
30
+ this.name = 'UnsupportedContentBlockError';
31
+ }
32
+ }
33
+ exports.UnsupportedContentBlockError = UnsupportedContentBlockError;
34
+ class UnsupportedProviderError extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.code = 'ERR_PROVIDER_UNSUPPORTED';
38
+ this.name = 'UnsupportedProviderError';
39
+ }
40
+ }
41
+ exports.UnsupportedProviderError = UnsupportedProviderError;
42
+ class ProviderCapabilityError extends Error {
43
+ constructor(message) {
44
+ super(message);
45
+ this.code = 'ERR_PROVIDER_CAPABILITY';
46
+ this.name = 'ProviderCapabilityError';
47
+ }
48
+ }
49
+ exports.ProviderCapabilityError = ProviderCapabilityError;
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventBus = void 0;
4
+ const events_1 = require("events");
5
+ const logger_1 = require("../utils/logger");
6
+ class EventBus {
7
+ constructor() {
8
+ this.cursor = 0;
9
+ this.seq = 0;
10
+ this.timeline = [];
11
+ this.subscribers = new Map();
12
+ this.controlEmitter = new events_1.EventEmitter();
13
+ this.monitorEmitter = new events_1.EventEmitter();
14
+ this.failedEvents = [];
15
+ this.MAX_FAILED_BUFFER = 1000;
16
+ this.subscribers.set('progress', new Set());
17
+ this.subscribers.set('control', new Set());
18
+ this.subscribers.set('monitor', new Set());
19
+ // Prevent Node.js ERR_UNHANDLED_ERROR when emitting 'error' events
20
+ // without a listener. This allows MonitorEvent with type='error' to be
21
+ // emitted safely. Users can still register their own 'error' handlers.
22
+ this.monitorEmitter.on('error', () => {
23
+ // No-op: errors are handled through the subscriber system
24
+ });
25
+ }
26
+ setStore(store, agentId) {
27
+ this.store = store;
28
+ this.agentId = agentId;
29
+ }
30
+ emitProgress(event) {
31
+ const envelope = this.emit('progress', event);
32
+ this.notifySubscribers('progress', envelope);
33
+ return envelope;
34
+ }
35
+ emitControl(event) {
36
+ const envelope = this.emit('control', event);
37
+ this.controlEmitter.emit(event.type, envelope.event);
38
+ this.notifySubscribers('control', envelope);
39
+ return envelope;
40
+ }
41
+ emitMonitor(event) {
42
+ const envelope = this.emit('monitor', event);
43
+ this.monitorEmitter.emit(event.type, envelope.event);
44
+ this.notifySubscribers('monitor', envelope);
45
+ return envelope;
46
+ }
47
+ subscribeProgress(opts) {
48
+ const subscriber = new EventSubscriber(opts?.kinds);
49
+ this.subscribers.get('progress').add(subscriber);
50
+ if (opts?.since) {
51
+ void this.replayHistory('progress', subscriber, opts.since);
52
+ }
53
+ const bus = this;
54
+ return {
55
+ [Symbol.asyncIterator]() {
56
+ return {
57
+ async next() {
58
+ const value = (await subscriber.next());
59
+ if (!value) {
60
+ bus.subscribers.get('progress').delete(subscriber);
61
+ return { done: true, value: undefined };
62
+ }
63
+ return { done: false, value };
64
+ },
65
+ async return() {
66
+ subscriber.close();
67
+ bus.subscribers.get('progress').delete(subscriber);
68
+ return { done: true, value: undefined };
69
+ },
70
+ };
71
+ },
72
+ };
73
+ }
74
+ subscribe(channels = ['progress', 'control', 'monitor'], opts) {
75
+ const subscriber = new EventSubscriber(opts?.kinds);
76
+ for (const channel of channels) {
77
+ this.subscribers.get(channel).add(subscriber);
78
+ if (opts?.since) {
79
+ void this.replayHistory(channel, subscriber, opts.since);
80
+ }
81
+ }
82
+ return this.iterableFor(channels, subscriber);
83
+ }
84
+ onControl(type, handler) {
85
+ this.controlEmitter.on(type, handler);
86
+ return () => this.controlEmitter.off(type, handler);
87
+ }
88
+ onMonitor(type, handler) {
89
+ this.monitorEmitter.on(type, handler);
90
+ return () => this.monitorEmitter.off(type, handler);
91
+ }
92
+ getTimeline(since) {
93
+ return since !== undefined ? this.timeline.filter((t) => t.cursor >= since) : this.timeline;
94
+ }
95
+ getCursor() {
96
+ return this.cursor;
97
+ }
98
+ getLastBookmark() {
99
+ const last = this.timeline[this.timeline.length - 1];
100
+ return last?.bookmark;
101
+ }
102
+ syncCursor(bookmark) {
103
+ if (!bookmark)
104
+ return;
105
+ const nextSeq = bookmark.seq + 1;
106
+ if (this.seq < nextSeq) {
107
+ this.seq = nextSeq;
108
+ }
109
+ const timelineCursor = this.timeline.length
110
+ ? this.timeline[this.timeline.length - 1].cursor + 1
111
+ : 0;
112
+ const nextCursor = Math.max(this.cursor, nextSeq, timelineCursor);
113
+ if (this.cursor < nextCursor) {
114
+ this.cursor = nextCursor;
115
+ }
116
+ }
117
+ reset() {
118
+ this.cursor = 0;
119
+ this.seq = 0;
120
+ this.timeline = [];
121
+ for (const set of this.subscribers.values()) {
122
+ set.clear();
123
+ }
124
+ this.controlEmitter.removeAllListeners();
125
+ this.monitorEmitter.removeAllListeners();
126
+ }
127
+ emit(channel, event) {
128
+ const bookmark = {
129
+ seq: this.seq++,
130
+ timestamp: Date.now(),
131
+ };
132
+ const eventWithChannel = { ...event, channel };
133
+ const eventWithBookmark = { ...eventWithChannel, bookmark };
134
+ const envelope = {
135
+ cursor: this.cursor++,
136
+ bookmark,
137
+ event: eventWithBookmark,
138
+ };
139
+ const timelineEntry = {
140
+ cursor: envelope.cursor,
141
+ bookmark,
142
+ event: envelope.event,
143
+ };
144
+ this.timeline.push(timelineEntry);
145
+ if (this.timeline.length > 10000) {
146
+ this.timeline = this.timeline.slice(-5000);
147
+ }
148
+ if (this.store && this.agentId) {
149
+ const isCritical = this.isCriticalEvent(event);
150
+ this.store.appendEvent(this.agentId, timelineEntry)
151
+ .then(() => {
152
+ // 成功后尝试重试之前失败的事件
153
+ if (this.failedEvents.length > 0) {
154
+ void this.retryFailedEvents();
155
+ }
156
+ })
157
+ .catch((err) => {
158
+ if (isCritical) {
159
+ // 关键事件失败:缓存到内存
160
+ this.failedEvents.push(timelineEntry);
161
+ if (this.failedEvents.length > this.MAX_FAILED_BUFFER) {
162
+ this.failedEvents = this.failedEvents.slice(-this.MAX_FAILED_BUFFER);
163
+ }
164
+ // 发送降级的内存 Monitor 事件(不持久化)
165
+ try {
166
+ this.monitorEmitter.emit('storage_failure', {
167
+ type: 'storage_failure',
168
+ severity: 'critical',
169
+ failedEvent: event.type,
170
+ bufferedCount: this.failedEvents.length,
171
+ error: err.message
172
+ });
173
+ }
174
+ catch {
175
+ // 降级事件发送失败也不阻塞
176
+ }
177
+ }
178
+ else {
179
+ // 非关键事件失败:仅记录日志
180
+ logger_1.logger.warn(`[EventBus] Failed to persist non-critical event: ${event.type}`, err);
181
+ }
182
+ });
183
+ }
184
+ return envelope;
185
+ }
186
+ isCriticalEvent(event) {
187
+ const criticalTypes = new Set([
188
+ 'tool:end',
189
+ 'done',
190
+ 'permission_decided',
191
+ 'agent_resumed',
192
+ 'state_changed',
193
+ 'breakpoint_changed',
194
+ 'error',
195
+ ]);
196
+ return criticalTypes.has(event.type);
197
+ }
198
+ async retryFailedEvents() {
199
+ if (!this.store || !this.agentId || this.failedEvents.length === 0)
200
+ return;
201
+ const toRetry = this.failedEvents.splice(0, 10);
202
+ for (const event of toRetry) {
203
+ try {
204
+ await this.store.appendEvent(this.agentId, event);
205
+ }
206
+ catch (err) {
207
+ this.failedEvents.unshift(event);
208
+ break;
209
+ }
210
+ }
211
+ }
212
+ getFailedEventCount() {
213
+ return this.failedEvents.length;
214
+ }
215
+ async flushFailedEvents() {
216
+ while (this.failedEvents.length > 0) {
217
+ await this.retryFailedEvents();
218
+ if (this.failedEvents.length > 0) {
219
+ await new Promise(resolve => setTimeout(resolve, 1000));
220
+ }
221
+ }
222
+ }
223
+ notifySubscribers(channel, envelope) {
224
+ const subscribers = this.subscribers.get(channel);
225
+ if (!subscribers)
226
+ return;
227
+ for (const subscriber of subscribers) {
228
+ if (subscriber.accepts(envelope)) {
229
+ subscriber.push(envelope);
230
+ }
231
+ }
232
+ }
233
+ async replayHistory(channel, subscriber, since) {
234
+ if (this.store && this.agentId) {
235
+ try {
236
+ const opts = { channel: channel, since };
237
+ for await (const entry of this.store.readEvents(this.agentId, opts)) {
238
+ const envelope = entry;
239
+ if (subscriber.accepts(envelope)) {
240
+ subscriber.push(envelope);
241
+ }
242
+ }
243
+ return;
244
+ }
245
+ catch (error) {
246
+ logger_1.logger.error('Failed to replay events from store:', error);
247
+ }
248
+ }
249
+ const past = this.timeline.filter((t) => {
250
+ if (t.event.channel !== channel)
251
+ return false;
252
+ if (!since)
253
+ return true;
254
+ return t.bookmark.seq > since.seq;
255
+ });
256
+ for (const entry of past) {
257
+ const envelope = entry;
258
+ if (subscriber.accepts(envelope)) {
259
+ subscriber.push(envelope);
260
+ }
261
+ }
262
+ }
263
+ iterableFor(channel, subscriber) {
264
+ const channels = Array.isArray(channel) ? channel : [channel];
265
+ const bus = this;
266
+ return {
267
+ [Symbol.asyncIterator]() {
268
+ return {
269
+ next: async () => {
270
+ const value = (await subscriber.next());
271
+ if (!value) {
272
+ for (const ch of channels)
273
+ bus.subscribers.get(ch).delete(subscriber);
274
+ return { done: true, value: undefined };
275
+ }
276
+ return { done: false, value };
277
+ },
278
+ return: async () => {
279
+ subscriber.close();
280
+ for (const ch of channels)
281
+ bus.subscribers.get(ch).delete(subscriber);
282
+ return { done: true, value: undefined };
283
+ },
284
+ };
285
+ },
286
+ };
287
+ }
288
+ }
289
+ exports.EventBus = EventBus;
290
+ class EventSubscriber {
291
+ constructor(kinds) {
292
+ this.kinds = kinds;
293
+ this.queue = [];
294
+ this.waiting = null;
295
+ this.closed = false;
296
+ }
297
+ accepts(envelope) {
298
+ if (!this.kinds || this.kinds.length === 0)
299
+ return true;
300
+ return this.kinds.includes(String(envelope.event.type));
301
+ }
302
+ push(envelope) {
303
+ if (this.closed)
304
+ return;
305
+ if (this.waiting) {
306
+ this.waiting(envelope);
307
+ this.waiting = null;
308
+ }
309
+ else {
310
+ this.queue.push(envelope);
311
+ }
312
+ }
313
+ async next() {
314
+ if (this.closed)
315
+ return null;
316
+ if (this.queue.length > 0)
317
+ return this.queue.shift();
318
+ return new Promise((resolve) => {
319
+ this.waiting = resolve;
320
+ });
321
+ }
322
+ close() {
323
+ this.closed = true;
324
+ if (this.waiting) {
325
+ this.waiting(null);
326
+ this.waiting = null;
327
+ }
328
+ }
329
+ }
@@ -23,6 +23,7 @@ export declare class FilePool {
23
23
  private readonly sandbox;
24
24
  private records;
25
25
  private watchers;
26
+ private watchPending;
26
27
  private readonly watchEnabled;
27
28
  private readonly onChange?;
28
29
  constructor(sandbox: Sandbox, opts?: FilePoolOptions);
@@ -33,6 +34,7 @@ export declare class FilePool {
33
34
  checkFreshness(path: string): Promise<FileFreshness>;
34
35
  getTrackedFiles(): string[];
35
36
  private ensureWatch;
37
+ private doWatch;
36
38
  getAccessedFiles(): Array<{
37
39
  path: string;
38
40
  mtime: number;