hypercore-cli 1.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/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/api-XGC7D5AW.js +162 -0
- package/dist/auth-DNQWYQKT.js +21 -0
- package/dist/background-2EGCAAQH.js +14 -0
- package/dist/backlog-Q2NZCLNY.js +24 -0
- package/dist/chunk-2CMSCWQW.js +162 -0
- package/dist/chunk-2LJ2DVEB.js +167 -0
- package/dist/chunk-3RPFCQKJ.js +288 -0
- package/dist/chunk-43OLRXM5.js +263 -0
- package/dist/chunk-4DVYJAJL.js +57 -0
- package/dist/chunk-6OL3GA3P.js +173 -0
- package/dist/chunk-AUHU7ALH.js +2023 -0
- package/dist/chunk-B6A2AKLN.js +139 -0
- package/dist/chunk-BE46C7JW.js +46 -0
- package/dist/chunk-CUVAUOXL.js +58 -0
- package/dist/chunk-GH7E2OJE.js +223 -0
- package/dist/chunk-GOOTEPBK.js +271 -0
- package/dist/chunk-GPPMJYSM.js +133 -0
- package/dist/chunk-GU2FZQ6A.js +69 -0
- package/dist/chunk-IOPKN5GD.js +190 -0
- package/dist/chunk-IXOIOGR5.js +1505 -0
- package/dist/chunk-KRPOPWGA.js +251 -0
- package/dist/chunk-MGLJ53QN.js +219 -0
- package/dist/chunk-MV4TTRYX.js +533 -0
- package/dist/chunk-OPZYEVYR.js +150 -0
- package/dist/chunk-QTSLP47C.js +166 -0
- package/dist/chunk-R3GPQC7I.js +393 -0
- package/dist/chunk-RKB2JOV2.js +43 -0
- package/dist/chunk-RNG3K465.js +80 -0
- package/dist/chunk-TGTYKBGC.js +86 -0
- package/dist/chunk-U5SGAIMM.js +681 -0
- package/dist/chunk-V5UHPPSY.js +140 -0
- package/dist/chunk-WHLVZCQY.js +245 -0
- package/dist/chunk-XDRCBMZZ.js +66 -0
- package/dist/chunk-XOS6HPEF.js +134 -0
- package/dist/chunk-ZSBHUGWR.js +262 -0
- package/dist/claude-NSQ442XD.js +12 -0
- package/dist/commands-CK3WFAGI.js +128 -0
- package/dist/commands-U63OEO5J.js +1044 -0
- package/dist/commands-ZE6GD3WC.js +232 -0
- package/dist/config-4EW42BSF.js +8 -0
- package/dist/config-loader-SXO674TF.js +24 -0
- package/dist/diagnose-AFW3ZTZ4.js +12 -0
- package/dist/display-IIUBEYWN.js +58 -0
- package/dist/extractor-QV53W2YJ.js +129 -0
- package/dist/history-WMSCHERZ.js +180 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +406 -0
- package/dist/instance-registry-YSIJXSO7.js +15 -0
- package/dist/keybindings-JAAMLH3G.js +15 -0
- package/dist/loader-WHNTZTLP.js +58 -0
- package/dist/network-MM6YWPGO.js +279 -0
- package/dist/notify-HPTALZDC.js +14 -0
- package/dist/openai-compat-UQWJXBEK.js +12 -0
- package/dist/permissions-JUKXMNDH.js +10 -0
- package/dist/prompt-QV45TXRL.js +166 -0
- package/dist/quality-ST7PPNFR.js +16 -0
- package/dist/repl-RT3AHL7M.js +3375 -0
- package/dist/roadmap-5OBEKROY.js +17 -0
- package/dist/server-PORT7OEG.js +57 -0
- package/dist/session-4VUNDWLH.js +21 -0
- package/dist/skills-V4A35XKG.js +175 -0
- package/dist/store-Y4LU5QTO.js +25 -0
- package/dist/team-HO7Z4SIM.js +385 -0
- package/dist/telemetry-6R4EIE6O.js +30 -0
- package/dist/test-runner-ZQH5Y6OJ.js +619 -0
- package/dist/theme-3SYJ3UQA.js +14 -0
- package/dist/upgrade-7TGI3SXO.js +83 -0
- package/dist/verify-JUDKTPKZ.js +14 -0
- package/dist/web/static/app.js +562 -0
- package/dist/web/static/index.html +132 -0
- package/dist/web/static/mirror.css +1001 -0
- package/dist/web/static/mirror.html +184 -0
- package/dist/web/static/mirror.js +1125 -0
- package/dist/web/static/onboard.css +302 -0
- package/dist/web/static/onboard.html +140 -0
- package/dist/web/static/onboard.js +260 -0
- package/dist/web/static/style.css +602 -0
- package/dist/web/static/workspace.css +1568 -0
- package/dist/web/static/workspace.html +408 -0
- package/dist/web/static/workspace.js +1683 -0
- package/dist/web-Z5HSCQHW.js +39 -0
- package/package.json +67 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hookManager
|
|
3
|
+
} from "./chunk-B6A2AKLN.js";
|
|
4
|
+
|
|
5
|
+
// src/llm/openai-compat.ts
|
|
6
|
+
import OpenAI from "openai";
|
|
7
|
+
function createOpenAIClient(config) {
|
|
8
|
+
return new OpenAI({
|
|
9
|
+
apiKey: config.apiKey,
|
|
10
|
+
baseURL: config.baseURL
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function triggerToolHook(event, context) {
|
|
14
|
+
try {
|
|
15
|
+
return await hookManager.trigger(event, context);
|
|
16
|
+
} catch {
|
|
17
|
+
return { intercepted: false };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function callOpenAILLM(client, options) {
|
|
21
|
+
const {
|
|
22
|
+
systemPrompt,
|
|
23
|
+
userPrompt,
|
|
24
|
+
tools,
|
|
25
|
+
model = "gemini-2.5-flash",
|
|
26
|
+
maxTokens = 8192,
|
|
27
|
+
onText,
|
|
28
|
+
onToolCall,
|
|
29
|
+
onThinking,
|
|
30
|
+
onToolCallDone
|
|
31
|
+
} = options;
|
|
32
|
+
const toolDefinitions = tools.map((t) => ({
|
|
33
|
+
type: "function",
|
|
34
|
+
function: {
|
|
35
|
+
name: t.definition.name,
|
|
36
|
+
description: t.definition.description,
|
|
37
|
+
parameters: t.definition.input_schema
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
const messages = [
|
|
41
|
+
{ role: "system", content: systemPrompt },
|
|
42
|
+
{ role: "user", content: userPrompt }
|
|
43
|
+
];
|
|
44
|
+
let totalOutput = "";
|
|
45
|
+
let currentRoundText = "";
|
|
46
|
+
const allToolCalls = [];
|
|
47
|
+
let totalInputTokens = 0;
|
|
48
|
+
let totalOutputTokens = 0;
|
|
49
|
+
let maxIterations = 10;
|
|
50
|
+
while (maxIterations-- > 0) {
|
|
51
|
+
currentRoundText = "";
|
|
52
|
+
const response = await client.chat.completions.create({
|
|
53
|
+
model,
|
|
54
|
+
max_tokens: maxTokens,
|
|
55
|
+
messages,
|
|
56
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0
|
|
57
|
+
});
|
|
58
|
+
if (response.usage) {
|
|
59
|
+
totalInputTokens += response.usage.prompt_tokens || 0;
|
|
60
|
+
totalOutputTokens += response.usage.completion_tokens || 0;
|
|
61
|
+
}
|
|
62
|
+
const choice = response.choices[0];
|
|
63
|
+
if (!choice) break;
|
|
64
|
+
const message = choice.message;
|
|
65
|
+
if (message.content) {
|
|
66
|
+
totalOutput += message.content;
|
|
67
|
+
currentRoundText += message.content;
|
|
68
|
+
onText?.(message.content);
|
|
69
|
+
}
|
|
70
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
71
|
+
if (currentRoundText.trim()) {
|
|
72
|
+
onThinking?.(currentRoundText.trim());
|
|
73
|
+
}
|
|
74
|
+
messages.push(message);
|
|
75
|
+
for (const toolCall of message.tool_calls) {
|
|
76
|
+
const fn = toolCall.function;
|
|
77
|
+
const fnName = fn.name;
|
|
78
|
+
let fnArgs = {};
|
|
79
|
+
try {
|
|
80
|
+
fnArgs = JSON.parse(fn.arguments || "{}");
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
const toolInputText = JSON.stringify(fnArgs);
|
|
84
|
+
onToolCall?.(fnName, fnArgs);
|
|
85
|
+
const tool = tools.find((t) => t.definition.name === fnName);
|
|
86
|
+
let result;
|
|
87
|
+
const toolStart = Date.now();
|
|
88
|
+
const hookResult = await triggerToolHook("onToolCall", {
|
|
89
|
+
model,
|
|
90
|
+
toolName: fnName,
|
|
91
|
+
toolInput: toolInputText
|
|
92
|
+
});
|
|
93
|
+
if (hookResult.intercepted) {
|
|
94
|
+
result = `Error: Hook intercepted tool call - ${hookResult.reason || "blocked"}`;
|
|
95
|
+
} else if (tool) {
|
|
96
|
+
try {
|
|
97
|
+
result = await tool.handler(fnArgs);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
result = `Error: Tool "${fnName}" not found`;
|
|
103
|
+
}
|
|
104
|
+
const toolDuration = Date.now() - toolStart;
|
|
105
|
+
onToolCallDone?.(fnName, toolDuration);
|
|
106
|
+
await triggerToolHook("onToolResult", {
|
|
107
|
+
model,
|
|
108
|
+
toolName: fnName,
|
|
109
|
+
toolInput: toolInputText,
|
|
110
|
+
toolResult: result
|
|
111
|
+
});
|
|
112
|
+
allToolCalls.push({
|
|
113
|
+
toolName: fnName,
|
|
114
|
+
input: fnArgs,
|
|
115
|
+
output: result
|
|
116
|
+
});
|
|
117
|
+
messages.push({
|
|
118
|
+
role: "tool",
|
|
119
|
+
tool_call_id: toolCall.id,
|
|
120
|
+
content: result
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
output: totalOutput,
|
|
129
|
+
responseText: allToolCalls.length > 0 ? currentRoundText : totalOutput,
|
|
130
|
+
toolCalls: allToolCalls,
|
|
131
|
+
tokenUsage: {
|
|
132
|
+
inputTokens: totalInputTokens,
|
|
133
|
+
outputTokens: totalOutputTokens
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function streamOpenAIChat(client, messages, options) {
|
|
138
|
+
const { model, tools = [], onChunk, onToolCall, onThinking, onToolCallDone } = options;
|
|
139
|
+
const toolDefinitions = tools.map((t) => ({
|
|
140
|
+
type: "function",
|
|
141
|
+
function: {
|
|
142
|
+
name: t.definition.name,
|
|
143
|
+
description: t.definition.description,
|
|
144
|
+
parameters: t.definition.input_schema
|
|
145
|
+
}
|
|
146
|
+
}));
|
|
147
|
+
let totalContent = "";
|
|
148
|
+
let currentRoundContent = "";
|
|
149
|
+
let hasCalledTools = false;
|
|
150
|
+
let totalInputTokens = 0;
|
|
151
|
+
let totalOutputTokens = 0;
|
|
152
|
+
let maxIterations = 10;
|
|
153
|
+
while (maxIterations-- > 0) {
|
|
154
|
+
currentRoundContent = "";
|
|
155
|
+
let stream;
|
|
156
|
+
let lastError;
|
|
157
|
+
for (let retry = 0; retry < 5; retry++) {
|
|
158
|
+
try {
|
|
159
|
+
stream = await client.chat.completions.create({
|
|
160
|
+
model,
|
|
161
|
+
max_tokens: 4096,
|
|
162
|
+
messages,
|
|
163
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0,
|
|
164
|
+
stream: true
|
|
165
|
+
});
|
|
166
|
+
lastError = null;
|
|
167
|
+
break;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
lastError = err;
|
|
170
|
+
const status = err.status;
|
|
171
|
+
if (status === 429) {
|
|
172
|
+
const wait = (retry + 1) * 5e3;
|
|
173
|
+
process.stdout.write(`\x1B[2m \u23F3 API \u9650\u6D41\uFF0C${wait / 1e3}s \u540E\u91CD\u8BD5...\x1B[0m
|
|
174
|
+
`);
|
|
175
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (lastError) throw lastError;
|
|
182
|
+
let chunkContent = "";
|
|
183
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
184
|
+
for await (const chunk of stream) {
|
|
185
|
+
const delta = chunk.choices[0]?.delta;
|
|
186
|
+
if (!delta) continue;
|
|
187
|
+
if (delta.content) {
|
|
188
|
+
chunkContent += delta.content;
|
|
189
|
+
totalContent += delta.content;
|
|
190
|
+
currentRoundContent += delta.content;
|
|
191
|
+
onChunk?.(delta.content);
|
|
192
|
+
}
|
|
193
|
+
if (delta.tool_calls) {
|
|
194
|
+
for (const tc of delta.tool_calls) {
|
|
195
|
+
const existing = toolCalls.get(tc.index) || { id: "", name: "", args: "" };
|
|
196
|
+
if (tc.id) existing.id = tc.id;
|
|
197
|
+
if (tc.function?.name) existing.name = tc.function.name;
|
|
198
|
+
if (tc.function?.arguments) existing.args += tc.function.arguments;
|
|
199
|
+
toolCalls.set(tc.index, existing);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (chunk.usage) {
|
|
203
|
+
totalInputTokens += chunk.usage.prompt_tokens || 0;
|
|
204
|
+
totalOutputTokens += chunk.usage.completion_tokens || 0;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (toolCalls.size === 0) break;
|
|
208
|
+
hasCalledTools = true;
|
|
209
|
+
if (chunkContent.trim()) {
|
|
210
|
+
onThinking?.(chunkContent.trim());
|
|
211
|
+
}
|
|
212
|
+
const assistantMessage = {
|
|
213
|
+
role: "assistant",
|
|
214
|
+
content: chunkContent || null,
|
|
215
|
+
tool_calls: Array.from(toolCalls.values()).map((tc) => ({
|
|
216
|
+
id: tc.id,
|
|
217
|
+
type: "function",
|
|
218
|
+
function: { name: tc.name, arguments: tc.args }
|
|
219
|
+
}))
|
|
220
|
+
};
|
|
221
|
+
messages.push(assistantMessage);
|
|
222
|
+
for (const [, tc] of toolCalls) {
|
|
223
|
+
let fnArgs = {};
|
|
224
|
+
try {
|
|
225
|
+
fnArgs = JSON.parse(tc.args || "{}");
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
onToolCall?.(tc.name, fnArgs);
|
|
229
|
+
const toolInputText = JSON.stringify(fnArgs);
|
|
230
|
+
const tool = tools.find((t) => t.definition.name === tc.name);
|
|
231
|
+
let result;
|
|
232
|
+
const toolStart = Date.now();
|
|
233
|
+
const hookResult = await triggerToolHook("onToolCall", {
|
|
234
|
+
model,
|
|
235
|
+
toolName: tc.name,
|
|
236
|
+
toolInput: toolInputText
|
|
237
|
+
});
|
|
238
|
+
if (hookResult.intercepted) {
|
|
239
|
+
result = `Error: Hook intercepted tool call - ${hookResult.reason || "blocked"}`;
|
|
240
|
+
} else if (tool) {
|
|
241
|
+
try {
|
|
242
|
+
result = await tool.handler(fnArgs);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
result = `Error: Tool "${tc.name}" not found`;
|
|
248
|
+
}
|
|
249
|
+
const toolDuration = Date.now() - toolStart;
|
|
250
|
+
onToolCallDone?.(tc.name, toolDuration);
|
|
251
|
+
await triggerToolHook("onToolResult", {
|
|
252
|
+
model,
|
|
253
|
+
toolName: tc.name,
|
|
254
|
+
toolInput: toolInputText,
|
|
255
|
+
toolResult: result
|
|
256
|
+
});
|
|
257
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: result });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
content: totalContent,
|
|
262
|
+
responseContent: hasCalledTools ? currentRoundContent : totalContent,
|
|
263
|
+
tokenUsage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens }
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export {
|
|
268
|
+
createOpenAIClient,
|
|
269
|
+
callOpenAILLM,
|
|
270
|
+
streamOpenAIChat
|
|
271
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HYPERCORE_DIR
|
|
3
|
+
} from "./chunk-V5UHPPSY.js";
|
|
4
|
+
|
|
5
|
+
// src/core/instance-registry.ts
|
|
6
|
+
import { readFile, writeFile, unlink } from "fs/promises";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var REGISTRY_PATH = join(HYPERCORE_DIR, "instances.json");
|
|
10
|
+
var LOCK_PATH = join(HYPERCORE_DIR, "instances.lock");
|
|
11
|
+
function isPidAlive(pid) {
|
|
12
|
+
try {
|
|
13
|
+
process.kill(pid, 0);
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function withLock(fn) {
|
|
20
|
+
const maxRetries = 5;
|
|
21
|
+
const baseDelay = 50;
|
|
22
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
23
|
+
try {
|
|
24
|
+
await writeFile(LOCK_PATH, String(process.pid), { flag: "wx" });
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
} finally {
|
|
28
|
+
await unlink(LOCK_PATH).catch(() => {
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
const code = err.code;
|
|
33
|
+
if (code === "EEXIST") {
|
|
34
|
+
try {
|
|
35
|
+
const lockPid = parseInt(await readFile(LOCK_PATH, "utf-8"), 10);
|
|
36
|
+
if (!isPidAlive(lockPid)) {
|
|
37
|
+
await unlink(LOCK_PATH).catch(() => {
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
await new Promise((r) => setTimeout(r, baseDelay * (i + 1)));
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
await unlink(LOCK_PATH).catch(() => {
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw new Error("Failed to acquire instance registry lock after " + maxRetries + " retries");
|
|
52
|
+
}
|
|
53
|
+
async function readRegistry() {
|
|
54
|
+
try {
|
|
55
|
+
if (!existsSync(REGISTRY_PATH)) {
|
|
56
|
+
return { instances: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
57
|
+
}
|
|
58
|
+
const data = JSON.parse(await readFile(REGISTRY_PATH, "utf-8"));
|
|
59
|
+
return data;
|
|
60
|
+
} catch {
|
|
61
|
+
return { instances: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function writeRegistry(registry) {
|
|
65
|
+
registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
66
|
+
await writeFile(REGISTRY_PATH, JSON.stringify(registry, null, 2), "utf-8");
|
|
67
|
+
}
|
|
68
|
+
async function registerInstance(instance) {
|
|
69
|
+
await withLock(async () => {
|
|
70
|
+
const registry = await readRegistry();
|
|
71
|
+
registry.instances = registry.instances.filter(
|
|
72
|
+
(inst) => inst.pid !== instance.pid && isPidAlive(inst.pid)
|
|
73
|
+
);
|
|
74
|
+
registry.instances.push(instance);
|
|
75
|
+
await writeRegistry(registry);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async function updateInstanceRuntime(pid, patch) {
|
|
79
|
+
if (Object.keys(patch).length === 0) return;
|
|
80
|
+
await withLock(async () => {
|
|
81
|
+
const registry = await readRegistry();
|
|
82
|
+
let changed = false;
|
|
83
|
+
const aliveInstances = registry.instances.filter((inst) => isPidAlive(inst.pid));
|
|
84
|
+
if (aliveInstances.length !== registry.instances.length) {
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
registry.instances = aliveInstances.map((inst) => {
|
|
88
|
+
if (inst.pid !== pid) return inst;
|
|
89
|
+
changed = true;
|
|
90
|
+
return { ...inst, ...patch };
|
|
91
|
+
});
|
|
92
|
+
if (changed) {
|
|
93
|
+
await writeRegistry(registry);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function unregisterInstance(pid) {
|
|
98
|
+
await withLock(async () => {
|
|
99
|
+
const registry = await readRegistry();
|
|
100
|
+
registry.instances = registry.instances.filter((inst) => inst.pid !== pid);
|
|
101
|
+
await writeRegistry(registry);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async function listInstances() {
|
|
105
|
+
const registry = await readRegistry();
|
|
106
|
+
const alive = registry.instances.filter((inst) => isPidAlive(inst.pid));
|
|
107
|
+
if (alive.length !== registry.instances.length) {
|
|
108
|
+
withLock(async () => {
|
|
109
|
+
const fresh = await readRegistry();
|
|
110
|
+
fresh.instances = fresh.instances.filter((inst) => isPidAlive(inst.pid));
|
|
111
|
+
await writeRegistry(fresh);
|
|
112
|
+
}).catch(() => {
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return alive;
|
|
116
|
+
}
|
|
117
|
+
async function getCanonicalDashboardPort(fallbackPort = 3210) {
|
|
118
|
+
try {
|
|
119
|
+
const instances = await listInstances();
|
|
120
|
+
if (instances.length === 0) return fallbackPort;
|
|
121
|
+
return Math.min(...instances.map((i) => i.port));
|
|
122
|
+
} catch {
|
|
123
|
+
return fallbackPort;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
registerInstance,
|
|
129
|
+
updateInstanceRuntime,
|
|
130
|
+
unregisterInstance,
|
|
131
|
+
listInstances,
|
|
132
|
+
getCanonicalDashboardPort
|
|
133
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/core/permissions.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { confirm } from "@inquirer/prompts";
|
|
4
|
+
var TOOL_PERMISSIONS = {
|
|
5
|
+
// safe — 只读操作
|
|
6
|
+
file_read: "safe",
|
|
7
|
+
glob: "safe",
|
|
8
|
+
grep: "safe",
|
|
9
|
+
web_fetch: "safe",
|
|
10
|
+
web_search: "safe",
|
|
11
|
+
memory_search: "safe",
|
|
12
|
+
notebook_read: "safe",
|
|
13
|
+
task_output: "safe",
|
|
14
|
+
sub_agent: "safe",
|
|
15
|
+
// write — 文件修改操作
|
|
16
|
+
file_write: "write",
|
|
17
|
+
file_edit: "write",
|
|
18
|
+
memory_save: "write",
|
|
19
|
+
notebook_edit: "write",
|
|
20
|
+
// dangerous — 系统级操作
|
|
21
|
+
bash: "dangerous"
|
|
22
|
+
};
|
|
23
|
+
function getToolPermission(toolName) {
|
|
24
|
+
if (TOOL_PERMISSIONS[toolName]) return TOOL_PERMISSIONS[toolName];
|
|
25
|
+
if (toolName.startsWith("mcp_")) return "write";
|
|
26
|
+
return "write";
|
|
27
|
+
}
|
|
28
|
+
function wrapToolWithPermission(tool, config) {
|
|
29
|
+
const level = getToolPermission(tool.definition.name);
|
|
30
|
+
if (level === "safe") return tool;
|
|
31
|
+
const originalHandler = tool.handler;
|
|
32
|
+
return {
|
|
33
|
+
definition: tool.definition,
|
|
34
|
+
handler: async (input) => {
|
|
35
|
+
const mode = config.permissionMode || "full";
|
|
36
|
+
if (mode === "full") return originalHandler(input);
|
|
37
|
+
if (mode === "safe") {
|
|
38
|
+
return `\u26D4 \u6743\u9650\u62D2\u7EDD: \u5F53\u524D\u4E3A safe \u6A21\u5F0F\uFF0C\u5DE5\u5177 "${tool.definition.name}" (${level}) \u4E0D\u53EF\u7528\u3002\u4F7F\u7528 /permissions full \u5207\u6362\u6A21\u5F0F\u3002`;
|
|
39
|
+
}
|
|
40
|
+
if (mode === "ask") {
|
|
41
|
+
const inputPreview = JSON.stringify(input).slice(0, 200);
|
|
42
|
+
console.log(chalk.yellow(`
|
|
43
|
+
\u26A0\uFE0F \u6743\u9650\u786E\u8BA4: ${tool.definition.name} [${level}]`));
|
|
44
|
+
console.log(chalk.dim(` \u8F93\u5165: ${inputPreview}`));
|
|
45
|
+
try {
|
|
46
|
+
const yes = await confirm({
|
|
47
|
+
message: `\u5141\u8BB8\u6267\u884C ${tool.definition.name}?`,
|
|
48
|
+
default: true
|
|
49
|
+
});
|
|
50
|
+
if (!yes) {
|
|
51
|
+
return `\u26D4 \u7528\u6237\u62D2\u7EDD\u6267\u884C "${tool.definition.name}"`;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
return `\u26D4 \u7528\u6237\u53D6\u6D88\u6267\u884C "${tool.definition.name}"`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return originalHandler(input);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function applyPermissions(tools, config) {
|
|
62
|
+
return tools.map((t) => wrapToolWithPermission(t, config));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
getToolPermission,
|
|
67
|
+
wrapToolWithPermission,
|
|
68
|
+
applyPermissions
|
|
69
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getEventsSummary
|
|
3
|
+
} from "./chunk-2CMSCWQW.js";
|
|
4
|
+
import {
|
|
5
|
+
getItem,
|
|
6
|
+
listItems,
|
|
7
|
+
updateItemMetrics
|
|
8
|
+
} from "./chunk-MGLJ53QN.js";
|
|
9
|
+
|
|
10
|
+
// src/admin/verify.ts
|
|
11
|
+
function inferBaselineKey(item) {
|
|
12
|
+
const titleLower = item.title.toLowerCase();
|
|
13
|
+
const descLower = item.description.toLowerCase();
|
|
14
|
+
const text = titleLower + " " + descLower;
|
|
15
|
+
if (text.includes("\u5931\u8D25\u7387") || text.includes("fail")) {
|
|
16
|
+
const cmdMatch = text.match(/(\/\w+)\s+失败/) || text.match(/(\/\w+).*fail/);
|
|
17
|
+
if (cmdMatch) return `cmd_fail_rate:${cmdMatch[1]}`;
|
|
18
|
+
return "cmd_fail_rate:all";
|
|
19
|
+
}
|
|
20
|
+
if (text.includes("\u9519\u8BEF") || text.includes("error")) {
|
|
21
|
+
return "error_count";
|
|
22
|
+
}
|
|
23
|
+
if (text.includes("\u8017\u65F6") || text.includes("\u6027\u80FD") || text.includes("perf") || text.includes("duration")) {
|
|
24
|
+
return "avg_session_rounds";
|
|
25
|
+
}
|
|
26
|
+
if (text.includes("\u4F1A\u8BDD") || text.includes("session") || text.includes("\u8F6E\u6B21")) {
|
|
27
|
+
return "avg_session_rounds";
|
|
28
|
+
}
|
|
29
|
+
if (text.includes("test") || text.includes("\u6D4B\u8BD5")) {
|
|
30
|
+
return "test_pass_rate";
|
|
31
|
+
}
|
|
32
|
+
if (text.includes("todo") || text.includes("fixme") || text.includes("\u6280\u672F\u503A")) {
|
|
33
|
+
return "todo_count";
|
|
34
|
+
}
|
|
35
|
+
if (text.includes("\u4F53\u79EF") || text.includes("size") || text.includes("bundle")) {
|
|
36
|
+
return "package_size";
|
|
37
|
+
}
|
|
38
|
+
if (item.type === "bugfix") return "error_count";
|
|
39
|
+
if (item.type === "improvement") return "avg_session_rounds";
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
async function getMetricValue(key, days = 7) {
|
|
43
|
+
if (key.startsWith("cmd_fail_rate:")) {
|
|
44
|
+
const cmd = key.replace("cmd_fail_rate:", "");
|
|
45
|
+
const summary = await getEventsSummary(days);
|
|
46
|
+
if (cmd === "all") {
|
|
47
|
+
let total = 0, fail = 0;
|
|
48
|
+
for (const stats2 of Object.values(summary.cmdCounts)) {
|
|
49
|
+
total += stats2.total;
|
|
50
|
+
fail += stats2.fail;
|
|
51
|
+
}
|
|
52
|
+
return total > 0 ? Math.round(fail / total * 1e3) / 10 : 0;
|
|
53
|
+
}
|
|
54
|
+
const stats = summary.cmdCounts[cmd];
|
|
55
|
+
if (!stats || stats.total === 0) return 0;
|
|
56
|
+
return Math.round(stats.fail / stats.total * 1e3) / 10;
|
|
57
|
+
}
|
|
58
|
+
if (key === "error_count") {
|
|
59
|
+
const summary = await getEventsSummary(days);
|
|
60
|
+
return summary.errors.reduce((s, e) => s + e.count, 0);
|
|
61
|
+
}
|
|
62
|
+
if (key === "avg_session_rounds") {
|
|
63
|
+
const summary = await getEventsSummary(days);
|
|
64
|
+
return summary.avgSessionRounds;
|
|
65
|
+
}
|
|
66
|
+
if (key === "test_pass_rate") {
|
|
67
|
+
const { checkTests } = await import("./quality-ST7PPNFR.js");
|
|
68
|
+
const result = await checkTests(process.cwd());
|
|
69
|
+
return result.value ?? null;
|
|
70
|
+
}
|
|
71
|
+
if (key === "todo_count") {
|
|
72
|
+
const { checkTodos } = await import("./quality-ST7PPNFR.js");
|
|
73
|
+
const result = await checkTodos(process.cwd());
|
|
74
|
+
return result.value ?? null;
|
|
75
|
+
}
|
|
76
|
+
if (key === "package_size") {
|
|
77
|
+
const { checkPackageSize } = await import("./quality-ST7PPNFR.js");
|
|
78
|
+
const result = await checkPackageSize(process.cwd());
|
|
79
|
+
return result.value ?? null;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
async function recordBaseline(itemId) {
|
|
84
|
+
const item = await getItem(itemId);
|
|
85
|
+
if (!item) return null;
|
|
86
|
+
if (item.metrics.baselineKey && item.metrics.baselineValue !== null) {
|
|
87
|
+
return { key: item.metrics.baselineKey, value: item.metrics.baselineValue };
|
|
88
|
+
}
|
|
89
|
+
const key = inferBaselineKey(item);
|
|
90
|
+
if (!key) return null;
|
|
91
|
+
const value = await getMetricValue(key);
|
|
92
|
+
if (value === null) return null;
|
|
93
|
+
const metrics = {
|
|
94
|
+
...item.metrics,
|
|
95
|
+
baselineKey: key,
|
|
96
|
+
baselineValue: value,
|
|
97
|
+
baselineDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98
|
+
verified: false
|
|
99
|
+
};
|
|
100
|
+
const updated = await updateItemMetrics(itemId, metrics);
|
|
101
|
+
if (!updated) return null;
|
|
102
|
+
return { key, value };
|
|
103
|
+
}
|
|
104
|
+
async function verifyCompletedItems() {
|
|
105
|
+
const items = await listItems({ status: "done" });
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const item of items) {
|
|
108
|
+
if (!item.metrics.baselineKey || item.metrics.baselineValue === null) continue;
|
|
109
|
+
if (item.metrics.verified) continue;
|
|
110
|
+
const currentValue = await getMetricValue(item.metrics.baselineKey);
|
|
111
|
+
let changePercent = null;
|
|
112
|
+
let verdict = "no-data";
|
|
113
|
+
let detail = "";
|
|
114
|
+
if (currentValue === null) {
|
|
115
|
+
verdict = "no-data";
|
|
116
|
+
detail = `\u65E0\u6CD5\u83B7\u53D6 ${item.metrics.baselineKey} \u7684\u5F53\u524D\u503C`;
|
|
117
|
+
} else {
|
|
118
|
+
const baseline = item.metrics.baselineValue;
|
|
119
|
+
if (baseline === 0) {
|
|
120
|
+
changePercent = currentValue === 0 ? 0 : 100;
|
|
121
|
+
verdict = currentValue <= 0 ? "effective" : "pending";
|
|
122
|
+
detail = `\u57FA\u7EBF: 0 \u2192 \u5F53\u524D: ${currentValue}`;
|
|
123
|
+
} else {
|
|
124
|
+
changePercent = Math.round((currentValue - baseline) / Math.abs(baseline) * 1e3) / 10;
|
|
125
|
+
const lowerIsBetter = isLowerBetter(item.metrics.baselineKey);
|
|
126
|
+
if (lowerIsBetter) {
|
|
127
|
+
if (changePercent <= -50) {
|
|
128
|
+
verdict = "effective";
|
|
129
|
+
detail = `\u4E0B\u964D ${Math.abs(changePercent)}% (${baseline} \u2192 ${currentValue})`;
|
|
130
|
+
} else if (changePercent <= -20) {
|
|
131
|
+
verdict = "pending";
|
|
132
|
+
detail = `\u4E0B\u964D ${Math.abs(changePercent)}%\uFF0C\u5F85\u8FDB\u4E00\u6B65\u89C2\u5BDF (${baseline} \u2192 ${currentValue})`;
|
|
133
|
+
} else {
|
|
134
|
+
verdict = "ineffective";
|
|
135
|
+
detail = changePercent >= 0 ? `\u672A\u6539\u5584\uFF0C\u53CD\u5347 ${changePercent}% (${baseline} \u2192 ${currentValue})` : `\u4EC5\u4E0B\u964D ${Math.abs(changePercent)}% (${baseline} \u2192 ${currentValue})`;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
if (changePercent >= 50) {
|
|
139
|
+
verdict = "effective";
|
|
140
|
+
detail = `\u4E0A\u5347 ${changePercent}% (${baseline} \u2192 ${currentValue})`;
|
|
141
|
+
} else if (changePercent >= 20) {
|
|
142
|
+
verdict = "pending";
|
|
143
|
+
detail = `\u4E0A\u5347 ${changePercent}%\uFF0C\u5F85\u8FDB\u4E00\u6B65\u89C2\u5BDF (${baseline} \u2192 ${currentValue})`;
|
|
144
|
+
} else {
|
|
145
|
+
verdict = "ineffective";
|
|
146
|
+
detail = changePercent <= 0 ? `\u672A\u6539\u5584\uFF0C\u53CD\u964D ${Math.abs(changePercent)}% (${baseline} \u2192 ${currentValue})` : `\u4EC5\u4E0A\u5347 ${changePercent}% (${baseline} \u2192 ${currentValue})`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
item.metrics.afterValue = currentValue;
|
|
151
|
+
item.metrics.afterDate = (/* @__PURE__ */ new Date()).toISOString();
|
|
152
|
+
if (verdict === "effective" || verdict === "ineffective") {
|
|
153
|
+
item.metrics.verified = true;
|
|
154
|
+
}
|
|
155
|
+
await updateItemMetrics(item.id, item.metrics);
|
|
156
|
+
}
|
|
157
|
+
results.push({
|
|
158
|
+
itemId: item.id,
|
|
159
|
+
title: item.title,
|
|
160
|
+
baselineKey: item.metrics.baselineKey,
|
|
161
|
+
baselineValue: item.metrics.baselineValue,
|
|
162
|
+
afterValue: currentValue,
|
|
163
|
+
changePercent,
|
|
164
|
+
verdict,
|
|
165
|
+
detail
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const summary = {
|
|
169
|
+
total: results.length,
|
|
170
|
+
effective: results.filter((r) => r.verdict === "effective").length,
|
|
171
|
+
ineffective: results.filter((r) => r.verdict === "ineffective").length,
|
|
172
|
+
pending: results.filter((r) => r.verdict === "pending").length,
|
|
173
|
+
noData: results.filter((r) => r.verdict === "no-data").length
|
|
174
|
+
};
|
|
175
|
+
return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), results, summary };
|
|
176
|
+
}
|
|
177
|
+
function isLowerBetter(key) {
|
|
178
|
+
if (key.startsWith("cmd_fail_rate")) return true;
|
|
179
|
+
if (key === "error_count") return true;
|
|
180
|
+
if (key === "todo_count") return true;
|
|
181
|
+
if (key === "package_size") return true;
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
inferBaselineKey,
|
|
187
|
+
getMetricValue,
|
|
188
|
+
recordBaseline,
|
|
189
|
+
verifyCompletedItems
|
|
190
|
+
};
|