@shareai-lab/kode-sdk 1.0.0-beta.8 → 2.7.0

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 (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -273
  3. package/README.zh-CN.md +114 -0
  4. package/dist/core/agent/breakpoint-manager.d.ts +16 -0
  5. package/dist/core/agent/breakpoint-manager.js +36 -0
  6. package/dist/core/agent/message-queue.d.ts +26 -0
  7. package/dist/core/agent/message-queue.js +57 -0
  8. package/dist/core/agent/permission-manager.d.ts +9 -0
  9. package/dist/core/agent/permission-manager.js +32 -0
  10. package/dist/core/agent/todo-manager.d.ts +26 -0
  11. package/dist/core/agent/todo-manager.js +91 -0
  12. package/dist/core/agent/tool-runner.d.ts +9 -0
  13. package/dist/core/agent/tool-runner.js +45 -0
  14. package/dist/core/agent.d.ts +228 -62
  15. package/dist/core/agent.js +1890 -615
  16. package/dist/core/config.d.ts +10 -0
  17. package/dist/core/config.js +2 -0
  18. package/dist/core/context-manager.d.ts +82 -0
  19. package/dist/core/context-manager.js +241 -0
  20. package/dist/core/errors.d.ts +22 -0
  21. package/dist/core/errors.js +49 -0
  22. package/dist/core/events.d.ts +41 -10
  23. package/dist/core/events.js +270 -68
  24. package/dist/core/file-pool.d.ts +41 -0
  25. package/dist/core/file-pool.js +102 -0
  26. package/dist/core/hooks.d.ts +3 -3
  27. package/dist/core/hooks.js +1 -1
  28. package/dist/core/permission-modes.d.ts +31 -0
  29. package/dist/core/permission-modes.js +61 -0
  30. package/dist/core/pool.d.ts +56 -13
  31. package/dist/core/pool.js +244 -34
  32. package/dist/core/room.d.ts +2 -2
  33. package/dist/core/room.js +10 -10
  34. package/dist/core/scheduler.d.ts +30 -23
  35. package/dist/core/scheduler.js +42 -168
  36. package/dist/core/skills/index.d.ts +10 -0
  37. package/dist/core/skills/index.js +20 -0
  38. package/dist/core/skills/management-manager.d.ts +130 -0
  39. package/dist/core/skills/management-manager.js +557 -0
  40. package/dist/core/skills/manager.d.ts +47 -0
  41. package/dist/core/skills/manager.js +243 -0
  42. package/dist/core/skills/operation-queue.d.ts +87 -0
  43. package/dist/core/skills/operation-queue.js +113 -0
  44. package/dist/core/skills/sandbox-file-manager.d.ts +82 -0
  45. package/dist/core/skills/sandbox-file-manager.js +183 -0
  46. package/dist/core/skills/types.d.ts +120 -0
  47. package/dist/core/skills/types.js +9 -0
  48. package/dist/core/skills/xml-generator.d.ts +13 -0
  49. package/dist/core/skills/xml-generator.js +70 -0
  50. package/dist/core/template.d.ts +57 -0
  51. package/dist/core/template.js +35 -0
  52. package/dist/core/time-bridge.d.ts +18 -0
  53. package/dist/core/time-bridge.js +100 -0
  54. package/dist/core/todo.d.ts +34 -0
  55. package/dist/core/todo.js +89 -0
  56. package/dist/core/types.d.ts +311 -114
  57. package/dist/core/types.js +1 -12
  58. package/dist/index.d.ts +47 -9
  59. package/dist/index.js +108 -15
  60. package/dist/infra/db/postgres/postgres-store.d.ts +97 -0
  61. package/dist/infra/db/postgres/postgres-store.js +1073 -0
  62. package/dist/infra/db/sqlite/sqlite-store.d.ts +84 -0
  63. package/dist/infra/db/sqlite/sqlite-store.js +800 -0
  64. package/dist/infra/e2b/e2b-fs.d.ts +29 -0
  65. package/dist/infra/e2b/e2b-fs.js +128 -0
  66. package/dist/infra/e2b/e2b-sandbox.d.ts +37 -0
  67. package/dist/infra/e2b/e2b-sandbox.js +156 -0
  68. package/dist/infra/e2b/e2b-template.d.ts +24 -0
  69. package/dist/infra/e2b/e2b-template.js +105 -0
  70. package/dist/infra/e2b/index.d.ts +4 -0
  71. package/dist/infra/e2b/index.js +9 -0
  72. package/dist/infra/e2b/types.d.ts +46 -0
  73. package/dist/infra/e2b/types.js +2 -0
  74. package/dist/infra/provider.d.ts +17 -58
  75. package/dist/infra/provider.js +65 -116
  76. package/dist/infra/providers/anthropic.d.ts +42 -0
  77. package/dist/infra/providers/anthropic.js +308 -0
  78. package/dist/infra/providers/core/errors.d.ts +230 -0
  79. package/dist/infra/providers/core/errors.js +353 -0
  80. package/dist/infra/providers/core/fork.d.ts +106 -0
  81. package/dist/infra/providers/core/fork.js +418 -0
  82. package/dist/infra/providers/core/index.d.ts +10 -0
  83. package/dist/infra/providers/core/index.js +76 -0
  84. package/dist/infra/providers/core/logger.d.ts +186 -0
  85. package/dist/infra/providers/core/logger.js +191 -0
  86. package/dist/infra/providers/core/retry.d.ts +62 -0
  87. package/dist/infra/providers/core/retry.js +189 -0
  88. package/dist/infra/providers/core/usage.d.ts +151 -0
  89. package/dist/infra/providers/core/usage.js +376 -0
  90. package/dist/infra/providers/gemini.d.ts +49 -0
  91. package/dist/infra/providers/gemini.js +493 -0
  92. package/dist/infra/providers/index.d.ts +25 -0
  93. package/dist/infra/providers/index.js +83 -0
  94. package/dist/infra/providers/openai.d.ts +123 -0
  95. package/dist/infra/providers/openai.js +662 -0
  96. package/dist/infra/providers/types.d.ts +334 -0
  97. package/dist/infra/providers/types.js +20 -0
  98. package/dist/infra/providers/utils.d.ts +53 -0
  99. package/dist/infra/providers/utils.js +400 -0
  100. package/dist/infra/sandbox-factory.d.ts +13 -0
  101. package/dist/infra/sandbox-factory.js +30 -0
  102. package/dist/infra/sandbox.d.ts +35 -6
  103. package/dist/infra/sandbox.js +174 -8
  104. package/dist/infra/store/factory.d.ts +45 -0
  105. package/dist/infra/store/factory.js +80 -0
  106. package/dist/infra/store/index.d.ts +3 -0
  107. package/dist/infra/store/index.js +26 -0
  108. package/dist/infra/store/json-store.d.ts +67 -0
  109. package/dist/infra/store/json-store.js +606 -0
  110. package/dist/infra/store/types.d.ts +342 -0
  111. package/dist/infra/store/types.js +2 -0
  112. package/dist/infra/store.d.ts +12 -32
  113. package/dist/infra/store.js +27 -130
  114. package/dist/tools/bash_kill/index.d.ts +1 -0
  115. package/dist/tools/bash_kill/index.js +35 -0
  116. package/dist/tools/bash_kill/prompt.d.ts +2 -0
  117. package/dist/tools/bash_kill/prompt.js +14 -0
  118. package/dist/tools/bash_logs/index.d.ts +1 -0
  119. package/dist/tools/bash_logs/index.js +40 -0
  120. package/dist/tools/bash_logs/prompt.d.ts +2 -0
  121. package/dist/tools/bash_logs/prompt.js +14 -0
  122. package/dist/tools/bash_run/index.d.ts +16 -0
  123. package/dist/tools/bash_run/index.js +61 -0
  124. package/dist/tools/bash_run/prompt.d.ts +2 -0
  125. package/dist/tools/bash_run/prompt.js +18 -0
  126. package/dist/tools/builtin.d.ts +7 -13
  127. package/dist/tools/builtin.js +19 -90
  128. package/dist/tools/define.d.ts +101 -0
  129. package/dist/tools/define.js +214 -0
  130. package/dist/tools/fs_edit/index.d.ts +1 -0
  131. package/dist/tools/fs_edit/index.js +62 -0
  132. package/dist/tools/fs_edit/prompt.d.ts +2 -0
  133. package/dist/tools/fs_edit/prompt.js +15 -0
  134. package/dist/tools/fs_glob/index.d.ts +1 -0
  135. package/dist/tools/fs_glob/index.js +40 -0
  136. package/dist/tools/fs_glob/prompt.d.ts +2 -0
  137. package/dist/tools/fs_glob/prompt.js +15 -0
  138. package/dist/tools/fs_grep/index.d.ts +1 -0
  139. package/dist/tools/fs_grep/index.js +66 -0
  140. package/dist/tools/fs_grep/prompt.d.ts +2 -0
  141. package/dist/tools/fs_grep/prompt.js +16 -0
  142. package/dist/tools/fs_multi_edit/index.d.ts +1 -0
  143. package/dist/tools/fs_multi_edit/index.js +106 -0
  144. package/dist/tools/fs_multi_edit/prompt.d.ts +2 -0
  145. package/dist/tools/fs_multi_edit/prompt.js +16 -0
  146. package/dist/tools/fs_read/index.d.ts +1 -0
  147. package/dist/tools/fs_read/index.js +40 -0
  148. package/dist/tools/fs_read/prompt.d.ts +2 -0
  149. package/dist/tools/fs_read/prompt.js +16 -0
  150. package/dist/tools/fs_write/index.d.ts +1 -0
  151. package/dist/tools/fs_write/index.js +40 -0
  152. package/dist/tools/fs_write/prompt.d.ts +2 -0
  153. package/dist/tools/fs_write/prompt.js +15 -0
  154. package/dist/tools/index.d.ts +11 -0
  155. package/dist/tools/index.js +61 -0
  156. package/dist/tools/mcp.d.ts +69 -0
  157. package/dist/tools/mcp.js +185 -0
  158. package/dist/tools/registry.d.ts +29 -0
  159. package/dist/tools/registry.js +26 -0
  160. package/dist/tools/scripts.d.ts +22 -0
  161. package/dist/tools/scripts.js +205 -0
  162. package/dist/tools/skills.d.ts +20 -0
  163. package/dist/tools/skills.js +115 -0
  164. package/dist/tools/task_run/index.d.ts +7 -0
  165. package/dist/tools/task_run/index.js +58 -0
  166. package/dist/tools/task_run/prompt.d.ts +5 -0
  167. package/dist/tools/task_run/prompt.js +25 -0
  168. package/dist/tools/todo_read/index.d.ts +1 -0
  169. package/dist/tools/todo_read/index.js +29 -0
  170. package/dist/tools/todo_read/prompt.d.ts +2 -0
  171. package/dist/tools/todo_read/prompt.js +18 -0
  172. package/dist/tools/todo_write/index.d.ts +1 -0
  173. package/dist/tools/todo_write/index.js +42 -0
  174. package/dist/tools/todo_write/prompt.d.ts +2 -0
  175. package/dist/tools/todo_write/prompt.js +23 -0
  176. package/dist/tools/tool.d.ts +43 -0
  177. package/dist/tools/tool.js +211 -0
  178. package/dist/tools/toolkit.d.ts +69 -0
  179. package/dist/tools/toolkit.js +98 -0
  180. package/dist/tools/type-inference.d.ts +127 -0
  181. package/dist/tools/type-inference.js +207 -0
  182. package/dist/utils/agent-id.d.ts +1 -0
  183. package/dist/utils/agent-id.js +28 -0
  184. package/dist/utils/logger.d.ts +15 -0
  185. package/dist/utils/logger.js +44 -0
  186. package/dist/utils/session-id.js +16 -16
  187. package/package.json +35 -11
  188. package/dist/tools/bash.d.ts +0 -63
  189. package/dist/tools/bash.js +0 -92
  190. package/dist/tools/fs.d.ts +0 -96
  191. package/dist/tools/fs.js +0 -100
  192. package/dist/tools/task.d.ts +0 -38
  193. package/dist/tools/task.js +0 -45
@@ -1,89 +1,289 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EventBus = void 0;
4
- const types_1 = require("../core/types");
5
4
  const events_1 = require("events");
6
- const crypto_1 = require("crypto");
7
- class EventBus extends events_1.EventEmitter {
5
+ const logger_1 = require("../utils/logger");
6
+ class EventBus {
8
7
  constructor() {
9
- super(...arguments);
10
8
  this.cursor = 0;
9
+ this.seq = 0;
11
10
  this.timeline = [];
12
- this.subscribers = new Set();
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
+ });
13
25
  }
14
- setStore(store, sessionId) {
26
+ setStore(store, agentId) {
15
27
  this.store = store;
16
- this.sessionId = sessionId;
17
- }
18
- emitEvent(event) {
19
- const cursor = this.cursor++;
20
- const eventId = (0, crypto_1.randomUUID)();
21
- const timestamp = Date.now();
22
- const fullEvent = { ...event, cursor, eventId, timestamp };
23
- const timeline = { cursor, event: fullEvent };
24
- this.timeline.push(timeline);
25
- // Memory management: keep only last 10k events in memory
26
- if (this.timeline.length > 10000) {
27
- this.timeline = this.timeline.slice(-5000);
28
- }
29
- // Persist to store if configured
30
- if (this.store && this.sessionId) {
31
- this.store.appendEvent(this.sessionId, timeline).catch((err) => {
32
- // Log error but don't block event emission
33
- console.error('Failed to persist event:', err);
34
- });
35
- }
36
- // Notify all subscribers
37
- for (const subscriber of this.subscribers) {
38
- if (subscriber.accepts(fullEvent.type)) {
39
- subscriber.push(fullEvent);
40
- }
41
- }
42
- // Emit control plane events
43
- this.emit(event.type, fullEvent);
44
- return cursor;
45
- }
46
- subscribe(opts) {
47
- const subscriber = new EventSubscriber(opts?.kinds || types_1.MINIMAL_EVENT_KINDS);
48
- this.subscribers.add(subscriber);
49
- // Replay past events if since is specified
50
- if (opts?.since !== undefined) {
51
- const past = this.timeline.filter((t) => t.cursor >= opts.since);
52
- for (const t of past) {
53
- if (subscriber.accepts(t.event.type)) {
54
- subscriber.push(t.event);
55
- }
56
- }
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);
57
52
  }
58
- const self = this;
53
+ const bus = this;
59
54
  return {
60
- [Symbol.asyncIterator]: () => ({
61
- next: async () => {
62
- const event = await subscriber.next();
63
- if (!event) {
64
- self.subscribers.delete(subscriber);
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);
65
68
  return { done: true, value: undefined };
66
- }
67
- return { done: false, value: event };
68
- },
69
- return: async () => {
70
- subscriber.close();
71
- self.subscribers.delete(subscriber);
72
- return { done: true, value: undefined };
73
- },
74
- }),
69
+ },
70
+ };
71
+ },
75
72
  };
