@hflin/cclin 0.1.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 (200) hide show
  1. package/README.md +124 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +165 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/llm/client.d.ts +32 -0
  7. package/dist/llm/client.d.ts.map +1 -0
  8. package/dist/llm/client.js +280 -0
  9. package/dist/llm/client.js.map +1 -0
  10. package/dist/runtime/compaction.d.ts +49 -0
  11. package/dist/runtime/compaction.d.ts.map +1 -0
  12. package/dist/runtime/compaction.js +118 -0
  13. package/dist/runtime/compaction.js.map +1 -0
  14. package/dist/runtime/compaction.test.d.ts +7 -0
  15. package/dist/runtime/compaction.test.d.ts.map +1 -0
  16. package/dist/runtime/compaction.test.js +70 -0
  17. package/dist/runtime/compaction.test.js.map +1 -0
  18. package/dist/runtime/history.d.ts +34 -0
  19. package/dist/runtime/history.d.ts.map +1 -0
  20. package/dist/runtime/history.js +63 -0
  21. package/dist/runtime/history.js.map +1 -0
  22. package/dist/runtime/hooks.d.ts +54 -0
  23. package/dist/runtime/hooks.d.ts.map +1 -0
  24. package/dist/runtime/hooks.js +113 -0
  25. package/dist/runtime/hooks.js.map +1 -0
  26. package/dist/runtime/hooks.test.d.ts +7 -0
  27. package/dist/runtime/hooks.test.d.ts.map +1 -0
  28. package/dist/runtime/hooks.test.js +73 -0
  29. package/dist/runtime/hooks.test.js.map +1 -0
  30. package/dist/runtime/model-profile.d.ts +42 -0
  31. package/dist/runtime/model-profile.d.ts.map +1 -0
  32. package/dist/runtime/model-profile.js +84 -0
  33. package/dist/runtime/model-profile.js.map +1 -0
  34. package/dist/runtime/prompt.d.ts +38 -0
  35. package/dist/runtime/prompt.d.ts.map +1 -0
  36. package/dist/runtime/prompt.js +152 -0
  37. package/dist/runtime/prompt.js.map +1 -0
  38. package/dist/runtime/prompt.md +64 -0
  39. package/dist/runtime/prompt.test.d.ts +7 -0
  40. package/dist/runtime/prompt.test.d.ts.map +1 -0
  41. package/dist/runtime/prompt.test.js +38 -0
  42. package/dist/runtime/prompt.test.js.map +1 -0
  43. package/dist/runtime/react-loop.d.ts +82 -0
  44. package/dist/runtime/react-loop.d.ts.map +1 -0
  45. package/dist/runtime/react-loop.js +311 -0
  46. package/dist/runtime/react-loop.js.map +1 -0
  47. package/dist/runtime/react-loop.test.d.ts +7 -0
  48. package/dist/runtime/react-loop.test.d.ts.map +1 -0
  49. package/dist/runtime/react-loop.test.js +78 -0
  50. package/dist/runtime/react-loop.test.js.map +1 -0
  51. package/dist/runtime/session.d.ts +109 -0
  52. package/dist/runtime/session.d.ts.map +1 -0
  53. package/dist/runtime/session.js +252 -0
  54. package/dist/runtime/session.js.map +1 -0
  55. package/dist/runtime/skills.d.ts +36 -0
  56. package/dist/runtime/skills.d.ts.map +1 -0
  57. package/dist/runtime/skills.js +187 -0
  58. package/dist/runtime/skills.js.map +1 -0
  59. package/dist/runtime/skills.test.d.ts +7 -0
  60. package/dist/runtime/skills.test.d.ts.map +1 -0
  61. package/dist/runtime/skills.test.js +92 -0
  62. package/dist/runtime/skills.test.js.map +1 -0
  63. package/dist/tools/approval.d.ts +61 -0
  64. package/dist/tools/approval.d.ts.map +1 -0
  65. package/dist/tools/approval.js +119 -0
  66. package/dist/tools/approval.js.map +1 -0
  67. package/dist/tools/approval.test.d.ts +9 -0
  68. package/dist/tools/approval.test.d.ts.map +1 -0
  69. package/dist/tools/approval.test.js +112 -0
  70. package/dist/tools/approval.test.js.map +1 -0
  71. package/dist/tools/bash.d.ts +6 -0
  72. package/dist/tools/bash.d.ts.map +1 -0
  73. package/dist/tools/bash.js +58 -0
  74. package/dist/tools/bash.js.map +1 -0
  75. package/dist/tools/edit-file.d.ts +6 -0
  76. package/dist/tools/edit-file.d.ts.map +1 -0
  77. package/dist/tools/edit-file.js +58 -0
  78. package/dist/tools/edit-file.js.map +1 -0
  79. package/dist/tools/get-memory.d.ts +9 -0
  80. package/dist/tools/get-memory.d.ts.map +1 -0
  81. package/dist/tools/get-memory.js +56 -0
  82. package/dist/tools/get-memory.js.map +1 -0
  83. package/dist/tools/list-directory.d.ts +6 -0
  84. package/dist/tools/list-directory.d.ts.map +1 -0
  85. package/dist/tools/list-directory.js +68 -0
  86. package/dist/tools/list-directory.js.map +1 -0
  87. package/dist/tools/mcp-client.d.ts +74 -0
  88. package/dist/tools/mcp-client.d.ts.map +1 -0
  89. package/dist/tools/mcp-client.js +129 -0
  90. package/dist/tools/mcp-client.js.map +1 -0
  91. package/dist/tools/mcp-config.d.ts +31 -0
  92. package/dist/tools/mcp-config.d.ts.map +1 -0
  93. package/dist/tools/mcp-config.js +55 -0
  94. package/dist/tools/mcp-config.js.map +1 -0
  95. package/dist/tools/mcp-registry.d.ts +39 -0
  96. package/dist/tools/mcp-registry.d.ts.map +1 -0
  97. package/dist/tools/mcp-registry.js +88 -0
  98. package/dist/tools/mcp-registry.js.map +1 -0
  99. package/dist/tools/orchestrator.d.ts +52 -0
  100. package/dist/tools/orchestrator.d.ts.map +1 -0
  101. package/dist/tools/orchestrator.js +190 -0
  102. package/dist/tools/orchestrator.js.map +1 -0
  103. package/dist/tools/orchestrator.test.d.ts +8 -0
  104. package/dist/tools/orchestrator.test.d.ts.map +1 -0
  105. package/dist/tools/orchestrator.test.js +122 -0
  106. package/dist/tools/orchestrator.test.js.map +1 -0
  107. package/dist/tools/read-file.d.ts +6 -0
  108. package/dist/tools/read-file.d.ts.map +1 -0
  109. package/dist/tools/read-file.js +50 -0
  110. package/dist/tools/read-file.js.map +1 -0
  111. package/dist/tools/registry.d.ts +55 -0
  112. package/dist/tools/registry.d.ts.map +1 -0
  113. package/dist/tools/registry.js +75 -0
  114. package/dist/tools/registry.js.map +1 -0
  115. package/dist/tools/registry.test.d.ts +8 -0
  116. package/dist/tools/registry.test.d.ts.map +1 -0
  117. package/dist/tools/registry.test.js +100 -0
  118. package/dist/tools/registry.test.js.map +1 -0
  119. package/dist/tools/router.d.ts +62 -0
  120. package/dist/tools/router.d.ts.map +1 -0
  121. package/dist/tools/router.js +119 -0
  122. package/dist/tools/router.js.map +1 -0
  123. package/dist/tools/router.test.d.ts +7 -0
  124. package/dist/tools/router.test.d.ts.map +1 -0
  125. package/dist/tools/router.test.js +102 -0
  126. package/dist/tools/router.test.js.map +1 -0
  127. package/dist/tools/safety.d.ts +16 -0
  128. package/dist/tools/safety.d.ts.map +1 -0
  129. package/dist/tools/safety.js +81 -0
  130. package/dist/tools/safety.js.map +1 -0
  131. package/dist/tools/safety.test.d.ts +7 -0
  132. package/dist/tools/safety.test.d.ts.map +1 -0
  133. package/dist/tools/safety.test.js +104 -0
  134. package/dist/tools/safety.test.js.map +1 -0
  135. package/dist/tools/search-files.d.ts +9 -0
  136. package/dist/tools/search-files.d.ts.map +1 -0
  137. package/dist/tools/search-files.js +114 -0
  138. package/dist/tools/search-files.js.map +1 -0
  139. package/dist/tools/update-plan.d.ts +9 -0
  140. package/dist/tools/update-plan.d.ts.map +1 -0
  141. package/dist/tools/update-plan.js +99 -0
  142. package/dist/tools/update-plan.js.map +1 -0
  143. package/dist/tools/write-file.d.ts +6 -0
  144. package/dist/tools/write-file.d.ts.map +1 -0
  145. package/dist/tools/write-file.js +41 -0
  146. package/dist/tools/write-file.js.map +1 -0
  147. package/dist/tui/app.d.ts +31 -0
  148. package/dist/tui/app.d.ts.map +1 -0
  149. package/dist/tui/app.js +121 -0
  150. package/dist/tui/app.js.map +1 -0
  151. package/dist/tui/chatwidget/markdown_renderer.d.ts +20 -0
  152. package/dist/tui/chatwidget/markdown_renderer.d.ts.map +1 -0
  153. package/dist/tui/chatwidget/markdown_renderer.js +188 -0
  154. package/dist/tui/chatwidget/markdown_renderer.js.map +1 -0
  155. package/dist/tui/cjk_text.d.ts +25 -0
  156. package/dist/tui/cjk_text.d.ts.map +1 -0
  157. package/dist/tui/cjk_text.js +84 -0
  158. package/dist/tui/cjk_text.js.map +1 -0
  159. package/dist/tui/cjk_text.test.d.ts +2 -0
  160. package/dist/tui/cjk_text.test.d.ts.map +1 -0
  161. package/dist/tui/cjk_text.test.js +62 -0
  162. package/dist/tui/cjk_text.test.js.map +1 -0
  163. package/dist/tui/composer_input.d.ts +31 -0
  164. package/dist/tui/composer_input.d.ts.map +1 -0
  165. package/dist/tui/composer_input.js +184 -0
  166. package/dist/tui/composer_input.js.map +1 -0
  167. package/dist/tui/composer_input.test.d.ts +2 -0
  168. package/dist/tui/composer_input.test.d.ts.map +1 -0
  169. package/dist/tui/composer_input.test.js +87 -0
  170. package/dist/tui/composer_input.test.js.map +1 -0
  171. package/dist/tui/input.d.ts +21 -0
  172. package/dist/tui/input.d.ts.map +1 -0
  173. package/dist/tui/input.js +166 -0
  174. package/dist/tui/input.js.map +1 -0
  175. package/dist/tui/output.d.ts +17 -0
  176. package/dist/tui/output.d.ts.map +1 -0
  177. package/dist/tui/output.js +104 -0
  178. package/dist/tui/output.js.map +1 -0
  179. package/dist/tui/state/chat_timeline.d.ts +50 -0
  180. package/dist/tui/state/chat_timeline.d.ts.map +1 -0
  181. package/dist/tui/state/chat_timeline.js +129 -0
  182. package/dist/tui/state/chat_timeline.js.map +1 -0
  183. package/dist/tui/types.d.ts +45 -0
  184. package/dist/tui/types.d.ts.map +1 -0
  185. package/dist/tui/types.js +14 -0
  186. package/dist/tui/types.js.map +1 -0
  187. package/dist/types.d.ts +435 -0
  188. package/dist/types.d.ts.map +1 -0
  189. package/dist/types.js +8 -0
  190. package/dist/types.js.map +1 -0
  191. package/dist/utils/tokenizer.d.ts +21 -0
  192. package/dist/utils/tokenizer.d.ts.map +1 -0
  193. package/dist/utils/tokenizer.js +71 -0
  194. package/dist/utils/tokenizer.js.map +1 -0
  195. package/dist/utils/tokenizer.test.d.ts +7 -0
  196. package/dist/utils/tokenizer.test.d.ts.map +1 -0
  197. package/dist/utils/tokenizer.test.js +51 -0
  198. package/dist/utils/tokenizer.test.js.map +1 -0
  199. package/package.json +41 -0
  200. package/src/runtime/prompt.md +64 -0
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @file 工具编排器 — 统一调度工具执行。
3
+ *
4
+ * Phase 4:在 ToolRegistry 和 ReAct 循环之间的中间层。
5
+ *
6
+ * 职责链:工具查找 → 审批检查 → 输入解析 → 执行 → 错误分类 → 结果截断
7
+ *
8
+ * 设计思路:
9
+ * 1. 将散落在 registry.createExecuteTool() 和 react-loop 中的
10
+ * 执行逻辑集中到一个统一入口。
11
+ * 2. 通过 ApprovalHooks 回调将审批 UI 解耦。
12
+ * 3. 提供 createExecuteTool() 兼容现有 ReAct 循环接口。
13
+ */
14
+ /** 工具输出最大字符数(超过则截断)。 */
15
+ const MAX_OUTPUT_CHARS = 50_000;
16
+ // ─── 辅助函数 ─────────────────────────────────────────────────────────────────
17
+ /** 截断过长的工具输出。 */
18
+ function truncateOutput(output, toolName) {
19
+ if (output.length <= MAX_OUTPUT_CHARS)
20
+ return output;
21
+ return (output.slice(0, MAX_OUTPUT_CHARS) +
22
+ `\n...[truncated] ${toolName} output too long ` +
23
+ `(${output.length} chars, max ${MAX_OUTPUT_CHARS})`);
24
+ }
25
+ /** 解析工具输入,确保为 Record 类型。 */
26
+ function parseToolInput(rawInput) {
27
+ if (rawInput === null || rawInput === undefined)
28
+ return {};
29
+ if (typeof rawInput === 'object' && !Array.isArray(rawInput)) {
30
+ return rawInput;
31
+ }
32
+ if (typeof rawInput === 'string') {
33
+ try {
34
+ const parsed = JSON.parse(rawInput);
35
+ if (typeof parsed === 'object' && parsed !== null) {
36
+ return parsed;
37
+ }
38
+ }
39
+ catch {
40
+ // 解析失败,返回空对象
41
+ }
42
+ }
43
+ return {};
44
+ }
45
+ /** 错误分类。 */
46
+ function classifyError(err) {
47
+ const msg = err instanceof Error
48
+ ? err.message.toLowerCase()
49
+ : String(err).toLowerCase();
50
+ if (msg.includes('permission denied') ||
51
+ msg.includes('eacces')) {
52
+ return 'execution_failed';
53
+ }
54
+ return 'execution_failed';
55
+ }
56
+ // ─── ToolOrchestrator 类 ─────────────────────────────────────────────────────
57
+ /**
58
+ * 工具编排器。
59
+ *
60
+ * 统一的工具执行入口,包含:
61
+ * 1. 工具查找
62
+ * 2. 审批检查
63
+ * 3. 输入解析
64
+ * 4. 工具执行
65
+ * 5. 错误分类
66
+ * 6. 结果截断
67
+ */
68
+ export class ToolOrchestrator {
69
+ registry;
70
+ approvalManager;
71
+ constructor(registry, approvalManager) {
72
+ this.registry = registry;
73
+ this.approvalManager = approvalManager;
74
+ }
75
+ /**
76
+ * 执行单个工具调用。
77
+ *
78
+ * 完整流程:查找 → 审批 → 解析 → 执行 → 截断
79
+ */
80
+ async executeAction(action, hooks) {
81
+ const startedAt = Date.now();
82
+ // 1. 工具查找
83
+ const tool = this.registry.get(action.name);
84
+ if (!tool) {
85
+ return {
86
+ actionId: action.id,
87
+ tool: action.name,
88
+ status: 'tool_not_found',
89
+ success: false,
90
+ observation: `Error: tool "${action.name}" not found.`,
91
+ durationMs: Date.now() - startedAt,
92
+ };
93
+ }
94
+ // 2. 审批检查
95
+ const check = this.approvalManager.check(action.name, action.input, tool.isMutating);
96
+ if (check.needsApproval) {
97
+ const request = {
98
+ toolName: check.toolName,
99
+ input: check.input,
100
+ fingerprint: check.fingerprint,
101
+ reason: check.reason,
102
+ };
103
+ // 调用 UI 审批回调
104
+ const decision = hooks?.requestApproval
105
+ ? await hooks.requestApproval(request)
106
+ : 'deny';
107
+ this.approvalManager.recordDecision(check.fingerprint, decision);
108
+ if (decision === 'deny') {
109
+ return {
110
+ actionId: action.id,
111
+ tool: action.name,
112
+ status: 'approval_denied',
113
+ success: false,
114
+ observation: `User denied: "${action.name}". ` +
115
+ 'Please inform the user and suggest alternatives.',
116
+ durationMs: Date.now() - startedAt,
117
+ };
118
+ }
119
+ }
120
+ // 3. 输入解析
121
+ const parsedInput = parseToolInput(action.input);
122
+ // 4. 执行工具
123
+ try {
124
+ const result = await tool.execute(parsedInput);
125
+ // 5. 结果截断
126
+ const output = truncateOutput(result.output, action.name);
127
+ return {
128
+ actionId: action.id,
129
+ tool: action.name,
130
+ status: result.isError ? 'execution_failed' : 'success',
131
+ success: !result.isError,
132
+ observation: output,
133
+ durationMs: Date.now() - startedAt,
134
+ };
135
+ }
136
+ catch (err) {
137
+ return {
138
+ actionId: action.id,
139
+ tool: action.name,
140
+ status: classifyError(err),
141
+ success: false,
142
+ observation: `Tool execution error: ${err.message}`,
143
+ durationMs: Date.now() - startedAt,
144
+ };
145
+ }
146
+ }
147
+ /**
148
+ * 批量执行工具调用(顺序执行)。
149
+ */
150
+ async executeActions(actions, hooks) {
151
+ const results = [];
152
+ for (const action of actions) {
153
+ const result = await this.executeAction(action, hooks);
154
+ results.push(result);
155
+ // 如果被拒绝,停止后续执行
156
+ if (result.status === 'approval_denied')
157
+ break;
158
+ }
159
+ const hasRejection = results.some((r) => r.status === 'approval_denied');
160
+ const combinedObservation = results
161
+ .map((r) => r.observation)
162
+ .join('\n---\n');
163
+ return { results, combinedObservation, hasRejection };
164
+ }
165
+ /**
166
+ * 创建兼容 ExecuteTool 签名的函数。
167
+ *
168
+ * 让 Orchestrator 可以无缝接入现有 ReAct 循环。
169
+ */
170
+ createExecuteTool(hooks) {
171
+ return async (toolName, toolInput) => {
172
+ const action = {
173
+ id: `${toolName}:${Date.now()}`,
174
+ name: toolName,
175
+ input: toolInput,
176
+ };
177
+ const result = await this.executeAction(action, hooks);
178
+ return result.observation;
179
+ };
180
+ }
181
+ /** 清除 once 级别授权(Turn 结束时调用)。 */
182
+ clearOnceApprovals() {
183
+ this.approvalManager.clearOnceApprovals();
184
+ }
185
+ /** 清除所有授权(Session 结束时调用)。 */
186
+ dispose() {
187
+ this.approvalManager.dispose();
188
+ }
189
+ }
190
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/tools/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @file Unit tests for ToolOrchestrator (Phase 4).
3
+ *
4
+ * Tests: tool not found, approval denied, successful execution,
5
+ * output truncation, parseToolInput, createExecuteTool
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=orchestrator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.test.d.ts","sourceRoot":"","sources":["../../src/tools/orchestrator.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @file Unit tests for ToolOrchestrator (Phase 4).
3
+ *
4
+ * Tests: tool not found, approval denied, successful execution,
5
+ * output truncation, parseToolInput, createExecuteTool
6
+ */
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ import { ToolOrchestrator } from './orchestrator.js';
9
+ import { ApprovalManager } from './approval.js';
10
+ // ─── Test Fixtures ────────────────────────────────────────────────────────────
11
+ function makeTool(name, mutating, output = 'ok') {
12
+ return {
13
+ name,
14
+ description: `${name} desc`,
15
+ inputSchema: { type: 'object', properties: {}, required: [] },
16
+ isMutating: mutating,
17
+ execute: vi.fn(async () => ({ output })),
18
+ };
19
+ }
20
+ function makeRegistry(tools) {
21
+ const map = new Map(tools.map((t) => [t.name, t]));
22
+ return { get: (name) => map.get(name) };
23
+ }
24
+ function makeAction(name, input = {}) {
25
+ return { id: `${name}:1`, name, input };
26
+ }
27
+ // ─── Tests ────────────────────────────────────────────────────────────────────
28
+ describe('ToolOrchestrator', () => {
29
+ let approval;
30
+ let orchestrator;
31
+ describe('executeAction', () => {
32
+ beforeEach(() => {
33
+ approval = new ApprovalManager();
34
+ });
35
+ it('should return tool_not_found for unknown tools', async () => {
36
+ orchestrator = new ToolOrchestrator(makeRegistry([]), approval);
37
+ const result = await orchestrator.executeAction(makeAction('x'));
38
+ expect(result.status).toBe('tool_not_found');
39
+ expect(result.success).toBe(false);
40
+ });
41
+ it('should deny when no approval hook and tool is mutating', async () => {
42
+ const tool = makeTool('bash', true);
43
+ orchestrator = new ToolOrchestrator(makeRegistry([tool]), approval);
44
+ const result = await orchestrator.executeAction(makeAction('bash'));
45
+ expect(result.status).toBe('approval_denied');
46
+ expect(result.success).toBe(false);
47
+ });
48
+ it('should auto-pass non-mutating tools', async () => {
49
+ const tool = makeTool('read_file', false, 'file content');
50
+ orchestrator = new ToolOrchestrator(makeRegistry([tool]), approval);
51
+ const result = await orchestrator.executeAction(makeAction('read_file'));
52
+ expect(result.status).toBe('success');
53
+ expect(result.observation).toBe('file content');
54
+ });
55
+ it('should pass with approval hook returning approve', async () => {
56
+ const tool = makeTool('bash', true, 'done');
57
+ orchestrator = new ToolOrchestrator(makeRegistry([tool]), approval);
58
+ const hooks = { requestApproval: vi.fn(async () => 'approve') };
59
+ const result = await orchestrator.executeAction(makeAction('bash'), hooks);
60
+ expect(result.status).toBe('success');
61
+ expect(result.observation).toBe('done');
62
+ });
63
+ it('should handle tool execution error', async () => {
64
+ const tool = makeTool('bad', false);
65
+ vi.mocked(tool.execute).mockRejectedValue(new Error('boom'));
66
+ orchestrator = new ToolOrchestrator(makeRegistry([tool]), approval);
67
+ const result = await orchestrator.executeAction(makeAction('bad'));
68
+ expect(result.status).toBe('execution_failed');
69
+ expect(result.observation).toContain('boom');
70
+ });
71
+ it('should truncate oversized output', async () => {
72
+ const big = 'x'.repeat(60_000);
73
+ const tool = makeTool('big_tool', false, big);
74
+ orchestrator = new ToolOrchestrator(makeRegistry([tool]), approval);
75
+ const result = await orchestrator.executeAction(makeAction('big_tool'));
76
+ expect(result.observation.length).toBeLessThan(big.length);
77
+ expect(result.observation).toContain('[truncated]');
78
+ });
79
+ });
80
+ describe('executeActions', () => {
81
+ it('should stop on first approval_denied', async () => {
82
+ approval = new ApprovalManager();
83
+ const t1 = makeTool('a', true);
84
+ const t2 = makeTool('b', true);
85
+ orchestrator = new ToolOrchestrator(makeRegistry([t1, t2]), approval);
86
+ const result = await orchestrator.executeActions([
87
+ makeAction('a'),
88
+ makeAction('b'),
89
+ ]);
90
+ expect(result.hasRejection).toBe(true);
91
+ // Only first was attempted
92
+ expect(result.results).toHaveLength(1);
93
+ });
94
+ });
95
+ describe('createExecuteTool', () => {
96
+ it('should return observation string', async () => {
97
+ approval = new ApprovalManager();
98
+ const tool = makeTool('list', false, 'files here');
99
+ orchestrator = new ToolOrchestrator(makeRegistry([tool]), approval);
100
+ const exec = orchestrator.createExecuteTool();
101
+ const obs = await exec('list', {});
102
+ expect(obs).toBe('files here');
103
+ });
104
+ });
105
+ describe('lifecycle', () => {
106
+ it('clearOnceApprovals delegates to approval manager', () => {
107
+ approval = new ApprovalManager();
108
+ const spy = vi.spyOn(approval, 'clearOnceApprovals');
109
+ orchestrator = new ToolOrchestrator(makeRegistry([]), approval);
110
+ orchestrator.clearOnceApprovals();
111
+ expect(spy).toHaveBeenCalledOnce();
112
+ });
113
+ it('dispose delegates to approval manager', () => {
114
+ approval = new ApprovalManager();
115
+ const spy = vi.spyOn(approval, 'dispose');
116
+ orchestrator = new ToolOrchestrator(makeRegistry([]), approval);
117
+ orchestrator.dispose();
118
+ expect(spy).toHaveBeenCalledOnce();
119
+ });
120
+ });
121
+ });
122
+ //# sourceMappingURL=orchestrator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.test.js","sourceRoot":"","sources":["../../src/tools/orchestrator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @file read_file 工具 — 读取文件内容,支持 offset/limit 分段。
3
+ */
4
+ import type { ToolDefinition } from '../types.js';
5
+ export declare const readFileTool: ToolDefinition;
6
+ //# sourceMappingURL=read-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-file.d.ts","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD,eAAO,MAAM,YAAY,EAAE,cAkD1B,CAAA"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @file read_file 工具 — 读取文件内容,支持 offset/limit 分段。
3
+ */
4
+ import * as fs from 'node:fs/promises';
5
+ import * as path from 'node:path';
6
+ import { validatePath } from './safety.js';
7
+ export const readFileTool = {
8
+ name: 'read_file',
9
+ description: 'Read the contents of a file. Supports offset/limit for partial reads. ' +
10
+ 'Returns line-numbered output.',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ path: { type: 'string', description: 'File path to read.' },
15
+ offset: { type: 'number', description: 'Start line (0-based, default 0).' },
16
+ limit: { type: 'number', description: 'Max lines to return (default: all).' },
17
+ },
18
+ required: ['path'],
19
+ },
20
+ isMutating: false,
21
+ async execute(input) {
22
+ const filePath = String(input.path ?? '');
23
+ if (!filePath)
24
+ return { output: 'Error: path is required.', isError: true };
25
+ const validation = validatePath(filePath);
26
+ if (!validation.ok)
27
+ return { output: validation.error, isError: true };
28
+ const resolved = path.resolve(filePath);
29
+ try {
30
+ const raw = await fs.readFile(resolved, 'utf-8');
31
+ let lines = raw.split('\n');
32
+ const offset = Math.max(0, Number(input.offset) || 0);
33
+ const limit = Number(input.limit) || 0;
34
+ if (offset > 0 || limit > 0) {
35
+ const end = limit > 0 ? offset + limit : undefined;
36
+ lines = lines.slice(offset, end);
37
+ }
38
+ const numbered = lines.map((line, i) => `${offset + i + 1}: ${line}`).join('\n');
39
+ return { output: `File: ${resolved}\n${numbered}` };
40
+ }
41
+ catch (err) {
42
+ const code = err.code;
43
+ const msg = code === 'ENOENT'
44
+ ? `File not found: ${resolved}`
45
+ : `read_file failed: ${err.message}`;
46
+ return { output: msg, isError: true };
47
+ }
48
+ },
49
+ };
50
+ //# sourceMappingURL=read-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAmB;IACxC,IAAI,EAAE,WAAW;IACjB,WAAW,EACP,wEAAwE;QACxE,+BAA+B;IACnC,WAAW,EAAE;QACT,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACR,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAE;YAC3D,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kCAAkC,EAAE;YAC3E,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qCAAqC,EAAE;SAChF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACrB;IACD,UAAU,EAAE,KAAK;IAEjB,KAAK,CAAC,OAAO,CAAC,KAAK;QACf,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QAE3E,MAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QAEtE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAEvC,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YAChD,IAAI,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAE3B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;YACrD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAEtC,IAAI,MAAM,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;gBAClD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;YACpC,CAAC;YAED,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CACnC,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAC/B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAEZ,OAAO,EAAE,MAAM,EAAE,SAAS,QAAQ,KAAK,QAAQ,EAAE,EAAE,CAAA;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAA;YAChD,MAAM,GAAG,GAAG,IAAI,KAAK,QAAQ;gBACzB,CAAC,CAAC,mBAAmB,QAAQ,EAAE;gBAC/B,CAAC,CAAC,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAA;YACnD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QACzC,CAAC;IACL,CAAC;CACJ,CAAA"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file 工具注册表 — 管理所有已注册的工具定义。
3
+ *
4
+ * Phase 3:提供工具注册、查询和格式转换能力。
5
+ *
6
+ * 职责:
7
+ * 1. 存储 ToolDefinition 实例
8
+ * 2. 提供 toOpenAITools() 生成 LLM 所需的 tools 参数
9
+ * 3. (功能已转移)提供 createExecuteTool() 生成符合 ExecuteTool 签名的函数
10
+ */
11
+ import type { ToolDefinition } from '../types.js';
12
+ /**
13
+ * 工具注册表。
14
+ *
15
+ * 用法:
16
+ * ```ts
17
+ * const registry = new ToolRegistry()
18
+ * registry.register(readFileTool)
19
+ * registry.register(bashTool)
20
+ *
21
+ * const tools = registry.toOpenAITools() // 传给 LLM
22
+ * (功能已转移)const executeTool = registry.createExecuteTool() // 传给 ReAct 循环
23
+ * ```
24
+ */
25
+ export declare class ToolRegistry {
26
+ private tools;
27
+ /** 注册单个工具。 */
28
+ register(tool: ToolDefinition): void;
29
+ /** 批量注册工具。 */
30
+ registerMany(tools: ToolDefinition[]): void;
31
+ /** 获取指定工具。 */
32
+ get(name: string): ToolDefinition | undefined;
33
+ /** 获取所有工具。 */
34
+ getAll(): ToolDefinition[];
35
+ /** 检查工具是否存在。 */
36
+ has(name: string): boolean;
37
+ /** 已注册工具数量。 */
38
+ get size(): number;
39
+ /**
40
+ * 转换为 OpenAI function calling 的 tools 参数格式。
41
+ */
42
+ toOpenAITools(): Array<{
43
+ type: 'function';
44
+ function: {
45
+ name: string;
46
+ description: string;
47
+ parameters: Record<string, unknown>;
48
+ };
49
+ }>;
50
+ /**
51
+ * 转换为 Markdown 文本格式,供系统提示词注入。
52
+ */
53
+ toMarkdown(): string;
54
+ }
55
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,KAAK,CAAyC;IAEtD,cAAc;IACd,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAIpC,cAAc;IACd,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI;IAM3C,cAAc;IACd,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI7C,cAAc;IACd,MAAM,IAAI,cAAc,EAAE;IAI1B,gBAAgB;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,eAAe;IACf,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,aAAa,IAAI,KAAK,CAAC;QACnB,IAAI,EAAE,UAAU,CAAA;QAChB,QAAQ,EAAE;YACN,IAAI,EAAE,MAAM,CAAA;YACZ,WAAW,EAAE,MAAM,CAAA;YACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SACtC,CAAA;KACJ,CAAC;IAWF;;OAEG;IACH,UAAU,IAAI,MAAM;CAmCvB"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @file 工具注册表 — 管理所有已注册的工具定义。
3
+ *
4
+ * Phase 3:提供工具注册、查询和格式转换能力。
5
+ *
6
+ * 职责:
7
+ * 1. 存储 ToolDefinition 实例
8
+ * 2. 提供 toOpenAITools() 生成 LLM 所需的 tools 参数
9
+ * 3. (功能已转移)提供 createExecuteTool() 生成符合 ExecuteTool 签名的函数
10
+ */
11
+ // ─── ToolRegistry 类 ─────────────────────────────────────────────────────────
12
+ /**
13
+ * 工具注册表。
14
+ *
15
+ * 用法:
16
+ * ```ts
17
+ * const registry = new ToolRegistry()
18
+ * registry.register(readFileTool)
19
+ * registry.register(bashTool)
20
+ *
21
+ * const tools = registry.toOpenAITools() // 传给 LLM
22
+ * (功能已转移)const executeTool = registry.createExecuteTool() // 传给 ReAct 循环
23
+ * ```
24
+ */
25
+ export class ToolRegistry {
26
+ tools = new Map();
27
+ /** 注册单个工具。 */
28
+ register(tool) {
29
+ this.tools.set(tool.name, tool);
30
+ }
31
+ /** 批量注册工具。 */
32
+ registerMany(tools) {
33
+ for (const tool of tools) {
34
+ this.register(tool);
35
+ }
36
+ }
37
+ /** 获取指定工具。 */
38
+ get(name) {
39
+ return this.tools.get(name);
40
+ }
41
+ /** 获取所有工具。 */
42
+ getAll() {
43
+ return Array.from(this.tools.values());
44
+ }
45
+ /** 检查工具是否存在。 */
46
+ has(name) {
47
+ return this.tools.has(name);
48
+ }
49
+ /** 已注册工具数量。 */
50
+ get size() {
51
+ return this.tools.size;
52
+ }
53
+ /**
54
+ * 转换为 OpenAI function calling 的 tools 参数格式。
55
+ */
56
+ toOpenAITools() {
57
+ return this.getAll().map((tool) => ({
58
+ type: 'function',
59
+ function: {
60
+ name: tool.name,
61
+ description: tool.description,
62
+ parameters: tool.inputSchema,
63
+ },
64
+ }));
65
+ }
66
+ /**
67
+ * 转换为 Markdown 文本格式,供系统提示词注入。
68
+ */
69
+ toMarkdown() {
70
+ return this.getAll()
71
+ .map((tool) => `### ${tool.name}\n${tool.description}\n\n**Parameters Schema**:\n\`\`\`json\n${JSON.stringify(tool.inputSchema, null, 2)}\n\`\`\``)
72
+ .join('\n\n');
73
+ }
74
+ }
75
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,+EAA+E;AAE/E;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,YAAY;IACb,KAAK,GAAgC,IAAI,GAAG,EAAE,CAAA;IAEtD,cAAc;IACd,QAAQ,CAAC,IAAoB;QACzB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACnC,CAAC;IAED,cAAc;IACd,YAAY,CAAC,KAAuB;QAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACvB,CAAC;IACL,CAAC;IAED,cAAc;IACd,GAAG,CAAC,IAAY;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IAED,cAAc;IACd,MAAM;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,gBAAgB;IAChB,GAAG,CAAC,IAAY;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IAED,eAAe;IACf,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;IAC1B,CAAC;IAED;;OAEG;IACH,aAAa;QAQT,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,EAAE,UAAmB;YACzB,QAAQ,EAAE;gBACN,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,UAAU,EAAE,IAAI,CAAC,WAAW;aAC/B;SACJ,CAAC,CAAC,CAAA;IACP,CAAC;IAED;;OAEG;IACH,UAAU;QACN,OAAO,IAAI,CAAC,MAAM,EAAE;aACf,GAAG,CACA,CAAC,IAAI,EAAE,EAAE,CACL,OAAO,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,2CAA2C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,CAC1I;aACA,IAAI,CAAC,MAAM,CAAC,CAAA;IACrB,CAAC;CA4BJ"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @file Unit tests for ToolRegistry (Phase 3).
3
+ *
4
+ * Tests: register, get, has, size, getAll, registerMany,
5
+ * toOpenAITools, toMarkdown
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../src/tools/registry.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}