kode-sdk 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 (169) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +74 -0
  3. package/dist/core/agent/breakpoint-manager.d.ts +16 -0
  4. package/dist/core/agent/breakpoint-manager.js +36 -0
  5. package/dist/core/agent/message-queue.d.ts +26 -0
  6. package/dist/core/agent/message-queue.js +47 -0
  7. package/dist/core/agent/permission-manager.d.ts +9 -0
  8. package/dist/core/agent/permission-manager.js +32 -0
  9. package/dist/core/agent/todo-manager.d.ts +26 -0
  10. package/dist/core/agent/todo-manager.js +91 -0
  11. package/dist/core/agent/tool-runner.d.ts +9 -0
  12. package/dist/core/agent/tool-runner.js +45 -0
  13. package/dist/core/agent.d.ts +271 -0
  14. package/dist/core/agent.js +2334 -0
  15. package/dist/core/checkpointer.d.ts +96 -0
  16. package/dist/core/checkpointer.js +57 -0
  17. package/dist/core/checkpointers/file.d.ts +20 -0
  18. package/dist/core/checkpointers/file.js +153 -0
  19. package/dist/core/checkpointers/index.d.ts +3 -0
  20. package/dist/core/checkpointers/index.js +9 -0
  21. package/dist/core/checkpointers/redis.d.ts +35 -0
  22. package/dist/core/checkpointers/redis.js +113 -0
  23. package/dist/core/compression/ai-strategy.d.ts +53 -0
  24. package/dist/core/compression/ai-strategy.js +298 -0
  25. package/dist/core/compression/index.d.ts +12 -0
  26. package/dist/core/compression/index.js +27 -0
  27. package/dist/core/compression/prompts.d.ts +35 -0
  28. package/dist/core/compression/prompts.js +114 -0
  29. package/dist/core/compression/simple-strategy.d.ts +44 -0
  30. package/dist/core/compression/simple-strategy.js +240 -0
  31. package/dist/core/compression/token-estimator.d.ts +42 -0
  32. package/dist/core/compression/token-estimator.js +121 -0
  33. package/dist/core/compression/types.d.ts +140 -0
  34. package/dist/core/compression/types.js +9 -0
  35. package/dist/core/config.d.ts +10 -0
  36. package/dist/core/config.js +2 -0
  37. package/dist/core/context-manager.d.ts +115 -0
  38. package/dist/core/context-manager.js +107 -0
  39. package/dist/core/errors.d.ts +6 -0
  40. package/dist/core/errors.js +17 -0
  41. package/dist/core/events.d.ts +49 -0
  42. package/dist/core/events.js +312 -0
  43. package/dist/core/file-pool.d.ts +43 -0
  44. package/dist/core/file-pool.js +120 -0
  45. package/dist/core/hooks.d.ts +23 -0
  46. package/dist/core/hooks.js +71 -0
  47. package/dist/core/permission-modes.d.ts +31 -0
  48. package/dist/core/permission-modes.js +61 -0
  49. package/dist/core/pool.d.ts +31 -0
  50. package/dist/core/pool.js +87 -0
  51. package/dist/core/room.d.ts +15 -0
  52. package/dist/core/room.js +57 -0
  53. package/dist/core/scheduler.d.ts +33 -0
  54. package/dist/core/scheduler.js +58 -0
  55. package/dist/core/template.d.ts +69 -0
  56. package/dist/core/template.js +35 -0
  57. package/dist/core/time-bridge.d.ts +18 -0
  58. package/dist/core/time-bridge.js +100 -0
  59. package/dist/core/todo.d.ts +34 -0
  60. package/dist/core/todo.js +89 -0
  61. package/dist/core/types.d.ts +380 -0
  62. package/dist/core/types.js +3 -0
  63. package/dist/index.d.ts +51 -0
  64. package/dist/index.js +147 -0
  65. package/dist/infra/provider.d.ts +144 -0
  66. package/dist/infra/provider.js +294 -0
  67. package/dist/infra/sandbox-factory.d.ts +10 -0
  68. package/dist/infra/sandbox-factory.js +21 -0
  69. package/dist/infra/sandbox.d.ts +87 -0
  70. package/dist/infra/sandbox.js +255 -0
  71. package/dist/infra/store.d.ts +154 -0
  72. package/dist/infra/store.js +584 -0
  73. package/dist/skills/index.d.ts +12 -0
  74. package/dist/skills/index.js +36 -0
  75. package/dist/skills/injector.d.ts +29 -0
  76. package/dist/skills/injector.js +96 -0
  77. package/dist/skills/loader.d.ts +59 -0
  78. package/dist/skills/loader.js +215 -0
  79. package/dist/skills/manager.d.ts +85 -0
  80. package/dist/skills/manager.js +221 -0
  81. package/dist/skills/parser.d.ts +40 -0
  82. package/dist/skills/parser.js +107 -0
  83. package/dist/skills/types.d.ts +107 -0
  84. package/dist/skills/types.js +7 -0
  85. package/dist/skills/validator.d.ts +30 -0
  86. package/dist/skills/validator.js +121 -0
  87. package/dist/store.d.ts +1 -0
  88. package/dist/store.js +5 -0
  89. package/dist/tools/bash_kill/index.d.ts +1 -0
  90. package/dist/tools/bash_kill/index.js +35 -0
  91. package/dist/tools/bash_kill/prompt.d.ts +2 -0
  92. package/dist/tools/bash_kill/prompt.js +14 -0
  93. package/dist/tools/bash_logs/index.d.ts +1 -0
  94. package/dist/tools/bash_logs/index.js +40 -0
  95. package/dist/tools/bash_logs/prompt.d.ts +2 -0
  96. package/dist/tools/bash_logs/prompt.js +14 -0
  97. package/dist/tools/bash_run/index.d.ts +16 -0
  98. package/dist/tools/bash_run/index.js +61 -0
  99. package/dist/tools/bash_run/prompt.d.ts +2 -0
  100. package/dist/tools/bash_run/prompt.js +18 -0
  101. package/dist/tools/builtin.d.ts +9 -0
  102. package/dist/tools/builtin.js +27 -0
  103. package/dist/tools/define.d.ts +101 -0
  104. package/dist/tools/define.js +214 -0
  105. package/dist/tools/fs_edit/index.d.ts +1 -0
  106. package/dist/tools/fs_edit/index.js +62 -0
  107. package/dist/tools/fs_edit/prompt.d.ts +2 -0
  108. package/dist/tools/fs_edit/prompt.js +15 -0
  109. package/dist/tools/fs_glob/index.d.ts +1 -0
  110. package/dist/tools/fs_glob/index.js +60 -0
  111. package/dist/tools/fs_glob/prompt.d.ts +2 -0
  112. package/dist/tools/fs_glob/prompt.js +18 -0
  113. package/dist/tools/fs_grep/index.d.ts +1 -0
  114. package/dist/tools/fs_grep/index.js +66 -0
  115. package/dist/tools/fs_grep/prompt.d.ts +2 -0
  116. package/dist/tools/fs_grep/prompt.js +16 -0
  117. package/dist/tools/fs_multi_edit/index.d.ts +1 -0
  118. package/dist/tools/fs_multi_edit/index.js +106 -0
  119. package/dist/tools/fs_multi_edit/prompt.d.ts +2 -0
  120. package/dist/tools/fs_multi_edit/prompt.js +16 -0
  121. package/dist/tools/fs_read/index.d.ts +1 -0
  122. package/dist/tools/fs_read/index.js +40 -0
  123. package/dist/tools/fs_read/prompt.d.ts +2 -0
  124. package/dist/tools/fs_read/prompt.js +16 -0
  125. package/dist/tools/fs_rm/index.d.ts +1 -0
  126. package/dist/tools/fs_rm/index.js +41 -0
  127. package/dist/tools/fs_rm/prompt.d.ts +2 -0
  128. package/dist/tools/fs_rm/prompt.js +14 -0
  129. package/dist/tools/fs_write/index.d.ts +1 -0
  130. package/dist/tools/fs_write/index.js +40 -0
  131. package/dist/tools/fs_write/prompt.d.ts +2 -0
  132. package/dist/tools/fs_write/prompt.js +15 -0
  133. package/dist/tools/index.d.ts +9 -0
  134. package/dist/tools/index.js +56 -0
  135. package/dist/tools/mcp.d.ts +73 -0
  136. package/dist/tools/mcp.js +198 -0
  137. package/dist/tools/registry.d.ts +29 -0
  138. package/dist/tools/registry.js +26 -0
  139. package/dist/tools/skill_activate/index.d.ts +5 -0
  140. package/dist/tools/skill_activate/index.js +63 -0
  141. package/dist/tools/skill_list/index.d.ts +5 -0
  142. package/dist/tools/skill_list/index.js +48 -0
  143. package/dist/tools/skill_resource/index.d.ts +5 -0
  144. package/dist/tools/skill_resource/index.js +82 -0
  145. package/dist/tools/task_run/index.d.ts +7 -0
  146. package/dist/tools/task_run/index.js +60 -0
  147. package/dist/tools/task_run/prompt.d.ts +5 -0
  148. package/dist/tools/task_run/prompt.js +29 -0
  149. package/dist/tools/todo_read/index.d.ts +1 -0
  150. package/dist/tools/todo_read/index.js +29 -0
  151. package/dist/tools/todo_read/prompt.d.ts +2 -0
  152. package/dist/tools/todo_read/prompt.js +18 -0
  153. package/dist/tools/todo_write/index.d.ts +1 -0
  154. package/dist/tools/todo_write/index.js +42 -0
  155. package/dist/tools/todo_write/prompt.d.ts +2 -0
  156. package/dist/tools/todo_write/prompt.js +23 -0
  157. package/dist/tools/tool.d.ts +43 -0
  158. package/dist/tools/tool.js +104 -0
  159. package/dist/tools/toolkit.d.ts +69 -0
  160. package/dist/tools/toolkit.js +98 -0
  161. package/dist/tools/type-inference.d.ts +127 -0
  162. package/dist/tools/type-inference.js +207 -0
  163. package/dist/utils/agent-id.d.ts +1 -0
  164. package/dist/utils/agent-id.js +28 -0
  165. package/dist/utils/session-id.d.ts +21 -0
  166. package/dist/utils/session-id.js +64 -0
  167. package/dist/utils/unicode.d.ts +17 -0
  168. package/dist/utils/unicode.js +62 -0
  169. package/package.json +117 -0
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContextManager = void 0;
4
+ const compression_1 = require("./compression");
5
+ // ============================================================================
6
+ // ContextManager 实现
7
+ // ============================================================================
8
+ /**
9
+ * ContextManager v3 - 支持多种压缩策略的上下文管理器
10
+ *
11
+ * 职责:
12
+ * 1. 分析上下文使用情况(token 估算)
13
+ * 2. 根据配置选择压缩策略(simple 或 ai)
14
+ * 3. 委托策略执行压缩
15
+ * 4. 提供历史记录查询接口
16
+ *
17
+ * 策略模式:
18
+ * - simple: 简单截断(保留最近消息 + 文本摘要)
19
+ * - ai: AI 智能压缩(使用 LLM 生成结构化摘要)
20
+ */
21
+ class ContextManager {
22
+ constructor(store, agentId, opts) {
23
+ this.store = store;
24
+ this.agentId = agentId;
25
+ // 根据配置选择策略
26
+ this.strategy = this.createStrategy(opts);
27
+ }
28
+ /**
29
+ * 创建压缩策略
30
+ *
31
+ * 默认值基于 Claude 官方最佳实践:
32
+ * - maxTokens: 64K = 50% 的 128K 上下文窗口(对标 Claude 默认 100K/200K)
33
+ * - compressToTokens: 40K = 约 62.5% 的 maxTokens
34
+ * @see https://platform.claude.com/cookbook/tool-use-automatic-context-compaction
35
+ */
36
+ createStrategy(opts) {
37
+ const maxTokens = opts?.maxTokens ?? 64000;
38
+ const compressToTokens = opts?.compressToTokens ?? 40000;
39
+ // 如果选择 AI 策略且提供了必要配置
40
+ if (opts?.algorithm === 'ai' && opts?.modelConfig && opts?.modelFactory) {
41
+ return new compression_1.AICompressionStrategy({
42
+ store: this.store,
43
+ agentId: this.agentId,
44
+ maxTokens,
45
+ compressToTokens,
46
+ modelConfig: opts.modelConfig,
47
+ modelFactory: opts.modelFactory,
48
+ summaryModelConfig: opts.summaryModelConfig,
49
+ summaryPrompt: opts.summaryPrompt,
50
+ preserveRecentTurns: opts.preserveRecentTurns,
51
+ eventEmitter: opts.eventEmitter,
52
+ });
53
+ }
54
+ // 默认使用简单策略
55
+ return new compression_1.SimpleCompressionStrategy({
56
+ store: this.store,
57
+ agentId: this.agentId,
58
+ maxTokens,
59
+ compressToTokens,
60
+ compressionModel: opts?.compressionModel,
61
+ compressionPrompt: opts?.compressionPrompt,
62
+ eventEmitter: opts?.eventEmitter,
63
+ });
64
+ }
65
+ /**
66
+ * 分析上下文使用情况(粗略的 token 估算)
67
+ *
68
+ * 方法签名保持不变,委托给策略执行
69
+ */
70
+ analyze(messages) {
71
+ return this.strategy.analyze(messages);
72
+ }
73
+ /**
74
+ * 压缩上下文并保存历史
75
+ *
76
+ * 方法签名保持不变,委托给策略执行
77
+ *
78
+ * 流程:
79
+ * 1. 保存 HistoryWindow(压缩前的完整快照)
80
+ * 2. 执行压缩(根据策略不同,方式不同)
81
+ * 3. 保存 CompressionRecord(压缩元信息)
82
+ * 4. 保存重要文件快照(如果有 FilePool)
83
+ * 5. 返回压缩结果
84
+ */
85
+ async compress(messages, events, filePool, sandbox) {
86
+ return this.strategy.compress(messages, events, filePool, sandbox);
87
+ }
88
+ /**
89
+ * 恢复历史窗口(用于审计或调试)
90
+ */
91
+ async loadHistory() {
92
+ return await this.store.loadHistoryWindows(this.agentId);
93
+ }
94
+ /**
95
+ * 加载压缩记录
96
+ */
97
+ async loadCompressions() {
98
+ return await this.store.loadCompressionRecords(this.agentId);
99
+ }
100
+ /**
101
+ * 加载恢复的文件
102
+ */
103
+ async loadRecoveredFiles() {
104
+ return await this.store.loadRecoveredFiles(this.agentId);
105
+ }
106
+ }
107
+ exports.ContextManager = ContextManager;
@@ -0,0 +1,6 @@
1
+ export type ResumeErrorCode = 'SESSION_NOT_FOUND' | 'AGENT_NOT_FOUND' | 'TEMPLATE_NOT_FOUND' | 'TEMPLATE_VERSION_MISMATCH' | 'SANDBOX_INIT_FAILED' | 'CORRUPTED_DATA';
2
+ export declare class ResumeError extends Error {
3
+ readonly code: ResumeErrorCode;
4
+ constructor(code: ResumeErrorCode, message: string);
5
+ }
6
+ export declare function assert(condition: any, code: ResumeErrorCode, message: string): asserts condition;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ 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
+ }
@@ -0,0 +1,49 @@
1
+ import { AgentEvent, AgentEventEnvelope, ControlEvent, MonitorEvent, ProgressEvent, Timeline, Bookmark } from '../core/types';
2
+ import { Store } from '../infra/store';
3
+ type ControlEventType = ControlEvent['type'];
4
+ type MonitorEventType = MonitorEvent['type'];
5
+ type SubscriberChannel = 'progress' | 'control' | 'monitor';
6
+ export declare class EventBus {
7
+ private cursor;
8
+ private seq;
9
+ private timeline;
10
+ private subscribers;
11
+ private controlEmitter;
12
+ private monitorEmitter;
13
+ private store?;
14
+ private agentId?;
15
+ private failedEvents;
16
+ private readonly MAX_FAILED_BUFFER;
17
+ constructor();
18
+ setStore(store: Store, agentId: string): void;
19
+ emitProgress(event: ProgressEvent): AgentEventEnvelope<ProgressEvent>;
20
+ emitControl(event: ControlEvent): AgentEventEnvelope<ControlEvent>;
21
+ emitMonitor(event: MonitorEvent): AgentEventEnvelope<MonitorEvent>;
22
+ subscribeProgress(opts?: {
23
+ since?: Bookmark;
24
+ kinds?: Array<ProgressEvent['type']>;
25
+ }): AsyncIterable<AgentEventEnvelope<ProgressEvent>>;
26
+ subscribe(channels?: SubscriberChannel[], opts?: {
27
+ since?: Bookmark;
28
+ kinds?: Array<AgentEvent['type']>;
29
+ }): AsyncIterable<AgentEventEnvelope>;
30
+ onControl<T extends ControlEventType>(type: T, handler: (evt: Extract<ControlEvent, {
31
+ type: T;
32
+ }>) => void): () => void;
33
+ onMonitor<T extends MonitorEventType>(type: T, handler: (evt: Extract<MonitorEvent, {
34
+ type: T;
35
+ }>) => void): () => void;
36
+ getTimeline(since?: number): Timeline[];
37
+ getCursor(): number;
38
+ getLastBookmark(): Bookmark | undefined;
39
+ reset(): void;
40
+ private emit;
41
+ private isCriticalEvent;
42
+ private retryFailedEvents;
43
+ getFailedEventCount(): number;
44
+ flushFailedEvents(): Promise<void>;
45
+ private notifySubscribers;
46
+ private replayHistory;
47
+ private iterableFor;
48
+ }
49
+ export {};
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventBus = void 0;
4
+ const events_1 = require("events");
5
+ class EventBus {
6
+ constructor() {
7
+ this.cursor = 0;
8
+ this.seq = 0;
9
+ this.timeline = [];
10
+ this.subscribers = new Map();
11
+ this.controlEmitter = new events_1.EventEmitter();
12
+ this.monitorEmitter = new events_1.EventEmitter();
13
+ this.failedEvents = [];
14
+ this.MAX_FAILED_BUFFER = 1000;
15
+ this.subscribers.set('progress', new Set());
16
+ this.subscribers.set('control', new Set());
17
+ this.subscribers.set('monitor', new Set());
18
+ // Node.js EventEmitter treats the "error" event specially: emitting it without a listener throws.
19
+ // Our Monitor channel intentionally uses event.type === 'error' for structured error telemetry.
20
+ // Add a default no-op listener so that Monitor errors never crash the host process when no callback is registered.
21
+ this.monitorEmitter.on('error', () => { });
22
+ this.controlEmitter.on('error', () => { });
23
+ }
24
+ setStore(store, agentId) {
25
+ this.store = store;
26
+ this.agentId = agentId;
27
+ }
28
+ emitProgress(event) {
29
+ const envelope = this.emit('progress', event);
30
+ this.notifySubscribers('progress', envelope);
31
+ return envelope;
32
+ }
33
+ emitControl(event) {
34
+ const envelope = this.emit('control', event);
35
+ this.controlEmitter.emit(event.type, envelope.event);
36
+ this.notifySubscribers('control', envelope);
37
+ return envelope;
38
+ }
39
+ emitMonitor(event) {
40
+ const envelope = this.emit('monitor', event);
41
+ this.monitorEmitter.emit(event.type, envelope.event);
42
+ this.notifySubscribers('monitor', envelope);
43
+ return envelope;
44
+ }
45
+ subscribeProgress(opts) {
46
+ const subscriber = new EventSubscriber(opts?.kinds);
47
+ this.subscribers.get('progress').add(subscriber);
48
+ if (opts?.since) {
49
+ void this.replayHistory('progress', subscriber, opts.since);
50
+ }
51
+ const bus = this;
52
+ return {
53
+ [Symbol.asyncIterator]() {
54
+ return {
55
+ async next() {
56
+ const value = (await subscriber.next());
57
+ if (!value) {
58
+ bus.subscribers.get('progress').delete(subscriber);
59
+ return { done: true, value: undefined };
60
+ }
61
+ return { done: false, value };
62
+ },
63
+ async return() {
64
+ subscriber.close();
65
+ bus.subscribers.get('progress').delete(subscriber);
66
+ return { done: true, value: undefined };
67
+ },
68
+ };
69
+ },
70
+ };
71
+ }
72
+ subscribe(channels = ['progress', 'control', 'monitor'], opts) {
73
+ const subscriber = new EventSubscriber(opts?.kinds);
74
+ for (const channel of channels) {
75
+ this.subscribers.get(channel).add(subscriber);
76
+ if (opts?.since) {
77
+ void this.replayHistory(channel, subscriber, opts.since);
78
+ }
79
+ }
80
+ return this.iterableFor(channels, subscriber);
81
+ }
82
+ onControl(type, handler) {
83
+ this.controlEmitter.on(type, handler);
84
+ return () => this.controlEmitter.off(type, handler);
85
+ }
86
+ onMonitor(type, handler) {
87
+ this.monitorEmitter.on(type, handler);
88
+ return () => this.monitorEmitter.off(type, handler);
89
+ }
90
+ getTimeline(since) {
91
+ return since !== undefined ? this.timeline.filter((t) => t.cursor >= since) : this.timeline;
92
+ }
93
+ getCursor() {
94
+ return this.cursor;
95
+ }
96
+ getLastBookmark() {
97
+ const last = this.timeline[this.timeline.length - 1];
98
+ return last?.bookmark;
99
+ }
100
+ reset() {
101
+ this.cursor = 0;
102
+ this.seq = 0;
103
+ this.timeline = [];
104
+ for (const set of this.subscribers.values()) {
105
+ set.clear();
106
+ }
107
+ this.controlEmitter.removeAllListeners();
108
+ this.monitorEmitter.removeAllListeners();
109
+ }
110
+ emit(channel, event) {
111
+ const bookmark = {
112
+ seq: this.seq++,
113
+ timestamp: Date.now(),
114
+ };
115
+ const eventWithChannel = { ...event, channel };
116
+ const eventWithBookmark = { ...eventWithChannel, bookmark };
117
+ const envelope = {
118
+ cursor: this.cursor++,
119
+ bookmark,
120
+ event: eventWithBookmark,
121
+ };
122
+ const timelineEntry = {
123
+ cursor: envelope.cursor,
124
+ bookmark,
125
+ event: envelope.event,
126
+ };
127
+ this.timeline.push(timelineEntry);
128
+ if (this.timeline.length > 10000) {
129
+ this.timeline = this.timeline.slice(-5000);
130
+ }
131
+ if (this.store && this.agentId) {
132
+ const isCritical = this.isCriticalEvent(event);
133
+ this.store.appendEvent(this.agentId, timelineEntry)
134
+ .then(() => {
135
+ // 成功后尝试重试之前失败的事件
136
+ if (this.failedEvents.length > 0) {
137
+ void this.retryFailedEvents();
138
+ }
139
+ })
140
+ .catch((err) => {
141
+ if (isCritical) {
142
+ // 关键事件失败:缓存到内存
143
+ this.failedEvents.push(timelineEntry);
144
+ if (this.failedEvents.length > this.MAX_FAILED_BUFFER) {
145
+ this.failedEvents = this.failedEvents.slice(-this.MAX_FAILED_BUFFER);
146
+ }
147
+ // 发送降级的内存 Monitor 事件(不持久化)
148
+ try {
149
+ this.monitorEmitter.emit('storage_failure', {
150
+ type: 'storage_failure',
151
+ severity: 'critical',
152
+ failedEvent: event.type,
153
+ bufferedCount: this.failedEvents.length,
154
+ error: err.message
155
+ });
156
+ }
157
+ catch {
158
+ // 降级事件发送失败也不阻塞
159
+ }
160
+ }
161
+ else {
162
+ // 非关键事件失败:仅记录日志
163
+ console.warn(`[EventBus] Failed to persist non-critical event: ${event.type}`, err);
164
+ }
165
+ });
166
+ }
167
+ return envelope;
168
+ }
169
+ isCriticalEvent(event) {
170
+ const criticalTypes = new Set([
171
+ 'tool:end',
172
+ 'done',
173
+ 'permission_decided',
174
+ 'agent_resumed',
175
+ 'state_changed',
176
+ 'breakpoint_changed',
177
+ 'error',
178
+ ]);
179
+ return criticalTypes.has(event.type);
180
+ }
181
+ async retryFailedEvents() {
182
+ if (!this.store || !this.agentId || this.failedEvents.length === 0)
183
+ return;
184
+ const toRetry = this.failedEvents.splice(0, 10);
185
+ for (const event of toRetry) {
186
+ try {
187
+ await this.store.appendEvent(this.agentId, event);
188
+ }
189
+ catch (err) {
190
+ this.failedEvents.unshift(event);
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ getFailedEventCount() {
196
+ return this.failedEvents.length;
197
+ }
198
+ async flushFailedEvents() {
199
+ while (this.failedEvents.length > 0) {
200
+ await this.retryFailedEvents();
201
+ if (this.failedEvents.length > 0) {
202
+ await new Promise(resolve => setTimeout(resolve, 1000));
203
+ }
204
+ }
205
+ }
206
+ notifySubscribers(channel, envelope) {
207
+ const subscribers = this.subscribers.get(channel);
208
+ if (!subscribers)
209
+ return;
210
+ for (const subscriber of subscribers) {
211
+ if (subscriber.accepts(envelope)) {
212
+ subscriber.push(envelope);
213
+ }
214
+ }
215
+ }
216
+ async replayHistory(channel, subscriber, since) {
217
+ if (this.store && this.agentId) {
218
+ try {
219
+ const opts = { channel: channel, since };
220
+ for await (const entry of this.store.readEvents(this.agentId, opts)) {
221
+ const envelope = entry;
222
+ if (subscriber.accepts(envelope)) {
223
+ subscriber.push(envelope);
224
+ }
225
+ }
226
+ return;
227
+ }
228
+ catch (error) {
229
+ console.error('Failed to replay events from store:', error);
230
+ }
231
+ }
232
+ const past = this.timeline.filter((t) => {
233
+ if (t.event.channel !== channel)
234
+ return false;
235
+ if (!since)
236
+ return true;
237
+ return t.bookmark.seq > since.seq;
238
+ });
239
+ for (const entry of past) {
240
+ const envelope = entry;
241
+ if (subscriber.accepts(envelope)) {
242
+ subscriber.push(envelope);
243
+ }
244
+ }
245
+ }
246
+ iterableFor(channel, subscriber) {
247
+ const channels = Array.isArray(channel) ? channel : [channel];
248
+ const bus = this;
249
+ return {
250
+ [Symbol.asyncIterator]() {
251
+ return {
252
+ next: async () => {
253
+ const value = (await subscriber.next());
254
+ if (!value) {
255
+ for (const ch of channels)
256
+ bus.subscribers.get(ch).delete(subscriber);
257
+ return { done: true, value: undefined };
258
+ }
259
+ return { done: false, value };
260
+ },
261
+ return: async () => {
262
+ subscriber.close();
263
+ for (const ch of channels)
264
+ bus.subscribers.get(ch).delete(subscriber);
265
+ return { done: true, value: undefined };
266
+ },
267
+ };
268
+ },
269
+ };
270
+ }
271
+ }
272
+ exports.EventBus = EventBus;
273
+ class EventSubscriber {
274
+ constructor(kinds) {
275
+ this.kinds = kinds;
276
+ this.queue = [];
277
+ this.waiting = null;
278
+ this.closed = false;
279
+ }
280
+ accepts(envelope) {
281
+ if (!this.kinds || this.kinds.length === 0)
282
+ return true;
283
+ return this.kinds.includes(String(envelope.event.type));
284
+ }
285
+ push(envelope) {
286
+ if (this.closed)
287
+ return;
288
+ if (this.waiting) {
289
+ this.waiting(envelope);
290
+ this.waiting = null;
291
+ }
292
+ else {
293
+ this.queue.push(envelope);
294
+ }
295
+ }
296
+ async next() {
297
+ if (this.closed)
298
+ return null;
299
+ if (this.queue.length > 0)
300
+ return this.queue.shift();
301
+ return new Promise((resolve) => {
302
+ this.waiting = resolve;
303
+ });
304
+ }
305
+ close() {
306
+ this.closed = true;
307
+ if (this.waiting) {
308
+ this.waiting(null);
309
+ this.waiting = null;
310
+ }
311
+ }
312
+ }
@@ -0,0 +1,43 @@
1
+ import { Sandbox } from '../infra/sandbox';
2
+ export interface FileRecord {
3
+ path: string;
4
+ lastRead?: number;
5
+ lastEdit?: number;
6
+ lastReadMtime?: number;
7
+ lastEditMtime?: number;
8
+ lastKnownMtime?: number;
9
+ }
10
+ export interface FileFreshness {
11
+ isFresh: boolean;
12
+ lastRead?: number;
13
+ lastEdit?: number;
14
+ currentMtime?: number;
15
+ }
16
+ interface FilePoolOptions {
17
+ watch?: boolean;
18
+ onChange?: (event: {
19
+ path: string;
20
+ mtime: number;
21
+ }) => void;
22
+ }
23
+ export declare class FilePool {
24
+ private readonly sandbox;
25
+ private records;
26
+ private watchers;
27
+ private readonly watchEnabled;
28
+ private readonly onChange?;
29
+ constructor(sandbox: Sandbox, opts?: FilePoolOptions);
30
+ private getMtime;
31
+ recordRead(path: string): Promise<void>;
32
+ recordEdit(path: string): Promise<void>;
33
+ recordDelete(path: string): Promise<void>;
34
+ validateWrite(path: string): Promise<FileFreshness>;
35
+ checkFreshness(path: string): Promise<FileFreshness>;
36
+ getTrackedFiles(): string[];
37
+ private ensureWatch;
38
+ getAccessedFiles(): Array<{
39
+ path: string;
40
+ mtime: number;
41
+ }>;
42
+ }
43
+ export {};
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilePool = void 0;
4
+ class FilePool {
5
+ constructor(sandbox, opts) {
6
+ this.sandbox = sandbox;
7
+ this.records = new Map();
8
+ this.watchers = new Map();
9
+ this.watchEnabled = opts?.watch ?? true;
10
+ this.onChange = opts?.onChange;
11
+ }
12
+ async getMtime(path) {
13
+ try {
14
+ const stat = await this.sandbox.fs.stat(path);
15
+ return stat.mtimeMs;
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
21
+ async recordRead(path) {
22
+ const resolved = this.sandbox.fs.resolve(path);
23
+ const record = this.records.get(resolved) || { path: resolved };
24
+ record.lastRead = Date.now();
25
+ record.lastReadMtime = await this.getMtime(resolved);
26
+ record.lastKnownMtime = record.lastReadMtime;
27
+ this.records.set(resolved, record);
28
+ await this.ensureWatch(resolved);
29
+ }
30
+ async recordEdit(path) {
31
+ const resolved = this.sandbox.fs.resolve(path);
32
+ const record = this.records.get(resolved) || { path: resolved };
33
+ record.lastEdit = Date.now();
34
+ record.lastEditMtime = await this.getMtime(resolved);
35
+ record.lastKnownMtime = record.lastEditMtime;
36
+ this.records.set(resolved, record);
37
+ await this.ensureWatch(resolved);
38
+ }
39
+ async recordDelete(path) {
40
+ const resolved = this.sandbox.fs.resolve(path);
41
+ this.records.delete(resolved);
42
+ const watcherId = this.watchers.get(resolved);
43
+ if (watcherId) {
44
+ try {
45
+ this.sandbox.unwatchFiles?.(watcherId);
46
+ }
47
+ catch {
48
+ // best-effort
49
+ }
50
+ this.watchers.delete(resolved);
51
+ }
52
+ }
53
+ async validateWrite(path) {
54
+ const resolved = this.sandbox.fs.resolve(path);
55
+ const record = this.records.get(resolved);
56
+ const currentMtime = await this.getMtime(resolved);
57
+ if (!record) {
58
+ return { isFresh: true, currentMtime };
59
+ }
60
+ // Accept writes if the file hasn't changed since either the last read OR the last edit.
61
+ // This reduces false positives when the agent performs multiple writes without re-reading,
62
+ // while still preventing overwriting external changes (mtime drift).
63
+ const matchesLastRead = record.lastReadMtime !== undefined && currentMtime === record.lastReadMtime;
64
+ const matchesLastEdit = record.lastEditMtime !== undefined && currentMtime === record.lastEditMtime;
65
+ const isFresh = currentMtime === undefined || matchesLastRead || matchesLastEdit;
66
+ return {
67
+ isFresh,
68
+ lastRead: record.lastRead,
69
+ lastEdit: record.lastEdit,
70
+ currentMtime,
71
+ };
72
+ }
73
+ async checkFreshness(path) {
74
+ const resolved = this.sandbox.fs.resolve(path);
75
+ const record = this.records.get(resolved);
76
+ const currentMtime = await this.getMtime(resolved);
77
+ if (!record) {
78
+ return { isFresh: false, currentMtime };
79
+ }
80
+ const isFresh = record.lastRead !== undefined &&
81
+ (currentMtime === undefined || record.lastKnownMtime === undefined || currentMtime === record.lastKnownMtime);
82
+ return {
83
+ isFresh,
84
+ lastRead: record.lastRead,
85
+ lastEdit: record.lastEdit,
86
+ currentMtime,
87
+ };
88
+ }
89
+ getTrackedFiles() {
90
+ return Array.from(this.records.keys());
91
+ }
92
+ async ensureWatch(path) {
93
+ if (!this.watchEnabled)
94
+ return;
95
+ if (!this.sandbox.watchFiles)
96
+ return;
97
+ if (this.watchers.has(path))
98
+ return;
99
+ try {
100
+ const id = await this.sandbox.watchFiles([path], (event) => {
101
+ const record = this.records.get(path);
102
+ if (record) {
103
+ record.lastKnownMtime = event.mtimeMs;
104
+ }
105
+ this.onChange?.({ path, mtime: event.mtimeMs });
106
+ });
107
+ this.watchers.set(path, id);
108
+ }
109
+ catch (err) {
110
+ // 记录 watch 失败,但不中断流程
111
+ console.warn(`[FilePool] Failed to watch file: ${path}`, err);
112
+ }
113
+ }
114
+ getAccessedFiles() {
115
+ return Array.from(this.records.values())
116
+ .filter((r) => r.lastKnownMtime !== undefined)
117
+ .map((r) => ({ path: r.path, mtime: r.lastKnownMtime }));
118
+ }
119
+ }
120
+ exports.FilePool = FilePool;
@@ -0,0 +1,23 @@
1
+ import { ToolCall, ToolOutcome, HookDecision, PostHookResult, ToolContext } from '../core/types';
2
+ import { ModelResponse } from '../infra/provider';
3
+ export interface Hooks {
4
+ preToolUse?: (call: ToolCall, ctx: ToolContext) => HookDecision | Promise<HookDecision>;
5
+ postToolUse?: (outcome: ToolOutcome, ctx: ToolContext) => PostHookResult | Promise<PostHookResult>;
6
+ preModel?: (request: any) => void | Promise<void>;
7
+ postModel?: (response: ModelResponse) => void | Promise<void>;
8
+ messagesChanged?: (snapshot: any) => void | Promise<void>;
9
+ }
10
+ export interface RegisteredHook {
11
+ origin: 'agent' | 'toolTune';
12
+ names: Array<'preToolUse' | 'postToolUse' | 'preModel' | 'postModel'>;
13
+ }
14
+ export declare class HookManager {
15
+ private hooks;
16
+ register(hooks: Hooks, origin?: 'agent' | 'toolTune'): void;
17
+ getRegistered(): ReadonlyArray<RegisteredHook>;
18
+ runPreToolUse(call: ToolCall, ctx: ToolContext): Promise<HookDecision>;
19
+ runPostToolUse(outcome: ToolOutcome, ctx: ToolContext): Promise<ToolOutcome>;
20
+ runPreModel(request: any): Promise<void>;
21
+ runPostModel(response: ModelResponse): Promise<void>;
22
+ runMessagesChanged(snapshot: any): Promise<void>;
23
+ }