76
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
+ }
77
92
  getTimeline(since) {
78
93
  return since !== undefined ? this.timeline.filter((t) => t.cursor >= since) : this.timeline;
79
94
  }
80
95
  getCursor() {
81
96
  return this.cursor;
82
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
+ }
83
117
  reset() {
84
118
  this.cursor = 0;
119
+ this.seq = 0;
85
120
  this.timeline = [];
86
- this.subscribers.clear();
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
+ };
87
287
  }
88
288
  }
89
289
  exports.EventBus = EventBus;
@@ -94,18 +294,20 @@ class EventSubscriber {
94
294
  this.waiting = null;
95
295
  this.closed = false;
96
296
  }
97
- accepts(kind) {
98
- return this.kinds.includes(kind);
297
+ accepts(envelope) {
298
+ if (!this.kinds || this.kinds.length === 0)
299
+ return true;
300
+ return this.kinds.includes(String(envelope.event.type));
99
301
  }
100
- push(event) {
302
+ push(envelope) {
101
303
  if (this.closed)
102
304
  return;
103
305
  if (this.waiting) {
104
- this.waiting(event);
306
+ this.waiting(envelope);
105
307
  this.waiting = null;
106
308
  }
107
309
  else {
108
- this.queue.push(event);
310
+ this.queue.push(envelope);
109
311
  }
110
312
  }
111
313
  async next() {
@@ -0,0 +1,41 @@
1
+ import { Sandbox } from '../infra/sandbox';
2
+ export interface FileRecord {
3
+ path: string;
4
+ lastRead?: number;
5
+ lastEdit?: number;
6
+ lastReadMtime?: number;
7
+ lastKnownMtime?: number;
8
+ }
9
+ export interface FileFreshness {
10
+ isFresh: boolean;
11
+ lastRead?: number;
12
+ lastEdit?: number;
13
+ currentMtime?: number;
14
+ }
15
+ interface FilePoolOptions {
16
+ watch?: boolean;
17
+ onChange?: (event: {
18
+ path: string;
19
+ mtime: number;
20
+ }) => void;
21
+ }
22
+ export declare class FilePool {
23
+ private readonly sandbox;
24
+ private records;
25
+ private watchers;
26
+ private readonly watchEnabled;
27
+ private readonly onChange?;
28
+ constructor(sandbox: Sandbox, opts?: FilePoolOptions);
29
+ private getMtime;
30
+ recordRead(path: string): Promise<void>;
31
+ recordEdit(path: string): Promise<void>;
32
+ validateWrite(path: string): Promise<FileFreshness>;
33
+ checkFreshness(path: string): Promise<FileFreshness>;
34
+ getTrackedFiles(): string[];
35
+ private ensureWatch;
36
+ getAccessedFiles(): Array<{
37
+ path: string;
38
+ mtime: number;
39
+ }>;
40
+ }
41
+ export {};
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilePool = void 0;
4
+ const logger_1 = require("../utils/logger");
5
+ class FilePool {
6
+ constructor(sandbox, opts) {
7
+ this.sandbox = sandbox;
8
+ this.records = new Map();
9
+ this.watchers = new Map();
10
+ this.watchEnabled = opts?.watch ?? true;
11
+ this.onChange = opts?.onChange;
12
+ }
13
+ async getMtime(path) {
14
+ try {
15
+ const stat = await this.sandbox.fs.stat(path);
16
+ return stat.mtimeMs;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ async recordRead(path) {
23
+ const resolved = this.sandbox.fs.resolve(path);
24
+ const record = this.records.get(resolved) || { path: resolved };
25
+ record.lastRead = Date.now();
26
+ record.lastReadMtime = await this.getMtime(resolved);
27
+ record.lastKnownMtime = record.lastReadMtime;
28
+ this.records.set(resolved, record);
29
+ await this.ensureWatch(resolved);
30
+ }
31
+ async recordEdit(path) {
32
+ const resolved = this.sandbox.fs.resolve(path);
33
+ const record = this.records.get(resolved) || { path: resolved };
34
+ record.lastEdit = Date.now();
35
+ record.lastKnownMtime = await this.getMtime(resolved);
36
+ this.records.set(resolved, record);
37
+ await this.ensureWatch(resolved);
38
+ }
39
+ async validateWrite(path) {
40
+ const resolved = this.sandbox.fs.resolve(path);
41
+ const record = this.records.get(resolved);
42
+ const currentMtime = await this.getMtime(resolved);
43
+ if (!record) {
44
+ return { isFresh: true, currentMtime };
45
+ }
46
+ const isFresh = record.lastRead !== undefined &&
47
+ (currentMtime === undefined || record.lastReadMtime === undefined || currentMtime === record.lastReadMtime);
48
+ return {
49
+ isFresh,
50
+ lastRead: record.lastRead,
51
+ lastEdit: record.lastEdit,
52
+ currentMtime,
53
+ };
54
+ }
55
+ async checkFreshness(path) {
56
+ const resolved = this.sandbox.fs.resolve(path);
57
+ const record = this.records.get(resolved);
58
+ const currentMtime = await this.getMtime(resolved);
59
+ if (!record) {
60
+ return { isFresh: false, currentMtime };
61
+ }
62
+ const isFresh = record.lastRead !== undefined &&
63
+ (currentMtime === undefined || record.lastKnownMtime === undefined || currentMtime === record.lastKnownMtime);
64
+ return {
65
+ isFresh,
66
+ lastRead: record.lastRead,
67
+ lastEdit: record.lastEdit,
68
+ currentMtime,
69
+ };
70
+ }
71
+ getTrackedFiles() {
72
+ return Array.from(this.records.keys());
73
+ }
74
+ async ensureWatch(path) {
75
+ if (!this.watchEnabled)
76
+ return;
77
+ if (!this.sandbox.watchFiles)
78
+ return;
79
+ if (this.watchers.has(path))
80
+ return;
81
+ try {
82
+ const id = await this.sandbox.watchFiles([path], (event) => {
83
+ const record = this.records.get(path);
84
+ if (record) {
85
+ record.lastKnownMtime = event.mtimeMs;
86
+ }
87
+ this.onChange?.({ path, mtime: event.mtimeMs });
88
+ });
89
+ this.watchers.set(path, id);
90
+ }
91
+ catch (err) {
92
+ // 记录 watch 失败,但不中断流程
93
+ logger_1.logger.warn(`[FilePool] Failed to watch file: ${path}`, err);
94
+ }
95
+ }
96
+ getAccessedFiles() {
97
+ return Array.from(this.records.values())
98
+ .filter((r) => r.lastKnownMtime !== undefined)
99
+ .map((r) => ({ path: r.path, mtime: r.lastKnownMtime }));
100
+ }
101
+ }
102
+ exports.FilePool = FilePool;
@@ -1,10 +1,10 @@
1
1
  import { ToolCall, ToolOutcome, HookDecision, PostHookResult, ToolContext } from '../core/types';
2
- import { ProviderResponse } from '../infra/provider';
2
+ import { ModelResponse } from '../infra/provider';
3
3
  export interface Hooks {
4
4
  preToolUse?: (call: ToolCall, ctx: ToolContext) => HookDecision | Promise<HookDecision>;
5
5
  postToolUse?: (outcome: ToolOutcome, ctx: ToolContext) => PostHookResult | Promise<PostHookResult>;
6
6
  preModel?: (request: any) => void | Promise<void>;
7
- postModel?: (response: ProviderResponse) => void | Promise<void>;
7
+ postModel?: (response: ModelResponse) => void | Promise<void>;
8
8
  messagesChanged?: (snapshot: any) => void | Promise<void>;
9
9
  }
10
10
  export interface RegisteredHook {
@@ -18,6 +18,6 @@ export declare class HookManager {
18
18
  runPreToolUse(call: ToolCall, ctx: ToolContext): Promise<HookDecision>;
19
19
  runPostToolUse(outcome: ToolOutcome, ctx: ToolContext): Promise<ToolOutcome>;
20
20
  runPreModel(request: any): Promise<void>;
21
- runPostModel(response: ProviderResponse): Promise<void>;
21
+ runPostModel(response: ModelResponse): Promise<void>;
22
22
  runMessagesChanged(snapshot: any): Promise<void>;
23
23
  }
@@ -34,7 +34,7 @@ class HookManager {
34
34
  for (const { hooks } of this.hooks) {
35
35
  if (hooks.postToolUse) {
36
36
  const result = await hooks.postToolUse(current, ctx);
37
- if (result) {
37
+ if (result && typeof result === 'object') {
38
38
  if ('replace' in result) {
39
39
  current = result.replace;
40
40
  }
@@ -0,0 +1,31 @@
1
+ import { PermissionConfig } from './template';
2
+ import { ToolDescriptor } from '../tools/registry';
3
+ export type PermissionDecision = 'allow' | 'deny' | 'ask';
4
+ export interface PermissionEvaluationContext {
5
+ toolName: string;
6
+ descriptor?: ToolDescriptor;
7
+ config: PermissionConfig;
8
+ }
9
+ export type PermissionModeHandler = (ctx: PermissionEvaluationContext) => PermissionDecision;
10
+ export interface SerializedPermissionMode {
11
+ name: string;
12
+ builtIn: boolean;
13
+ }
14
+ export declare class PermissionModeRegistry {
15
+ private handlers;
16
+ private customModes;
17
+ register(mode: string, handler: PermissionModeHandler, isBuiltIn?: boolean): void;
18
+ get(mode: string): PermissionModeHandler | undefined;
19
+ list(): string[];
20
+ /**
21
+ * 序列化权限模式配置
22
+ * 仅序列化自定义模式的名称,内置模式在 Resume 时自动恢复
23
+ */
24
+ serialize(): SerializedPermissionMode[];
25
+ /**
26
+ * 验证序列化的权限模式是否可恢复
27
+ * 返回缺失的自定义模式列表
28
+ */
29
+ validateRestore(serialized: SerializedPermissionMode[]): string[];
30
+ }
31
+ export declare const permissionModes: PermissionModeRegistry;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.permissionModes = exports.PermissionModeRegistry = void 0;
4
+ class PermissionModeRegistry {
5
+ constructor() {
6
+ this.handlers = new Map();
7
+ this.customModes = new Set();
8
+ }
9
+ register(mode, handler, isBuiltIn = false) {
10
+ this.handlers.set(mode, handler);
11
+ if (!isBuiltIn) {
12
+ this.customModes.add(mode);
13
+ }
14
+ }
15
+ get(mode) {
16
+ return this.handlers.get(mode);
17
+ }
18
+ list() {
19
+ return Array.from(this.handlers.keys());
20
+ }
21
+ /**
22
+ * 序列化权限模式配置
23
+ * 仅序列化自定义模式的名称,内置模式在 Resume 时自动恢复
24
+ */
25
+ serialize() {
26
+ return Array.from(this.handlers.keys()).map(name => ({
27
+ name,
28
+ builtIn: !this.customModes.has(name)
29
+ }));
30
+ }
31
+ /**
32
+ * 验证序列化的权限模式是否可恢复
33
+ * 返回缺失的自定义模式列表
34
+ */
35
+ validateRestore(serialized) {
36
+ const missing = [];
37
+ for (const mode of serialized) {
38
+ if (!mode.builtIn && !this.handlers.has(mode.name)) {
39
+ missing.push(mode.name);
40
+ }
41
+ }
42
+ return missing;
43
+ }
44
+ }
45
+ exports.PermissionModeRegistry = PermissionModeRegistry;
46
+ exports.permissionModes = new PermissionModeRegistry();
47
+ const MUTATING_ACCESS = new Set(['write', 'execute', 'manage', 'mutate']);
48
+ // 内置模式
49
+ exports.permissionModes.register('auto', () => 'allow', true);
50
+ exports.permissionModes.register('approval', () => 'ask', true);
51
+ exports.permissionModes.register('readonly', (ctx) => {
52
+ const metadata = ctx.descriptor?.metadata || {};
53
+ if (metadata.mutates === true)
54
+ return 'deny';
55
+ if (metadata.mutates === false)
56
+ return 'allow';
57
+ const access = typeof metadata.access === 'string' ? metadata.access.toLowerCase() : undefined;
58
+ if (access && MUTATING_ACCESS.has(access))
59
+ return 'deny';
60
+ return 'ask';
61
+ }, true);