@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.
- package/README.md +124 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +32 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +280 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/runtime/compaction.d.ts +49 -0
- package/dist/runtime/compaction.d.ts.map +1 -0
- package/dist/runtime/compaction.js +118 -0
- package/dist/runtime/compaction.js.map +1 -0
- package/dist/runtime/compaction.test.d.ts +7 -0
- package/dist/runtime/compaction.test.d.ts.map +1 -0
- package/dist/runtime/compaction.test.js +70 -0
- package/dist/runtime/compaction.test.js.map +1 -0
- package/dist/runtime/history.d.ts +34 -0
- package/dist/runtime/history.d.ts.map +1 -0
- package/dist/runtime/history.js +63 -0
- package/dist/runtime/history.js.map +1 -0
- package/dist/runtime/hooks.d.ts +54 -0
- package/dist/runtime/hooks.d.ts.map +1 -0
- package/dist/runtime/hooks.js +113 -0
- package/dist/runtime/hooks.js.map +1 -0
- package/dist/runtime/hooks.test.d.ts +7 -0
- package/dist/runtime/hooks.test.d.ts.map +1 -0
- package/dist/runtime/hooks.test.js +73 -0
- package/dist/runtime/hooks.test.js.map +1 -0
- package/dist/runtime/model-profile.d.ts +42 -0
- package/dist/runtime/model-profile.d.ts.map +1 -0
- package/dist/runtime/model-profile.js +84 -0
- package/dist/runtime/model-profile.js.map +1 -0
- package/dist/runtime/prompt.d.ts +38 -0
- package/dist/runtime/prompt.d.ts.map +1 -0
- package/dist/runtime/prompt.js +152 -0
- package/dist/runtime/prompt.js.map +1 -0
- package/dist/runtime/prompt.md +64 -0
- package/dist/runtime/prompt.test.d.ts +7 -0
- package/dist/runtime/prompt.test.d.ts.map +1 -0
- package/dist/runtime/prompt.test.js +38 -0
- package/dist/runtime/prompt.test.js.map +1 -0
- package/dist/runtime/react-loop.d.ts +82 -0
- package/dist/runtime/react-loop.d.ts.map +1 -0
- package/dist/runtime/react-loop.js +311 -0
- package/dist/runtime/react-loop.js.map +1 -0
- package/dist/runtime/react-loop.test.d.ts +7 -0
- package/dist/runtime/react-loop.test.d.ts.map +1 -0
- package/dist/runtime/react-loop.test.js +78 -0
- package/dist/runtime/react-loop.test.js.map +1 -0
- package/dist/runtime/session.d.ts +109 -0
- package/dist/runtime/session.d.ts.map +1 -0
- package/dist/runtime/session.js +252 -0
- package/dist/runtime/session.js.map +1 -0
- package/dist/runtime/skills.d.ts +36 -0
- package/dist/runtime/skills.d.ts.map +1 -0
- package/dist/runtime/skills.js +187 -0
- package/dist/runtime/skills.js.map +1 -0
- package/dist/runtime/skills.test.d.ts +7 -0
- package/dist/runtime/skills.test.d.ts.map +1 -0
- package/dist/runtime/skills.test.js +92 -0
- package/dist/runtime/skills.test.js.map +1 -0
- package/dist/tools/approval.d.ts +61 -0
- package/dist/tools/approval.d.ts.map +1 -0
- package/dist/tools/approval.js +119 -0
- package/dist/tools/approval.js.map +1 -0
- package/dist/tools/approval.test.d.ts +9 -0
- package/dist/tools/approval.test.d.ts.map +1 -0
- package/dist/tools/approval.test.js +112 -0
- package/dist/tools/approval.test.js.map +1 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +58 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-file.d.ts +6 -0
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +58 -0
- package/dist/tools/edit-file.js.map +1 -0
- package/dist/tools/get-memory.d.ts +9 -0
- package/dist/tools/get-memory.d.ts.map +1 -0
- package/dist/tools/get-memory.js +56 -0
- package/dist/tools/get-memory.js.map +1 -0
- package/dist/tools/list-directory.d.ts +6 -0
- package/dist/tools/list-directory.d.ts.map +1 -0
- package/dist/tools/list-directory.js +68 -0
- package/dist/tools/list-directory.js.map +1 -0
- package/dist/tools/mcp-client.d.ts +74 -0
- package/dist/tools/mcp-client.d.ts.map +1 -0
- package/dist/tools/mcp-client.js +129 -0
- package/dist/tools/mcp-client.js.map +1 -0
- package/dist/tools/mcp-config.d.ts +31 -0
- package/dist/tools/mcp-config.d.ts.map +1 -0
- package/dist/tools/mcp-config.js +55 -0
- package/dist/tools/mcp-config.js.map +1 -0
- package/dist/tools/mcp-registry.d.ts +39 -0
- package/dist/tools/mcp-registry.d.ts.map +1 -0
- package/dist/tools/mcp-registry.js +88 -0
- package/dist/tools/mcp-registry.js.map +1 -0
- package/dist/tools/orchestrator.d.ts +52 -0
- package/dist/tools/orchestrator.d.ts.map +1 -0
- package/dist/tools/orchestrator.js +190 -0
- package/dist/tools/orchestrator.js.map +1 -0
- package/dist/tools/orchestrator.test.d.ts +8 -0
- package/dist/tools/orchestrator.test.d.ts.map +1 -0
- package/dist/tools/orchestrator.test.js +122 -0
- package/dist/tools/orchestrator.test.js.map +1 -0
- package/dist/tools/read-file.d.ts +6 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +50 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/registry.d.ts +55 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +75 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/registry.test.d.ts +8 -0
- package/dist/tools/registry.test.d.ts.map +1 -0
- package/dist/tools/registry.test.js +100 -0
- package/dist/tools/registry.test.js.map +1 -0
- package/dist/tools/router.d.ts +62 -0
- package/dist/tools/router.d.ts.map +1 -0
- package/dist/tools/router.js +119 -0
- package/dist/tools/router.js.map +1 -0
- package/dist/tools/router.test.d.ts +7 -0
- package/dist/tools/router.test.d.ts.map +1 -0
- package/dist/tools/router.test.js +102 -0
- package/dist/tools/router.test.js.map +1 -0
- package/dist/tools/safety.d.ts +16 -0
- package/dist/tools/safety.d.ts.map +1 -0
- package/dist/tools/safety.js +81 -0
- package/dist/tools/safety.js.map +1 -0
- package/dist/tools/safety.test.d.ts +7 -0
- package/dist/tools/safety.test.d.ts.map +1 -0
- package/dist/tools/safety.test.js +104 -0
- package/dist/tools/safety.test.js.map +1 -0
- package/dist/tools/search-files.d.ts +9 -0
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +114 -0
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/update-plan.d.ts +9 -0
- package/dist/tools/update-plan.d.ts.map +1 -0
- package/dist/tools/update-plan.js +99 -0
- package/dist/tools/update-plan.js.map +1 -0
- package/dist/tools/write-file.d.ts +6 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +41 -0
- package/dist/tools/write-file.js.map +1 -0
- package/dist/tui/app.d.ts +31 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +121 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/chatwidget/markdown_renderer.d.ts +20 -0
- package/dist/tui/chatwidget/markdown_renderer.d.ts.map +1 -0
- package/dist/tui/chatwidget/markdown_renderer.js +188 -0
- package/dist/tui/chatwidget/markdown_renderer.js.map +1 -0
- package/dist/tui/cjk_text.d.ts +25 -0
- package/dist/tui/cjk_text.d.ts.map +1 -0
- package/dist/tui/cjk_text.js +84 -0
- package/dist/tui/cjk_text.js.map +1 -0
- package/dist/tui/cjk_text.test.d.ts +2 -0
- package/dist/tui/cjk_text.test.d.ts.map +1 -0
- package/dist/tui/cjk_text.test.js +62 -0
- package/dist/tui/cjk_text.test.js.map +1 -0
- package/dist/tui/composer_input.d.ts +31 -0
- package/dist/tui/composer_input.d.ts.map +1 -0
- package/dist/tui/composer_input.js +184 -0
- package/dist/tui/composer_input.js.map +1 -0
- package/dist/tui/composer_input.test.d.ts +2 -0
- package/dist/tui/composer_input.test.d.ts.map +1 -0
- package/dist/tui/composer_input.test.js +87 -0
- package/dist/tui/composer_input.test.js.map +1 -0
- package/dist/tui/input.d.ts +21 -0
- package/dist/tui/input.d.ts.map +1 -0
- package/dist/tui/input.js +166 -0
- package/dist/tui/input.js.map +1 -0
- package/dist/tui/output.d.ts +17 -0
- package/dist/tui/output.d.ts.map +1 -0
- package/dist/tui/output.js +104 -0
- package/dist/tui/output.js.map +1 -0
- package/dist/tui/state/chat_timeline.d.ts +50 -0
- package/dist/tui/state/chat_timeline.d.ts.map +1 -0
- package/dist/tui/state/chat_timeline.js +129 -0
- package/dist/tui/state/chat_timeline.js.map +1 -0
- package/dist/tui/types.d.ts +45 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +14 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/types.d.ts +435 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/tokenizer.d.ts +21 -0
- package/dist/utils/tokenizer.d.ts.map +1 -0
- package/dist/utils/tokenizer.js +71 -0
- package/dist/utils/tokenizer.js.map +1 -0
- package/dist/utils/tokenizer.test.d.ts +7 -0
- package/dist/utils/tokenizer.test.d.ts.map +1 -0
- package/dist/utils/tokenizer.test.js +51 -0
- package/dist/utils/tokenizer.test.js.map +1 -0
- package/package.json +41 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../src/tools/registry.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|