@zhongqian97-code/ecode 0.5.32 → 0.5.34
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/dist/chunk-GR5MASXF.js +60 -0
- package/dist/chunk-JG2IGHYY.js +46 -0
- package/dist/chunk-O4YFKL3N.js +265 -0
- package/dist/chunk-VM35XIBY.js +1951 -0
- package/dist/index.js +228 -7084
- package/dist/ui-VPHPVIS5.js +2994 -0
- package/dist/web-Y5CK2WBF.js +1870 -0
- package/package.json +2 -2
|
@@ -0,0 +1,2994 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a){if((w?.message??w)?.includes?.('punycode'))return;_ew(w,...a);};
|
|
3
|
+
import {
|
|
4
|
+
APPLY_PATCH_TOOL,
|
|
5
|
+
BASH_TOOL,
|
|
6
|
+
EDIT_TOOL,
|
|
7
|
+
GLOB_TOOL,
|
|
8
|
+
GREP_TOOL,
|
|
9
|
+
READ_TOOL,
|
|
10
|
+
TASK_TOOL,
|
|
11
|
+
TODO_TOOL,
|
|
12
|
+
WEB_FETCH_TOOL,
|
|
13
|
+
WRITE_TOOL,
|
|
14
|
+
applyPatch,
|
|
15
|
+
createLogger,
|
|
16
|
+
createProvider,
|
|
17
|
+
editFile,
|
|
18
|
+
executeBash,
|
|
19
|
+
globFiles,
|
|
20
|
+
grepFiles,
|
|
21
|
+
handleBashTool,
|
|
22
|
+
handleTaskTool,
|
|
23
|
+
isTrustedSkillPath,
|
|
24
|
+
readFile,
|
|
25
|
+
resolveActiveProfile,
|
|
26
|
+
todo,
|
|
27
|
+
webFetch,
|
|
28
|
+
writeFile
|
|
29
|
+
} from "./chunk-VM35XIBY.js";
|
|
30
|
+
import {
|
|
31
|
+
loadJobs,
|
|
32
|
+
removeJob,
|
|
33
|
+
upsertJob
|
|
34
|
+
} from "./chunk-JG2IGHYY.js";
|
|
35
|
+
import {
|
|
36
|
+
createSessionMetadata,
|
|
37
|
+
generateTitle,
|
|
38
|
+
getContextLimit,
|
|
39
|
+
updateSessionMetadata,
|
|
40
|
+
writeSessionMetadata
|
|
41
|
+
} from "./chunk-O4YFKL3N.js";
|
|
42
|
+
|
|
43
|
+
// src/ui/index.ts
|
|
44
|
+
import { default as default2 } from "react";
|
|
45
|
+
import { render } from "ink";
|
|
46
|
+
|
|
47
|
+
// src/ui/App.tsx
|
|
48
|
+
import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3, useMemo } from "react";
|
|
49
|
+
import { Box as Box6, useInput as useInput2, useStdout, useStdin } from "ink";
|
|
50
|
+
|
|
51
|
+
// src/skills/resolver.ts
|
|
52
|
+
function isSkillCommand(input) {
|
|
53
|
+
return input.length > 1 && input.startsWith("/");
|
|
54
|
+
}
|
|
55
|
+
function parseSkillCommand(input) {
|
|
56
|
+
const withoutSlash = input.slice(1);
|
|
57
|
+
const spaceIndex = withoutSlash.search(/\s/);
|
|
58
|
+
if (spaceIndex === -1) {
|
|
59
|
+
return { name: withoutSlash, args: "" };
|
|
60
|
+
}
|
|
61
|
+
const name = withoutSlash.slice(0, spaceIndex);
|
|
62
|
+
const args = withoutSlash.slice(spaceIndex).trim();
|
|
63
|
+
return { name, args };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/skills/handler.ts
|
|
67
|
+
function handleSkillInput(input, registry) {
|
|
68
|
+
if (!isSkillCommand(input)) return { type: "passthrough" };
|
|
69
|
+
const { name, args } = parseSkillCommand(input);
|
|
70
|
+
const skill = registry.find(name);
|
|
71
|
+
if (!skill) {
|
|
72
|
+
const available = registry.list().map((s) => s.name).join(", ") || "none";
|
|
73
|
+
return {
|
|
74
|
+
type: "error",
|
|
75
|
+
message: `Unknown skill: /${name}. Available: ${available}`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const content = args ? `${skill.body}
|
|
79
|
+
|
|
80
|
+
${args}` : skill.body;
|
|
81
|
+
return { type: "skill", message: { role: "user", content }, skill };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/skills/executor.ts
|
|
85
|
+
import { exec } from "child_process";
|
|
86
|
+
import { dirname } from "path";
|
|
87
|
+
import { promisify } from "util";
|
|
88
|
+
var execAsync = promisify(exec);
|
|
89
|
+
var SecurityError = class extends Error {
|
|
90
|
+
constructor(message) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = "SecurityError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
async function runScript(scriptPath, args) {
|
|
96
|
+
const quotedArgs = args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
97
|
+
const cmd = quotedArgs ? `${scriptPath} ${quotedArgs}` : scriptPath;
|
|
98
|
+
const { stdout } = await execAsync(cmd);
|
|
99
|
+
return stdout;
|
|
100
|
+
}
|
|
101
|
+
async function executePreScript(skill, trustedDirs) {
|
|
102
|
+
if (!skill.preScript) {
|
|
103
|
+
throw new Error("No pre script configured for this skill");
|
|
104
|
+
}
|
|
105
|
+
const skillDir = dirname(skill.source);
|
|
106
|
+
if (!isTrustedSkillPath(skillDir, trustedDirs)) {
|
|
107
|
+
throw new SecurityError(
|
|
108
|
+
`Untrusted skill path: ${skillDir} is not in trusted dirs`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return runScript(skill.preScript, []);
|
|
112
|
+
}
|
|
113
|
+
async function executeSkillTool(tool, args, trustedDirs) {
|
|
114
|
+
const scriptDir = dirname(tool.scriptPath);
|
|
115
|
+
if (!isTrustedSkillPath(scriptDir, trustedDirs)) {
|
|
116
|
+
throw new SecurityError(
|
|
117
|
+
`Untrusted tool script path: ${tool.scriptPath}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return runScript(tool.scriptPath, [JSON.stringify(args)]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/ui/StatusBar.tsx
|
|
124
|
+
import { useEffect, useState } from "react";
|
|
125
|
+
import { Box, Text } from "ink";
|
|
126
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
127
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
128
|
+
var SPINNER_INTERVAL_MS = 80;
|
|
129
|
+
function formatTokenCount(n) {
|
|
130
|
+
if (n < 1e3) return String(n);
|
|
131
|
+
return (n / 1e3).toFixed(1) + "k";
|
|
132
|
+
}
|
|
133
|
+
function buildStatusLabel(status, toolName, confirmPrompt) {
|
|
134
|
+
if (confirmPrompt) {
|
|
135
|
+
return `[y/n] ${confirmPrompt}`;
|
|
136
|
+
}
|
|
137
|
+
switch (status) {
|
|
138
|
+
case "thinking":
|
|
139
|
+
return "\u601D\u8003\u4E2D";
|
|
140
|
+
case "tool_calling":
|
|
141
|
+
return `\u8C03\u7528\u5DE5\u5177: ${toolName ?? ""}`;
|
|
142
|
+
case "awaiting_confirm":
|
|
143
|
+
return "\u7B49\u5F85\u786E\u8BA4";
|
|
144
|
+
case "idle":
|
|
145
|
+
default:
|
|
146
|
+
return "\u7B49\u5F85\u8F93\u5165";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function statusIcon(status, confirmPrompt) {
|
|
150
|
+
if (confirmPrompt) return "";
|
|
151
|
+
switch (status) {
|
|
152
|
+
case "thinking":
|
|
153
|
+
return "\u27F3";
|
|
154
|
+
case "tool_calling":
|
|
155
|
+
return "\u2699";
|
|
156
|
+
case "awaiting_confirm":
|
|
157
|
+
case "idle":
|
|
158
|
+
default:
|
|
159
|
+
return "\u25CE";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function StatusBar({
|
|
163
|
+
status,
|
|
164
|
+
toolName,
|
|
165
|
+
confirmPrompt,
|
|
166
|
+
version,
|
|
167
|
+
tokenUsage
|
|
168
|
+
}) {
|
|
169
|
+
const [spinnerIdx, setSpinnerIdx] = useState(0);
|
|
170
|
+
const isAnimating = !confirmPrompt && (status === "thinking" || status === "tool_calling");
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!isAnimating) return;
|
|
173
|
+
const timer = setInterval(() => {
|
|
174
|
+
setSpinnerIdx((prev) => (prev + 1) % SPINNER_FRAMES.length);
|
|
175
|
+
}, SPINNER_INTERVAL_MS);
|
|
176
|
+
return () => clearInterval(timer);
|
|
177
|
+
}, [isAnimating]);
|
|
178
|
+
const label = buildStatusLabel(status, toolName, confirmPrompt);
|
|
179
|
+
const icon = statusIcon(status, confirmPrompt);
|
|
180
|
+
const spinner = isAnimating ? SPINNER_FRAMES[spinnerIdx] : " ";
|
|
181
|
+
const usedStr = formatTokenCount(tokenUsage.used);
|
|
182
|
+
const limitStr = formatTokenCount(tokenUsage.limit);
|
|
183
|
+
const ctxStr = `ctx: ${tokenUsage.estimated ? "~" : ""}${usedStr}/${limitStr}`;
|
|
184
|
+
const versionStr = `v${version}`;
|
|
185
|
+
return (
|
|
186
|
+
// 外层 Box:行方向布局,justifyContent="space-between" 实现左右分列
|
|
187
|
+
// 避免用 string.length 手动计算填充(CJK 双宽字符会导致计算偏差)
|
|
188
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
189
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
190
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
191
|
+
spinner,
|
|
192
|
+
" "
|
|
193
|
+
] }),
|
|
194
|
+
confirmPrompt ? (
|
|
195
|
+
// 确认模式:整体用黄色显示,吸引用户注意
|
|
196
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
197
|
+
icon,
|
|
198
|
+
label
|
|
199
|
+
] })
|
|
200
|
+
) : status === "thinking" || status === "tool_calling" ? (
|
|
201
|
+
// 进行中状态:图标用绿色(活跃感),标签用白色(可读性)
|
|
202
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
203
|
+
/* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
204
|
+
icon,
|
|
205
|
+
" "
|
|
206
|
+
] }),
|
|
207
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: label })
|
|
208
|
+
] })
|
|
209
|
+
) : (
|
|
210
|
+
// 空闲 / 等待确认过渡态:整体淡灰,降低视觉权重
|
|
211
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
212
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
213
|
+
icon,
|
|
214
|
+
" "
|
|
215
|
+
] }),
|
|
216
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: label })
|
|
217
|
+
] })
|
|
218
|
+
)
|
|
219
|
+
] }),
|
|
220
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
221
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
222
|
+
versionStr,
|
|
223
|
+
" "
|
|
224
|
+
] }),
|
|
225
|
+
/* @__PURE__ */ jsx(Text, { color: tokenUsage.estimated ? "yellow" : "cyan", children: ctxStr })
|
|
226
|
+
] })
|
|
227
|
+
] })
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/ui/ConversationHistory.tsx
|
|
232
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
233
|
+
|
|
234
|
+
// src/ui/messageLines.ts
|
|
235
|
+
var TOOL_RESULT_MAX_LINES = 3;
|
|
236
|
+
function wrapText(text, terminalWidth) {
|
|
237
|
+
const logicalLines = text.split("\n");
|
|
238
|
+
if (terminalWidth <= 0) return logicalLines;
|
|
239
|
+
const result = [];
|
|
240
|
+
for (const line of logicalLines) {
|
|
241
|
+
if (line.length === 0) {
|
|
242
|
+
result.push("");
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
for (let i = 0; i < line.length; i += terminalWidth) {
|
|
246
|
+
result.push(line.slice(i, i + terminalWidth));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function messageToLines(msg, expandTools, terminalWidth) {
|
|
252
|
+
if (msg.role === "user") {
|
|
253
|
+
return wrapText(`> ${msg.content}`, terminalWidth).map((text) => ({
|
|
254
|
+
text,
|
|
255
|
+
color: "white"
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
if (msg.role === "assistant") {
|
|
259
|
+
const lines = [];
|
|
260
|
+
const aMsg = msg;
|
|
261
|
+
if (aMsg.reasoning_content && aMsg.reasoning_content.length > 0) {
|
|
262
|
+
if (expandTools) {
|
|
263
|
+
lines.push({ text: "<thinking>", color: "gray" });
|
|
264
|
+
for (const t of wrapText(aMsg.reasoning_content, terminalWidth)) {
|
|
265
|
+
lines.push({ text: t, dimColor: true });
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
lines.push({ text: "[+ thinking]", color: "gray" });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (aMsg.content && aMsg.content.trim().length > 0) {
|
|
272
|
+
for (const t of wrapText(aMsg.content, terminalWidth)) {
|
|
273
|
+
lines.push({ text: t, color: "cyan" });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (aMsg.tool_calls && aMsg.tool_calls.length > 0) {
|
|
277
|
+
if (expandTools) {
|
|
278
|
+
for (const tc of aMsg.tool_calls) {
|
|
279
|
+
let argsDisplay = tc.function.arguments;
|
|
280
|
+
try {
|
|
281
|
+
const parsed = JSON.parse(tc.function.arguments);
|
|
282
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
283
|
+
argsDisplay = Object.values(parsed).map(String).join(", ");
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
const lineText = `\u2699 \u8C03\u7528\u5DE5\u5177: ${tc.function.name}(${argsDisplay})`;
|
|
288
|
+
for (const t of wrapText(lineText, terminalWidth)) {
|
|
289
|
+
lines.push({ text: t, color: "yellow" });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
lines.push({
|
|
294
|
+
text: `[+ ${aMsg.tool_calls.length} \u4E2A\u5DE5\u5177\u8C03\u7528]`,
|
|
295
|
+
color: "gray"
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return lines.length > 0 ? lines : [{ text: "" }];
|
|
300
|
+
}
|
|
301
|
+
if (msg.role === "tool") {
|
|
302
|
+
if (!expandTools) {
|
|
303
|
+
return [{ text: "[+ \u5DE5\u5177\u7ED3\u679C]", color: "gray" }];
|
|
304
|
+
}
|
|
305
|
+
const rawLines = msg.content.split("\n");
|
|
306
|
+
const truncated = rawLines.length > TOOL_RESULT_MAX_LINES ? rawLines.slice(0, TOOL_RESULT_MAX_LINES - 1).join("\n") + "\n\u2026" : msg.content;
|
|
307
|
+
return [
|
|
308
|
+
{ text: "[\u5DE5\u5177\u7ED3\u679C]", color: "gray" },
|
|
309
|
+
...wrapText(truncated, terminalWidth).map((text) => ({
|
|
310
|
+
text,
|
|
311
|
+
color: "gray"
|
|
312
|
+
}))
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/ui/ConversationHistory.tsx
|
|
319
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
320
|
+
function ConversationHistory({
|
|
321
|
+
messages,
|
|
322
|
+
maxHeight = 20,
|
|
323
|
+
expandTools = false,
|
|
324
|
+
terminalWidth = 0,
|
|
325
|
+
scrollOffset = 0
|
|
326
|
+
}) {
|
|
327
|
+
const visible = messages.filter(
|
|
328
|
+
(m) => m.role !== "system"
|
|
329
|
+
);
|
|
330
|
+
const allLines = visible.flatMap(
|
|
331
|
+
(msg) => messageToLines(msg, expandTools, terminalWidth)
|
|
332
|
+
);
|
|
333
|
+
const totalLines = allLines.length;
|
|
334
|
+
const end = Math.max(0, Math.min(totalLines, totalLines - scrollOffset));
|
|
335
|
+
let start = Math.max(0, end - maxHeight);
|
|
336
|
+
let linesAbove = start;
|
|
337
|
+
if (linesAbove > 0 && maxHeight > 1) {
|
|
338
|
+
start = Math.max(0, end - (maxHeight - 1));
|
|
339
|
+
linesAbove = start;
|
|
340
|
+
}
|
|
341
|
+
const visibleLines = allLines.slice(start, end);
|
|
342
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
343
|
+
linesAbove > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
|
|
344
|
+
"\u2191 ",
|
|
345
|
+
linesAbove,
|
|
346
|
+
" \u884C \xB7 PageUp/Down \u6EDA\u52A8"
|
|
347
|
+
] }),
|
|
348
|
+
visibleLines.map((line, idx) => /* @__PURE__ */ jsx2(Text2, { color: line.color, dimColor: line.dimColor, children: line.text }, idx))
|
|
349
|
+
] });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/ui/Input.tsx
|
|
353
|
+
import { useState as useState2, useEffect as useEffect2, useRef, forwardRef, useImperativeHandle } from "react";
|
|
354
|
+
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
355
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
356
|
+
var CURSOR_CHAR = "\u258C";
|
|
357
|
+
var BLINK_INTERVAL_MS = 530;
|
|
358
|
+
function wordBackward(s, pos) {
|
|
359
|
+
let i = pos;
|
|
360
|
+
while (i > 0 && s[i - 1] === " ") {
|
|
361
|
+
i--;
|
|
362
|
+
}
|
|
363
|
+
while (i > 0 && s[i - 1] !== " ") {
|
|
364
|
+
i--;
|
|
365
|
+
}
|
|
366
|
+
return i;
|
|
367
|
+
}
|
|
368
|
+
function wordForward(s, pos) {
|
|
369
|
+
let i = pos;
|
|
370
|
+
const len = s.length;
|
|
371
|
+
while (i < len && s[i] !== " ") {
|
|
372
|
+
i++;
|
|
373
|
+
}
|
|
374
|
+
while (i < len && s[i] === " ") {
|
|
375
|
+
i++;
|
|
376
|
+
}
|
|
377
|
+
return i;
|
|
378
|
+
}
|
|
379
|
+
var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placeholder }, ref) {
|
|
380
|
+
const [value, setValue] = useState2("");
|
|
381
|
+
const [cursorPos, setCursorPos] = useState2(0);
|
|
382
|
+
const [cursorVisible, setCursorVisible] = useState2(true);
|
|
383
|
+
const valueRef = useRef(value);
|
|
384
|
+
valueRef.current = value;
|
|
385
|
+
const cursorPosRef = useRef(cursorPos);
|
|
386
|
+
cursorPosRef.current = cursorPos;
|
|
387
|
+
const onChangeRef = useRef(onChange);
|
|
388
|
+
onChangeRef.current = onChange;
|
|
389
|
+
const onSubmitRef = useRef(onSubmit);
|
|
390
|
+
onSubmitRef.current = onSubmit;
|
|
391
|
+
useImperativeHandle(ref, () => ({
|
|
392
|
+
fill(text) {
|
|
393
|
+
var _a;
|
|
394
|
+
valueRef.current = text;
|
|
395
|
+
cursorPosRef.current = text.length;
|
|
396
|
+
setValue(text);
|
|
397
|
+
setCursorPos(text.length);
|
|
398
|
+
(_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, text);
|
|
399
|
+
}
|
|
400
|
+
}));
|
|
401
|
+
useEffect2(() => {
|
|
402
|
+
if (!isActive) {
|
|
403
|
+
setCursorVisible(true);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const timer = setInterval(() => {
|
|
407
|
+
setCursorVisible((prev) => !prev);
|
|
408
|
+
}, BLINK_INTERVAL_MS);
|
|
409
|
+
return () => {
|
|
410
|
+
clearInterval(timer);
|
|
411
|
+
};
|
|
412
|
+
}, [isActive]);
|
|
413
|
+
function setValueSync(newValue) {
|
|
414
|
+
valueRef.current = newValue;
|
|
415
|
+
setValue(newValue);
|
|
416
|
+
}
|
|
417
|
+
function setCursorPosSync(newPos) {
|
|
418
|
+
cursorPosRef.current = newPos;
|
|
419
|
+
setCursorPos(newPos);
|
|
420
|
+
}
|
|
421
|
+
useInput(
|
|
422
|
+
(input, key) => {
|
|
423
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
424
|
+
const v = valueRef.current;
|
|
425
|
+
const pos = cursorPosRef.current;
|
|
426
|
+
if (key.return && key.shift) {
|
|
427
|
+
const newValue = v.slice(0, pos) + "\n" + v.slice(pos);
|
|
428
|
+
setValueSync(newValue);
|
|
429
|
+
setCursorPosSync(pos + 1);
|
|
430
|
+
(_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, newValue);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (key.return) {
|
|
434
|
+
onSubmitRef.current(v);
|
|
435
|
+
setValueSync("");
|
|
436
|
+
setCursorPosSync(0);
|
|
437
|
+
(_b = onChangeRef.current) == null ? void 0 : _b.call(onChangeRef, "");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (key.backspace || key.delete) {
|
|
441
|
+
if (pos === 0) return;
|
|
442
|
+
const newValue = v.slice(0, pos - 1) + v.slice(pos);
|
|
443
|
+
setValueSync(newValue);
|
|
444
|
+
setCursorPosSync(pos - 1);
|
|
445
|
+
(_c = onChangeRef.current) == null ? void 0 : _c.call(onChangeRef, newValue);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (key.ctrl) {
|
|
449
|
+
switch (input) {
|
|
450
|
+
case "a": {
|
|
451
|
+
setCursorPosSync(0);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
case "e": {
|
|
455
|
+
setCursorPosSync(v.length);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
case "b": {
|
|
459
|
+
setCursorPosSync(Math.max(0, pos - 1));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
case "f": {
|
|
463
|
+
setCursorPosSync(Math.min(v.length, pos + 1));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
case "k": {
|
|
467
|
+
const nextNl = v.indexOf("\n", pos);
|
|
468
|
+
const lineEnd = nextNl === -1 ? v.length : nextNl;
|
|
469
|
+
const newValue = v.slice(0, pos) + v.slice(lineEnd);
|
|
470
|
+
setValueSync(newValue);
|
|
471
|
+
(_d = onChangeRef.current) == null ? void 0 : _d.call(onChangeRef, newValue);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
case "u": {
|
|
475
|
+
const newValue = v.slice(pos);
|
|
476
|
+
setValueSync(newValue);
|
|
477
|
+
setCursorPosSync(0);
|
|
478
|
+
(_e = onChangeRef.current) == null ? void 0 : _e.call(onChangeRef, newValue);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
case "w": {
|
|
482
|
+
const newPos = wordBackward(v, pos);
|
|
483
|
+
const newValue = v.slice(0, newPos) + v.slice(pos);
|
|
484
|
+
setValueSync(newValue);
|
|
485
|
+
setCursorPosSync(newPos);
|
|
486
|
+
(_f = onChangeRef.current) == null ? void 0 : _f.call(onChangeRef, newValue);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
case "d": {
|
|
490
|
+
if (pos >= v.length) return;
|
|
491
|
+
const newValue = v.slice(0, pos) + v.slice(pos + 1);
|
|
492
|
+
setValueSync(newValue);
|
|
493
|
+
(_g = onChangeRef.current) == null ? void 0 : _g.call(onChangeRef, newValue);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
default:
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (key.meta) {
|
|
501
|
+
if (input === "b") {
|
|
502
|
+
setCursorPosSync(wordBackward(v, pos));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (input === "f") {
|
|
506
|
+
setCursorPosSync(wordForward(v, pos));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (key.leftArrow) {
|
|
512
|
+
setCursorPosSync(Math.max(0, pos - 1));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (key.rightArrow) {
|
|
516
|
+
setCursorPosSync(Math.min(v.length, pos + 1));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (key.escape || key.upArrow || key.downArrow || key.tab || key.pageUp || key.pageDown) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (input.length > 0) {
|
|
523
|
+
const newValue = v.slice(0, pos) + input + v.slice(pos);
|
|
524
|
+
setValueSync(newValue);
|
|
525
|
+
setCursorPosSync(pos + input.length);
|
|
526
|
+
(_h = onChangeRef.current) == null ? void 0 : _h.call(onChangeRef, newValue);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
{ isActive }
|
|
530
|
+
);
|
|
531
|
+
const isEmpty = value === "";
|
|
532
|
+
const renderLines = () => {
|
|
533
|
+
if (isEmpty && placeholder) {
|
|
534
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
535
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "> " }),
|
|
536
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: placeholder }),
|
|
537
|
+
isActive && cursorVisible && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR })
|
|
538
|
+
] });
|
|
539
|
+
}
|
|
540
|
+
const lines = value.split("\n");
|
|
541
|
+
let remaining = cursorPos;
|
|
542
|
+
let cursorLine = 0;
|
|
543
|
+
let cursorCol = 0;
|
|
544
|
+
for (let i = 0; i < lines.length; i++) {
|
|
545
|
+
const lineLen = lines[i].length;
|
|
546
|
+
if (remaining <= lineLen) {
|
|
547
|
+
cursorLine = i;
|
|
548
|
+
cursorCol = remaining;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
remaining -= lineLen + 1;
|
|
552
|
+
}
|
|
553
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: lines.map((line, idx) => {
|
|
554
|
+
const prefix = idx === 0 ? "> " : " ";
|
|
555
|
+
const showCursor = isActive && cursorVisible && idx === cursorLine;
|
|
556
|
+
if (!showCursor) {
|
|
557
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
558
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
|
|
559
|
+
/* @__PURE__ */ jsx3(Text3, { children: line })
|
|
560
|
+
] }, idx);
|
|
561
|
+
}
|
|
562
|
+
const before = line.slice(0, cursorCol);
|
|
563
|
+
const after = line.slice(cursorCol);
|
|
564
|
+
if (after.length > 0) {
|
|
565
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
566
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
|
|
567
|
+
/* @__PURE__ */ jsx3(Text3, { children: before }),
|
|
568
|
+
/* @__PURE__ */ jsx3(Text3, { inverse: true, children: after[0] }),
|
|
569
|
+
/* @__PURE__ */ jsx3(Text3, { children: after.slice(1) })
|
|
570
|
+
] }, idx);
|
|
571
|
+
}
|
|
572
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
573
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
|
|
574
|
+
/* @__PURE__ */ jsx3(Text3, { children: before }),
|
|
575
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR })
|
|
576
|
+
] }, idx);
|
|
577
|
+
}) });
|
|
578
|
+
};
|
|
579
|
+
return /* @__PURE__ */ jsx3(Box3, { children: isActive ? renderLines() : /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
580
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "> " }),
|
|
581
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isEmpty ? placeholder ?? "" : value })
|
|
582
|
+
] }) });
|
|
583
|
+
});
|
|
584
|
+
var Input_default = Input;
|
|
585
|
+
|
|
586
|
+
// src/ui/SkillAutocomplete.tsx
|
|
587
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
588
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
589
|
+
function SkillAutocomplete({
|
|
590
|
+
suggestions,
|
|
591
|
+
selectedIndex,
|
|
592
|
+
isOpen: isOpen3
|
|
593
|
+
}) {
|
|
594
|
+
if (!isOpen3 || suggestions.length === 0) {
|
|
595
|
+
return /* @__PURE__ */ jsx4(Fragment2, {});
|
|
596
|
+
}
|
|
597
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", children: suggestions.map((skill, idx) => {
|
|
598
|
+
const selected = idx === selectedIndex;
|
|
599
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
600
|
+
/* @__PURE__ */ jsx4(Text4, { color: selected ? "cyan" : void 0, bold: selected, children: selected ? "> " : " " }),
|
|
601
|
+
/* @__PURE__ */ jsxs4(Text4, { color: selected ? "cyan" : void 0, bold: selected, children: [
|
|
602
|
+
"/",
|
|
603
|
+
skill.name
|
|
604
|
+
] }),
|
|
605
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
606
|
+
" \u2014 ",
|
|
607
|
+
skill.description
|
|
608
|
+
] })
|
|
609
|
+
] }, skill.name);
|
|
610
|
+
}) });
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/ui/FileAutocomplete.tsx
|
|
614
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
615
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
616
|
+
function FileAutocomplete({
|
|
617
|
+
suggestions,
|
|
618
|
+
selectedIndex,
|
|
619
|
+
isOpen: isOpen3
|
|
620
|
+
}) {
|
|
621
|
+
if (!isOpen3 || suggestions.length === 0) {
|
|
622
|
+
return /* @__PURE__ */ jsx5(Fragment3, {});
|
|
623
|
+
}
|
|
624
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", children: suggestions.map((entry, idx) => {
|
|
625
|
+
const selected = idx === selectedIndex;
|
|
626
|
+
const label = entry.isDir ? entry.path + "/" : entry.path;
|
|
627
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
628
|
+
/* @__PURE__ */ jsx5(Text5, { color: selected ? "yellow" : void 0, bold: selected, children: selected ? "> " : " " }),
|
|
629
|
+
/* @__PURE__ */ jsx5(Text5, { color: selected ? "yellow" : void 0, bold: selected, children: label })
|
|
630
|
+
] }, entry.path);
|
|
631
|
+
}) });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/ui/autocompleteLogic.ts
|
|
635
|
+
function getInitialState() {
|
|
636
|
+
return { query: "", selectedIndex: 0, dismissed: false };
|
|
637
|
+
}
|
|
638
|
+
function computeSuggestions(skills, state, maxSuggestions = 5) {
|
|
639
|
+
const { query } = state;
|
|
640
|
+
if (!query.startsWith("/")) return [];
|
|
641
|
+
const term = query.slice(1).toLowerCase();
|
|
642
|
+
if (term.includes(" ")) return [];
|
|
643
|
+
return skills.filter((s) => s.name.toLowerCase().startsWith(term)).slice(0, maxSuggestions);
|
|
644
|
+
}
|
|
645
|
+
function isOpen(state, suggestions) {
|
|
646
|
+
return state.query.startsWith("/") && suggestions.length > 0 && !state.dismissed;
|
|
647
|
+
}
|
|
648
|
+
function handleInputChange(_state, newQuery) {
|
|
649
|
+
return { query: newQuery, selectedIndex: 0, dismissed: false };
|
|
650
|
+
}
|
|
651
|
+
function moveUp(state, count) {
|
|
652
|
+
return { ...state, selectedIndex: (state.selectedIndex - 1 + count) % count };
|
|
653
|
+
}
|
|
654
|
+
function moveDown(state, count) {
|
|
655
|
+
return { ...state, selectedIndex: (state.selectedIndex + 1) % count };
|
|
656
|
+
}
|
|
657
|
+
function dismiss(state) {
|
|
658
|
+
return { ...state, dismissed: true };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/ui/fileCompletion.ts
|
|
662
|
+
import * as fs from "fs/promises";
|
|
663
|
+
import * as path from "path";
|
|
664
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
665
|
+
function isHidden(name) {
|
|
666
|
+
return name.startsWith(".");
|
|
667
|
+
}
|
|
668
|
+
async function walkDir(dir, root, results, maxResults) {
|
|
669
|
+
if (results.length >= maxResults) return;
|
|
670
|
+
let entries;
|
|
671
|
+
try {
|
|
672
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
673
|
+
} catch {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
for (const entry of entries) {
|
|
677
|
+
if (results.length >= maxResults) return;
|
|
678
|
+
if (SKIP_DIRS.has(entry.name) || isHidden(entry.name)) continue;
|
|
679
|
+
const relPath = path.relative(root, path.join(dir, entry.name));
|
|
680
|
+
if (entry.isDirectory()) {
|
|
681
|
+
results.push({ path: relPath, isDir: true });
|
|
682
|
+
await walkDir(path.join(dir, entry.name), root, results, maxResults);
|
|
683
|
+
} else {
|
|
684
|
+
results.push({ path: relPath, isDir: false });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async function listFilesForQuery(query, cwd, maxResults = 50) {
|
|
689
|
+
if (path.isAbsolute(query)) {
|
|
690
|
+
return listAbsolute(query, maxResults);
|
|
691
|
+
}
|
|
692
|
+
const all = [];
|
|
693
|
+
await walkDir(cwd, cwd, all, maxResults * 4);
|
|
694
|
+
const filtered = query ? all.filter((e) => e.path.includes(query)) : all;
|
|
695
|
+
return filtered.slice(0, maxResults);
|
|
696
|
+
}
|
|
697
|
+
async function listAbsolute(query, maxResults) {
|
|
698
|
+
let dir = query;
|
|
699
|
+
let filter = "";
|
|
700
|
+
try {
|
|
701
|
+
const stat2 = await fs.stat(dir);
|
|
702
|
+
if (!stat2.isDirectory()) {
|
|
703
|
+
filter = path.basename(dir);
|
|
704
|
+
dir = path.dirname(dir);
|
|
705
|
+
}
|
|
706
|
+
} catch {
|
|
707
|
+
filter = path.basename(dir);
|
|
708
|
+
dir = path.dirname(dir);
|
|
709
|
+
}
|
|
710
|
+
let entries;
|
|
711
|
+
try {
|
|
712
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
713
|
+
} catch {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
const results = [];
|
|
717
|
+
for (const entry of entries) {
|
|
718
|
+
if (SKIP_DIRS.has(entry.name) || isHidden(entry.name)) continue;
|
|
719
|
+
if (filter && !entry.name.includes(filter)) continue;
|
|
720
|
+
results.push({
|
|
721
|
+
path: path.join(dir, entry.name),
|
|
722
|
+
isDir: entry.isDirectory()
|
|
723
|
+
});
|
|
724
|
+
if (results.length >= maxResults) break;
|
|
725
|
+
}
|
|
726
|
+
return results;
|
|
727
|
+
}
|
|
728
|
+
function extractAtQuery(text) {
|
|
729
|
+
const lastAt = text.lastIndexOf("@");
|
|
730
|
+
if (lastAt === -1) return null;
|
|
731
|
+
const after = text.slice(lastAt + 1);
|
|
732
|
+
const spaceIdx = after.indexOf(" ");
|
|
733
|
+
if (spaceIdx !== -1) return null;
|
|
734
|
+
return after;
|
|
735
|
+
}
|
|
736
|
+
async function expandFileRefs(text, cwd) {
|
|
737
|
+
const atPattern = /@([\w./\-]+)/g;
|
|
738
|
+
const replacements = [];
|
|
739
|
+
let match;
|
|
740
|
+
atPattern.lastIndex = 0;
|
|
741
|
+
while ((match = atPattern.exec(text)) !== null) {
|
|
742
|
+
const filePath = match[1];
|
|
743
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
744
|
+
let replacement;
|
|
745
|
+
try {
|
|
746
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
747
|
+
replacement = `\`\`\`
|
|
748
|
+
// @${filePath}
|
|
749
|
+
${content}
|
|
750
|
+
\`\`\``;
|
|
751
|
+
} catch {
|
|
752
|
+
replacement = `@${filePath} (not found)`;
|
|
753
|
+
}
|
|
754
|
+
replacements.push({ start: match.index, end: match.index + match[0].length, replacement });
|
|
755
|
+
}
|
|
756
|
+
if (replacements.length === 0) return text;
|
|
757
|
+
let result = text;
|
|
758
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
759
|
+
const { start, end, replacement } = replacements[i];
|
|
760
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
761
|
+
}
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/ui/fileAutocompleteLogic.ts
|
|
766
|
+
function getInitialState2() {
|
|
767
|
+
return { query: "", selectedIndex: 0, dismissed: false };
|
|
768
|
+
}
|
|
769
|
+
function handleInputChange2(_state, newText) {
|
|
770
|
+
return { query: newText, selectedIndex: 0, dismissed: false };
|
|
771
|
+
}
|
|
772
|
+
function isOpen2(state, suggestions) {
|
|
773
|
+
if (state.dismissed) return false;
|
|
774
|
+
if (suggestions.length === 0) return false;
|
|
775
|
+
return extractAtQuery(state.query) !== null;
|
|
776
|
+
}
|
|
777
|
+
function moveUp2(state, count) {
|
|
778
|
+
return { ...state, selectedIndex: (state.selectedIndex - 1 + count) % count };
|
|
779
|
+
}
|
|
780
|
+
function moveDown2(state, count) {
|
|
781
|
+
return { ...state, selectedIndex: (state.selectedIndex + 1) % count };
|
|
782
|
+
}
|
|
783
|
+
function dismiss2(state) {
|
|
784
|
+
return { ...state, dismissed: true };
|
|
785
|
+
}
|
|
786
|
+
function confirmSelection(inputText, selectedPath) {
|
|
787
|
+
const lastAt = inputText.lastIndexOf("@");
|
|
788
|
+
if (lastAt === -1) return inputText;
|
|
789
|
+
const prefix = inputText.slice(0, lastAt);
|
|
790
|
+
return `${prefix}@${selectedPath} `;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/ui/mouseInput.ts
|
|
794
|
+
var SGR_MOUSE_RE = /^\x1b\[<(\d+);\d+;\d+[Mm]/;
|
|
795
|
+
function mouseEventLength(data) {
|
|
796
|
+
const s = typeof data === "string" ? data : data.toString("binary");
|
|
797
|
+
if (!s) return 0;
|
|
798
|
+
const sgrMatch = SGR_MOUSE_RE.exec(s);
|
|
799
|
+
if (sgrMatch) return sgrMatch[0].length;
|
|
800
|
+
if (s.length >= 6 && s.charCodeAt(0) === 27 && s[1] === "[" && s[2] === "M") return 6;
|
|
801
|
+
return 0;
|
|
802
|
+
}
|
|
803
|
+
function parseMouseScroll(data) {
|
|
804
|
+
const s = typeof data === "string" ? data : data.toString("binary");
|
|
805
|
+
if (!s) return null;
|
|
806
|
+
const sgrMatch = SGR_MOUSE_RE.exec(s);
|
|
807
|
+
if (sgrMatch) {
|
|
808
|
+
const cb = parseInt(sgrMatch[1], 10);
|
|
809
|
+
if (cb === 64) return { direction: "up" };
|
|
810
|
+
if (cb === 65) return { direction: "down" };
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
if (s.length >= 6 && s.charCodeAt(0) === 27 && s[1] === "[" && s[2] === "M") {
|
|
814
|
+
const buttonByte = s.charCodeAt(3);
|
|
815
|
+
if (buttonByte === 96) return { direction: "up" };
|
|
816
|
+
if (buttonByte === 97) return { direction: "down" };
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/ui/App.tsx
|
|
823
|
+
import { homedir as homedir2 } from "os";
|
|
824
|
+
import { join as join4 } from "path";
|
|
825
|
+
|
|
826
|
+
// src/automation/runtime.ts
|
|
827
|
+
import { randomUUID } from "crypto";
|
|
828
|
+
|
|
829
|
+
// src/automation/log.ts
|
|
830
|
+
import { readFile as readFile3, appendFile, mkdir } from "fs/promises";
|
|
831
|
+
import { join as join2 } from "path";
|
|
832
|
+
function logFilePath(logDir) {
|
|
833
|
+
return join2(logDir, "automation-runs.jsonl");
|
|
834
|
+
}
|
|
835
|
+
async function appendRunLog(logDir, entry) {
|
|
836
|
+
await mkdir(logDir, { recursive: true });
|
|
837
|
+
await appendFile(logFilePath(logDir), JSON.stringify(entry) + "\n", "utf-8");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/automation/runtime.ts
|
|
841
|
+
async function executeJob(job, config) {
|
|
842
|
+
var _a;
|
|
843
|
+
if (((_a = job.budget) == null ? void 0 : _a.maxTurns) !== void 0 && job.runCount >= job.budget.maxTurns) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
847
|
+
const runId = randomUUID();
|
|
848
|
+
let summaryText;
|
|
849
|
+
let errorMsg;
|
|
850
|
+
let result;
|
|
851
|
+
try {
|
|
852
|
+
summaryText = await config.session.run(job.promptTemplate);
|
|
853
|
+
result = "success";
|
|
854
|
+
} catch (err) {
|
|
855
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
856
|
+
result = "failure";
|
|
857
|
+
}
|
|
858
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
859
|
+
const entry = {
|
|
860
|
+
id: runId,
|
|
861
|
+
jobId: job.id,
|
|
862
|
+
startedAt,
|
|
863
|
+
finishedAt,
|
|
864
|
+
promptUsed: job.promptTemplate,
|
|
865
|
+
result,
|
|
866
|
+
summaryText,
|
|
867
|
+
error: errorMsg
|
|
868
|
+
};
|
|
869
|
+
await appendRunLog(config.logDir, entry);
|
|
870
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
871
|
+
const updatedJob = {
|
|
872
|
+
...job,
|
|
873
|
+
runCount: job.runCount + 1,
|
|
874
|
+
failureCount: result === "failure" ? job.failureCount + 1 : job.failureCount,
|
|
875
|
+
lastRunAt: finishedAt,
|
|
876
|
+
lastError: errorMsg,
|
|
877
|
+
updatedAt: now
|
|
878
|
+
};
|
|
879
|
+
await upsertJob(config.dataDir, updatedJob);
|
|
880
|
+
return entry;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/automation/goal/evaluator.ts
|
|
884
|
+
function parseVerdict(response) {
|
|
885
|
+
const scan = (text) => {
|
|
886
|
+
if (/\bnot_done\b/.test(text)) return "not_done";
|
|
887
|
+
if (/\bblocked\b/.test(text) && !/\bnot\s+blocked\b/.test(text)) return "blocked";
|
|
888
|
+
if (/\bdone\b/.test(text) && !/\bnot\s+done\b/.test(text)) return "done";
|
|
889
|
+
return null;
|
|
890
|
+
};
|
|
891
|
+
const firstLine = (response.split("\n")[0] ?? "").trim().toLowerCase();
|
|
892
|
+
const fromFirstLine = scan(firstLine);
|
|
893
|
+
if (fromFirstLine !== null) return fromFirstLine;
|
|
894
|
+
const fromFull = scan(response.toLowerCase());
|
|
895
|
+
return fromFull ?? "not_done";
|
|
896
|
+
}
|
|
897
|
+
function buildPrompt(input) {
|
|
898
|
+
return [
|
|
899
|
+
"You are evaluating whether a goal condition has been met based on a work transcript.",
|
|
900
|
+
"",
|
|
901
|
+
`Goal condition: ${input.condition}`,
|
|
902
|
+
"",
|
|
903
|
+
"Work transcript:",
|
|
904
|
+
input.transcript,
|
|
905
|
+
"",
|
|
906
|
+
'Reply with one of these verdicts: "done", "not_done", or "blocked".',
|
|
907
|
+
'"done" means the condition is satisfied.',
|
|
908
|
+
'"not_done" means the condition is not yet satisfied but work can continue.',
|
|
909
|
+
'"blocked" means progress is impossible due to an error or blocker.',
|
|
910
|
+
"After the verdict, explain your reasoning briefly."
|
|
911
|
+
].join("\n");
|
|
912
|
+
}
|
|
913
|
+
function createLLMEvaluator(session) {
|
|
914
|
+
return {
|
|
915
|
+
async evaluate(input) {
|
|
916
|
+
try {
|
|
917
|
+
const prompt = buildPrompt(input);
|
|
918
|
+
const response = await session.run(prompt);
|
|
919
|
+
const verdict = parseVerdict(response);
|
|
920
|
+
return { verdict, reason: response };
|
|
921
|
+
} catch (error) {
|
|
922
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
923
|
+
return { verdict: "blocked", reason: message };
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/automation/loop/scheduler.ts
|
|
930
|
+
var LoopScheduler = class {
|
|
931
|
+
options;
|
|
932
|
+
/** 按 jobId 索引的调度表 */
|
|
933
|
+
entries = /* @__PURE__ */ new Map();
|
|
934
|
+
constructor(options) {
|
|
935
|
+
this.options = options;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* 初始化调度器,传入初始 job 列表
|
|
939
|
+
* 每个 job 会被立即纳入调度
|
|
940
|
+
*/
|
|
941
|
+
start(jobs) {
|
|
942
|
+
for (const job of jobs) {
|
|
943
|
+
this.scheduleJob(job);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* 停止所有调度,清除全部挂起的 timer
|
|
948
|
+
*/
|
|
949
|
+
stop() {
|
|
950
|
+
for (const entry of this.entries.values()) {
|
|
951
|
+
if (entry.timerId !== void 0) {
|
|
952
|
+
clearTimeout(entry.timerId);
|
|
953
|
+
entry.timerId = void 0;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
this.entries.clear();
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* 动态添加一个新 job 并立即纳入调度
|
|
960
|
+
*/
|
|
961
|
+
add(job) {
|
|
962
|
+
const existing = this.entries.get(job.id);
|
|
963
|
+
if ((existing == null ? void 0 : existing.timerId) !== void 0) {
|
|
964
|
+
clearTimeout(existing.timerId);
|
|
965
|
+
}
|
|
966
|
+
this.scheduleJob(job);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* 按 id 移除 job 并取消其挂起的 timer
|
|
970
|
+
* id 不存在时静默忽略,不抛出异常
|
|
971
|
+
*/
|
|
972
|
+
remove(id) {
|
|
973
|
+
const entry = this.entries.get(id);
|
|
974
|
+
if (entry === void 0) return;
|
|
975
|
+
if (entry.timerId !== void 0) {
|
|
976
|
+
clearTimeout(entry.timerId);
|
|
977
|
+
}
|
|
978
|
+
this.entries.delete(id);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* 获取指定 job 的下次运行时间(ISO 字符串)
|
|
982
|
+
* job 不存在时返回 undefined
|
|
983
|
+
*/
|
|
984
|
+
getNextRunAt(jobId) {
|
|
985
|
+
const entry = this.entries.get(jobId);
|
|
986
|
+
if (entry === void 0) return void 0;
|
|
987
|
+
return new Date(entry.nextRunAtMs).toISOString();
|
|
988
|
+
}
|
|
989
|
+
// ──────────────────────────────────────────────────────────────
|
|
990
|
+
// 私有方法
|
|
991
|
+
// ──────────────────────────────────────────────────────────────
|
|
992
|
+
/**
|
|
993
|
+
* 将 job 纳入调度:
|
|
994
|
+
* 1. 计算第一次触发的延迟
|
|
995
|
+
* 2. 若 nextRunAt 已过期,快速推进到下一个未来时间(不补跑)
|
|
996
|
+
* 3. 挂起 setTimeout
|
|
997
|
+
*/
|
|
998
|
+
scheduleJob(job) {
|
|
999
|
+
const now = Date.now();
|
|
1000
|
+
let nextRunAtMs = new Date(job.nextRunAt).getTime();
|
|
1001
|
+
if (nextRunAtMs <= now) {
|
|
1002
|
+
const { intervalMs } = job;
|
|
1003
|
+
nextRunAtMs += intervalMs;
|
|
1004
|
+
while (nextRunAtMs <= now) {
|
|
1005
|
+
nextRunAtMs += intervalMs;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
const entry = {
|
|
1009
|
+
job,
|
|
1010
|
+
nextRunAtMs,
|
|
1011
|
+
timerId: void 0
|
|
1012
|
+
};
|
|
1013
|
+
this.entries.set(job.id, entry);
|
|
1014
|
+
this.arm(entry);
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* 为 entry 挂起一个 setTimeout,延迟到 entry.nextRunAtMs
|
|
1018
|
+
*/
|
|
1019
|
+
arm(entry) {
|
|
1020
|
+
const delay = Math.max(0, entry.nextRunAtMs - Date.now());
|
|
1021
|
+
entry.timerId = setTimeout(() => {
|
|
1022
|
+
void this.fire(entry);
|
|
1023
|
+
}, delay);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* job 到期时的触发逻辑:
|
|
1027
|
+
* 1. 检查 TTL,若已过期则不执行、不重新调度
|
|
1028
|
+
* 2. 调用 onJobRun;失败时调用 onJobError
|
|
1029
|
+
* 3. 无论成功失败,均重新调度下一次(TTL 由下次触发时再判断)
|
|
1030
|
+
*/
|
|
1031
|
+
async fire(entry) {
|
|
1032
|
+
const { job } = entry;
|
|
1033
|
+
const now = Date.now();
|
|
1034
|
+
if (job.ttlMs !== void 0) {
|
|
1035
|
+
const expiresAt = new Date(job.createdAt).getTime() + job.ttlMs;
|
|
1036
|
+
if (now >= expiresAt) {
|
|
1037
|
+
this.entries.delete(job.id);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
try {
|
|
1042
|
+
await this.options.onJobRun(job);
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
this.options.onJobError(job, error);
|
|
1045
|
+
}
|
|
1046
|
+
const current = this.entries.get(job.id);
|
|
1047
|
+
if (current === void 0) return;
|
|
1048
|
+
current.nextRunAtMs = Date.now() + job.intervalMs;
|
|
1049
|
+
this.arm(current);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
// src/automation/loop/command.ts
|
|
1054
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1055
|
+
|
|
1056
|
+
// src/automation/loop/parse.ts
|
|
1057
|
+
var DEFAULT_INTERVAL_MS = 6e5;
|
|
1058
|
+
var MIN_INTERVAL_MS = 3e4;
|
|
1059
|
+
var UNIT_MS = {
|
|
1060
|
+
s: 1e3,
|
|
1061
|
+
m: 6e4,
|
|
1062
|
+
h: 36e5
|
|
1063
|
+
};
|
|
1064
|
+
var TIME_TOKEN_RE = /(\d+)\s*([smh])/gi;
|
|
1065
|
+
function parseInterval(input) {
|
|
1066
|
+
if (input == null) return { intervalMs: DEFAULT_INTERVAL_MS };
|
|
1067
|
+
const trimmed = input.trim();
|
|
1068
|
+
if (trimmed === "") return { intervalMs: DEFAULT_INTERVAL_MS };
|
|
1069
|
+
if (trimmed.startsWith("-")) return null;
|
|
1070
|
+
const normalized = trimmed.replace(/^every\s+/i, "");
|
|
1071
|
+
if (/^\d+$/.test(normalized)) {
|
|
1072
|
+
const ms = parseInt(normalized, 10) * 1e3;
|
|
1073
|
+
return ms > 0 && ms >= MIN_INTERVAL_MS ? { intervalMs: ms } : null;
|
|
1074
|
+
}
|
|
1075
|
+
if (!/^(\d+\s*[smh])+$/i.test(normalized)) return null;
|
|
1076
|
+
let totalMs = 0;
|
|
1077
|
+
let matchCount = 0;
|
|
1078
|
+
TIME_TOKEN_RE.lastIndex = 0;
|
|
1079
|
+
let match;
|
|
1080
|
+
while ((match = TIME_TOKEN_RE.exec(normalized)) !== null) {
|
|
1081
|
+
const value = parseInt(match[1], 10);
|
|
1082
|
+
const unit = match[2].toLowerCase();
|
|
1083
|
+
totalMs += value * UNIT_MS[unit];
|
|
1084
|
+
matchCount++;
|
|
1085
|
+
}
|
|
1086
|
+
if (matchCount === 0) return null;
|
|
1087
|
+
if (totalMs <= 0 || totalMs < MIN_INTERVAL_MS) return null;
|
|
1088
|
+
return { intervalMs: totalMs };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// src/automation/loop/command.ts
|
|
1092
|
+
function formatInterval(ms) {
|
|
1093
|
+
const totalSec = Math.floor(ms / 1e3);
|
|
1094
|
+
const h = Math.floor(totalSec / 3600);
|
|
1095
|
+
const m = Math.floor(totalSec % 3600 / 60);
|
|
1096
|
+
const s = totalSec % 60;
|
|
1097
|
+
if (h > 0 && m === 0 && s === 0) return `${h}h`;
|
|
1098
|
+
if (h > 0 && s === 0) return `${h}h${m}m`;
|
|
1099
|
+
if (h > 0) return `${h}h${m}m${s}s`;
|
|
1100
|
+
if (m > 0 && s === 0) return `${m}m`;
|
|
1101
|
+
if (m > 0) return `${m}m${s}s`;
|
|
1102
|
+
return `${s}s`;
|
|
1103
|
+
}
|
|
1104
|
+
async function cmdLoop(args, deps) {
|
|
1105
|
+
const trimmed = args.trim();
|
|
1106
|
+
let intervalMs = DEFAULT_INTERVAL_MS;
|
|
1107
|
+
let prompt = trimmed;
|
|
1108
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
1109
|
+
if (spaceIdx !== -1) {
|
|
1110
|
+
const firstToken = trimmed.slice(0, spaceIdx);
|
|
1111
|
+
const rest = trimmed.slice(spaceIdx + 1);
|
|
1112
|
+
const parsed1 = parseInterval(firstToken);
|
|
1113
|
+
if (parsed1 !== null) {
|
|
1114
|
+
intervalMs = parsed1.intervalMs;
|
|
1115
|
+
prompt = rest.trim();
|
|
1116
|
+
} else {
|
|
1117
|
+
const secondSpaceIdx = rest.indexOf(" ");
|
|
1118
|
+
if (secondSpaceIdx !== -1) {
|
|
1119
|
+
const twoToken = `${firstToken} ${rest.slice(0, secondSpaceIdx)}`;
|
|
1120
|
+
const parsed2 = parseInterval(twoToken);
|
|
1121
|
+
if (parsed2 !== null) {
|
|
1122
|
+
intervalMs = parsed2.intervalMs;
|
|
1123
|
+
prompt = rest.slice(secondSpaceIdx + 1).trim();
|
|
1124
|
+
}
|
|
1125
|
+
} else {
|
|
1126
|
+
const parsed2 = parseInterval(`${firstToken} ${rest}`);
|
|
1127
|
+
if (parsed2 !== null) {
|
|
1128
|
+
intervalMs = parsed2.intervalMs;
|
|
1129
|
+
prompt = "";
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
} else {
|
|
1134
|
+
const parsed = parseInterval(trimmed);
|
|
1135
|
+
if (parsed !== null) {
|
|
1136
|
+
intervalMs = parsed.intervalMs;
|
|
1137
|
+
prompt = "";
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (!prompt.trim()) {
|
|
1141
|
+
return "\u7528\u6CD5\uFF1A/loop [interval] <prompt>\uFF0C\u4F8B\u5982\uFF1A/loop 10m \u68C0\u67E5 deploy \u72B6\u6001";
|
|
1142
|
+
}
|
|
1143
|
+
const now = /* @__PURE__ */ new Date();
|
|
1144
|
+
const job = {
|
|
1145
|
+
id: randomUUID2(),
|
|
1146
|
+
kind: "loop",
|
|
1147
|
+
title: prompt.slice(0, 60),
|
|
1148
|
+
createdAt: now.toISOString(),
|
|
1149
|
+
updatedAt: now.toISOString(),
|
|
1150
|
+
state: "scheduled",
|
|
1151
|
+
promptTemplate: prompt,
|
|
1152
|
+
intervalMs,
|
|
1153
|
+
nextRunAt: new Date(now.getTime() + intervalMs).toISOString(),
|
|
1154
|
+
autoRunNow: false,
|
|
1155
|
+
runCount: 0,
|
|
1156
|
+
failureCount: 0
|
|
1157
|
+
};
|
|
1158
|
+
await upsertJob(deps.dataDir, job);
|
|
1159
|
+
deps.onSchedule(job);
|
|
1160
|
+
return `\u5DF2\u521B\u5EFA loop job [${job.id.slice(0, 8)}]\uFF0C\u6BCF ${formatInterval(intervalMs)} \u6267\u884C\u4E00\u6B21\uFF1A${prompt}`;
|
|
1161
|
+
}
|
|
1162
|
+
async function cmdJobs(deps) {
|
|
1163
|
+
const jobs = await loadJobs(deps.dataDir);
|
|
1164
|
+
if (jobs.length === 0) {
|
|
1165
|
+
return "\u6682\u65E0 automation job\u3002\u4F7F\u7528 /loop <interval> <prompt> \u521B\u5EFA\u4E00\u4E2A\u3002";
|
|
1166
|
+
}
|
|
1167
|
+
const lines = jobs.map((j) => {
|
|
1168
|
+
const interval = j.kind === "loop" ? formatInterval(j.intervalMs) : "\u2014";
|
|
1169
|
+
const idPrefix = j.id.slice(0, 8);
|
|
1170
|
+
return `[${idPrefix}] ${interval} ${j.promptTemplate} (${j.state})`;
|
|
1171
|
+
});
|
|
1172
|
+
return `\u5F53\u524D job \u5217\u8868\uFF08\u5171 ${jobs.length} \u4E2A\uFF09\uFF1A
|
|
1173
|
+
${lines.join("\n")}`;
|
|
1174
|
+
}
|
|
1175
|
+
async function cmdUnloop(idOrPrefix, deps) {
|
|
1176
|
+
const jobs = await loadJobs(deps.dataDir);
|
|
1177
|
+
const target = jobs.find(
|
|
1178
|
+
(j) => j.id === idOrPrefix || j.id.startsWith(idOrPrefix)
|
|
1179
|
+
);
|
|
1180
|
+
if (target === void 0) {
|
|
1181
|
+
return `\u627E\u4E0D\u5230 job "${idOrPrefix}"\uFF0C\u8BF7\u7528 /jobs \u786E\u8BA4 id\u3002`;
|
|
1182
|
+
}
|
|
1183
|
+
await removeJob(deps.dataDir, target.id);
|
|
1184
|
+
deps.onUnschedule(target.id);
|
|
1185
|
+
return `\u5DF2\u53D6\u6D88 loop job [${target.id.slice(0, 8)}]\uFF1A${target.promptTemplate}`;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// src/automation/goal/command.ts
|
|
1189
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1190
|
+
async function cmdGoal(args, deps) {
|
|
1191
|
+
const condition = args.trim();
|
|
1192
|
+
if (!condition) {
|
|
1193
|
+
return "\u7528\u6CD5\uFF1A/goal <\u76EE\u6807\u6761\u4EF6>\uFF0C\u4F8B\u5982\uFF1A/goal \u6240\u6709\u6D4B\u8BD5\u901A\u8FC7";
|
|
1194
|
+
}
|
|
1195
|
+
const now = /* @__PURE__ */ new Date();
|
|
1196
|
+
const job = {
|
|
1197
|
+
id: randomUUID3(),
|
|
1198
|
+
kind: "goal",
|
|
1199
|
+
title: condition.slice(0, 60),
|
|
1200
|
+
createdAt: now.toISOString(),
|
|
1201
|
+
updatedAt: now.toISOString(),
|
|
1202
|
+
state: "scheduled",
|
|
1203
|
+
promptTemplate: condition,
|
|
1204
|
+
condition,
|
|
1205
|
+
activeTurnCount: 0,
|
|
1206
|
+
runCount: 0,
|
|
1207
|
+
failureCount: 0,
|
|
1208
|
+
// 快照字段:仅在调用方提供时写入,旧格式兼容(undefined 即不存在)
|
|
1209
|
+
snapshotLogFile: deps.snapshotLogFile,
|
|
1210
|
+
snapshotTurn: deps.snapshotTurn
|
|
1211
|
+
};
|
|
1212
|
+
await upsertJob(deps.dataDir, job);
|
|
1213
|
+
deps.onSchedule(job);
|
|
1214
|
+
return `\u5DF2\u521B\u5EFA goal job [${job.id.slice(0, 8)}]\uFF0C\u76EE\u6807\u6761\u4EF6\uFF1A${condition}`;
|
|
1215
|
+
}
|
|
1216
|
+
async function cmdUngoal(idOrPrefix, deps) {
|
|
1217
|
+
const jobs = await loadJobs(deps.dataDir);
|
|
1218
|
+
const target = jobs.find(
|
|
1219
|
+
(j) => j.id === idOrPrefix || j.id.startsWith(idOrPrefix)
|
|
1220
|
+
);
|
|
1221
|
+
if (target === void 0) {
|
|
1222
|
+
return `\u627E\u4E0D\u5230 job "${idOrPrefix}"\uFF0C\u8BF7\u7528 /jobs \u786E\u8BA4 id\u3002`;
|
|
1223
|
+
}
|
|
1224
|
+
await removeJob(deps.dataDir, target.id);
|
|
1225
|
+
deps.onUnschedule(target.id);
|
|
1226
|
+
return `\u5DF2\u53D6\u6D88 goal job [${target.id.slice(0, 8)}]\uFF1A${target.promptTemplate}`;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/automation/goal/rollback.ts
|
|
1230
|
+
function buildRollbackMessage(job) {
|
|
1231
|
+
if (job.snapshotLogFile === void 0 || job.snapshotTurn === void 0) {
|
|
1232
|
+
return "";
|
|
1233
|
+
}
|
|
1234
|
+
return `
|
|
1235
|
+
|
|
1236
|
+
\u4E0A\u4E0B\u6587\u53EF\u80FD\u5DF2\u504F\u79BB\u76EE\u6807\uFF0C\u56DE\u6EDA\u5230\u76EE\u6807\u521B\u5EFA\u524D\u7684\u4F4D\u7F6E\uFF1A
|
|
1237
|
+
ecode --fork ${job.snapshotLogFile}:${job.snapshotTurn}`;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/automation/index.ts
|
|
1241
|
+
var AutomationManager = class {
|
|
1242
|
+
constructor(config) {
|
|
1243
|
+
this.config = config;
|
|
1244
|
+
this.scheduler = new LoopScheduler({
|
|
1245
|
+
onJobRun: async (job) => {
|
|
1246
|
+
var _a;
|
|
1247
|
+
const entry = await executeJob(job, {
|
|
1248
|
+
dataDir: config.dataDir,
|
|
1249
|
+
logDir: config.logDir,
|
|
1250
|
+
session: this.createSession()
|
|
1251
|
+
});
|
|
1252
|
+
if (entry) {
|
|
1253
|
+
const status = entry.result === "success" ? "\u2713" : "\u2717";
|
|
1254
|
+
const summary = entry.summaryText ?? entry.error ?? "";
|
|
1255
|
+
(_a = config.onJobResult) == null ? void 0 : _a.call(
|
|
1256
|
+
config,
|
|
1257
|
+
job.id,
|
|
1258
|
+
`[loop ${job.id.slice(0, 8)}] ${status} ${summary}`.trim()
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
},
|
|
1262
|
+
onJobError: (job, error) => {
|
|
1263
|
+
var _a;
|
|
1264
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1265
|
+
(_a = config.onJobResult) == null ? void 0 : _a.call(config, job.id, `[loop ${job.id.slice(0, 8)}] \u6267\u884C\u9519\u8BEF\uFF1A${msg}`);
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
config;
|
|
1270
|
+
scheduler;
|
|
1271
|
+
/** 按 jobId 索引的 goal runner AbortController */
|
|
1272
|
+
goalRunners = /* @__PURE__ */ new Map();
|
|
1273
|
+
/**
|
|
1274
|
+
* 从持久化存储加载所有 job,启动调度器并恢复 goal runner。
|
|
1275
|
+
* 应在应用启动时调用一次。
|
|
1276
|
+
*/
|
|
1277
|
+
async start() {
|
|
1278
|
+
const jobs = await loadJobs(this.config.dataDir);
|
|
1279
|
+
const loopJobs = jobs.filter((j) => j.kind === "loop");
|
|
1280
|
+
const goalJobs = jobs.filter(
|
|
1281
|
+
(j) => j.kind === "goal" && j.state === "scheduled"
|
|
1282
|
+
);
|
|
1283
|
+
this.scheduler.start(loopJobs);
|
|
1284
|
+
for (const job of goalJobs) {
|
|
1285
|
+
this.startGoalRunner(job);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* 停止所有调度:清除 loop timer,中止所有 goal runner。
|
|
1290
|
+
* 应在应用退出时调用。
|
|
1291
|
+
*/
|
|
1292
|
+
stop() {
|
|
1293
|
+
this.scheduler.stop();
|
|
1294
|
+
for (const [, controller] of this.goalRunners) {
|
|
1295
|
+
controller.abort();
|
|
1296
|
+
}
|
|
1297
|
+
this.goalRunners.clear();
|
|
1298
|
+
}
|
|
1299
|
+
// ──────────────────────────────────────────────────────────────
|
|
1300
|
+
// 命令处理器(转发到 loop/goal 子模块)
|
|
1301
|
+
// ──────────────────────────────────────────────────────────────
|
|
1302
|
+
async cmdLoop(args) {
|
|
1303
|
+
return cmdLoop(args, {
|
|
1304
|
+
dataDir: this.config.dataDir,
|
|
1305
|
+
onSchedule: (job) => this.scheduler.add(job),
|
|
1306
|
+
onUnschedule: (id) => this.scheduler.remove(id)
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* 处理 /goal 命令,可选接收当前会话上下文以支持 blocked 时的自动回滚提示。
|
|
1311
|
+
* sessionContext 由 App.tsx 在用户提交 /goal 时注入(logFile + turnCount)。
|
|
1312
|
+
*/
|
|
1313
|
+
async cmdGoal(args, sessionContext) {
|
|
1314
|
+
return cmdGoal(args, {
|
|
1315
|
+
dataDir: this.config.dataDir,
|
|
1316
|
+
snapshotLogFile: sessionContext == null ? void 0 : sessionContext.logFile,
|
|
1317
|
+
snapshotTurn: sessionContext == null ? void 0 : sessionContext.turnCount,
|
|
1318
|
+
onSchedule: (job) => this.startGoalRunner(job),
|
|
1319
|
+
onUnschedule: (id) => this.stopGoalRunner(id)
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
async cmdJobs() {
|
|
1323
|
+
return cmdJobs({
|
|
1324
|
+
dataDir: this.config.dataDir,
|
|
1325
|
+
onSchedule: () => {
|
|
1326
|
+
},
|
|
1327
|
+
onUnschedule: () => {
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
async cmdUnloop(id) {
|
|
1332
|
+
return cmdUnloop(id, {
|
|
1333
|
+
dataDir: this.config.dataDir,
|
|
1334
|
+
onSchedule: () => {
|
|
1335
|
+
},
|
|
1336
|
+
onUnschedule: (jobId) => this.scheduler.remove(jobId)
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
async cmdUngoal(id) {
|
|
1340
|
+
return cmdUngoal(id, {
|
|
1341
|
+
dataDir: this.config.dataDir,
|
|
1342
|
+
onSchedule: () => {
|
|
1343
|
+
},
|
|
1344
|
+
onUnschedule: (jobId) => this.stopGoalRunner(jobId)
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
// ──────────────────────────────────────────────────────────────
|
|
1348
|
+
// 私有辅助方法
|
|
1349
|
+
// ──────────────────────────────────────────────────────────────
|
|
1350
|
+
/**
|
|
1351
|
+
* 从 ProviderClient 构建 RuntimeSessionHandle。
|
|
1352
|
+
* 将流式 chunk 拼接为完整字符串返回给调用方。
|
|
1353
|
+
*/
|
|
1354
|
+
createSession() {
|
|
1355
|
+
const { llmClient } = this.config;
|
|
1356
|
+
return {
|
|
1357
|
+
run: async (prompt) => {
|
|
1358
|
+
let text = "";
|
|
1359
|
+
const msgs = [{ role: "user", content: prompt }];
|
|
1360
|
+
for await (const chunk of llmClient.stream(msgs, [], void 0)) {
|
|
1361
|
+
if (chunk.text) text += chunk.text;
|
|
1362
|
+
}
|
|
1363
|
+
return text;
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
/** 启动 goal job 的执行协程(可被 AbortController 中断) */
|
|
1368
|
+
startGoalRunner(job) {
|
|
1369
|
+
this.stopGoalRunner(job.id);
|
|
1370
|
+
const ac = new AbortController();
|
|
1371
|
+
this.goalRunners.set(job.id, ac);
|
|
1372
|
+
void this.runGoalLoop(job, ac.signal);
|
|
1373
|
+
}
|
|
1374
|
+
/** 中止指定 goal runner */
|
|
1375
|
+
stopGoalRunner(jobId) {
|
|
1376
|
+
const ac = this.goalRunners.get(jobId);
|
|
1377
|
+
if (ac) {
|
|
1378
|
+
ac.abort();
|
|
1379
|
+
this.goalRunners.delete(jobId);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Goal job 执行循环:
|
|
1384
|
+
* 1. 调用 executeJob(LLM worker 执行一轮)
|
|
1385
|
+
* 2. 用 LLM 评估器判断条件是否满足
|
|
1386
|
+
* 3. done → 标记 completed;blocked → 标记 error;not_done → 等待 5s 后继续
|
|
1387
|
+
* 4. AbortSignal 触发时立即退出
|
|
1388
|
+
*/
|
|
1389
|
+
async runGoalLoop(initialJob, signal) {
|
|
1390
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1391
|
+
let job = initialJob;
|
|
1392
|
+
while (!signal.aborted) {
|
|
1393
|
+
const runtimeConfig = {
|
|
1394
|
+
dataDir: this.config.dataDir,
|
|
1395
|
+
logDir: this.config.logDir,
|
|
1396
|
+
session: this.createSession()
|
|
1397
|
+
};
|
|
1398
|
+
const entry = await executeJob(job, runtimeConfig);
|
|
1399
|
+
if (!entry) break;
|
|
1400
|
+
if (signal.aborted) break;
|
|
1401
|
+
const evaluator = createLLMEvaluator(this.createSession());
|
|
1402
|
+
const evaluation = await evaluator.evaluate({
|
|
1403
|
+
condition: job.condition,
|
|
1404
|
+
transcript: entry.summaryText ?? entry.error ?? ""
|
|
1405
|
+
});
|
|
1406
|
+
const updatedJob = {
|
|
1407
|
+
...job,
|
|
1408
|
+
lastEvaluation: evaluation,
|
|
1409
|
+
activeTurnCount: job.activeTurnCount + 1,
|
|
1410
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1411
|
+
};
|
|
1412
|
+
if (evaluation.verdict === "done") {
|
|
1413
|
+
const completedJob = {
|
|
1414
|
+
...updatedJob,
|
|
1415
|
+
state: "completed",
|
|
1416
|
+
achievedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1417
|
+
};
|
|
1418
|
+
await upsertJob(this.config.dataDir, completedJob);
|
|
1419
|
+
(_b = (_a = this.config).onJobResult) == null ? void 0 : _b.call(
|
|
1420
|
+
_a,
|
|
1421
|
+
job.id,
|
|
1422
|
+
`[goal ${job.id.slice(0, 8)}] \u76EE\u6807\u5DF2\u8FBE\u6210\uFF1A${evaluation.reason}`
|
|
1423
|
+
);
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1426
|
+
if (evaluation.verdict === "blocked") {
|
|
1427
|
+
const errorJob = { ...updatedJob, state: "error" };
|
|
1428
|
+
await upsertJob(this.config.dataDir, errorJob);
|
|
1429
|
+
const rollback = buildRollbackMessage(errorJob);
|
|
1430
|
+
(_d = (_c = this.config).onJobResult) == null ? void 0 : _d.call(
|
|
1431
|
+
_c,
|
|
1432
|
+
job.id,
|
|
1433
|
+
`[goal ${job.id.slice(0, 8)}] \u6267\u884C\u53D7\u963B\uFF1A${evaluation.reason}${rollback}`
|
|
1434
|
+
);
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
await upsertJob(this.config.dataDir, updatedJob);
|
|
1438
|
+
job = updatedJob;
|
|
1439
|
+
(_f = (_e = this.config).onJobResult) == null ? void 0 : _f.call(
|
|
1440
|
+
_e,
|
|
1441
|
+
job.id,
|
|
1442
|
+
`[goal ${job.id.slice(0, 8)}] \u7B2C ${job.activeTurnCount} \u8F6E\uFF1A\u5C1A\u672A\u5B8C\u6210\uFF0C\u7EE7\u7EED\u6267\u884C\u2026`
|
|
1443
|
+
);
|
|
1444
|
+
await new Promise((resolve2) => {
|
|
1445
|
+
const timer = setTimeout(resolve2, 5e3);
|
|
1446
|
+
signal.addEventListener("abort", () => {
|
|
1447
|
+
clearTimeout(timer);
|
|
1448
|
+
resolve2();
|
|
1449
|
+
}, { once: true });
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
this.goalRunners.delete(job.id);
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
// src/meta_skill/index.ts
|
|
1457
|
+
import * as fs3 from "fs";
|
|
1458
|
+
import * as path3 from "path";
|
|
1459
|
+
import { fileURLToPath } from "url";
|
|
1460
|
+
|
|
1461
|
+
// src/meta_skill/task-classifier.ts
|
|
1462
|
+
var RULES = {
|
|
1463
|
+
bugfix: [
|
|
1464
|
+
{ pattern: /(?<!\w)(bug|fix|修复|修bug|defect|error|crash|exception|broken|fails?|失败|报错|崩溃|异常|问题|故障|incorrect|wrong|不对|不正确)(?!\w)/i, weight: 3 },
|
|
1465
|
+
{ pattern: /(?<!\w)(debug|diagnose|诊断|定位|追踪|trace|reproduce|复现)(?!\w)/i, weight: 2 },
|
|
1466
|
+
{ pattern: /(?<!\w)(regression|revert|回归|回滚)(?!\w)/i, weight: 1 }
|
|
1467
|
+
],
|
|
1468
|
+
refactor: [
|
|
1469
|
+
{ pattern: /(?<!\w)(refactor|重构|重写|rewrite|restructure|重新设计|clean ?up|清理|整理|simplify|简化|extract|提取|split|拆分)(?!\w)/i, weight: 3 },
|
|
1470
|
+
{ pattern: /(?<!\w)(maintainability|可维护|readability|可读性|code smell|技术债|tech debt|coupling|耦合|cohesion|内聚)(?!\w)/i, weight: 2 },
|
|
1471
|
+
{ pattern: /(?<!\w)(rename|move|organize|抽象|abstraction)(?!\w)/i, weight: 1 }
|
|
1472
|
+
],
|
|
1473
|
+
"test-generation": [
|
|
1474
|
+
{ pattern: /(?<!\w)(tests?|测试|specs?|单元测试|unit tests?|coverage|覆盖率|vitest|jest|mocha|e2e|integration)(?!\w)/i, weight: 3 },
|
|
1475
|
+
{ pattern: /(?<!\w)(assert|验证|verify|tdd|red.green)(?!\w)/i, weight: 2 },
|
|
1476
|
+
{ pattern: /(?<!\w)(mock|stub|fixture|测试用例|用例)(?!\w)/i, weight: 1 }
|
|
1477
|
+
],
|
|
1478
|
+
"code-review": [
|
|
1479
|
+
{ pattern: /(?<!\w)(review|审查|审阅|代码审查|audit|安全审计|inspect)(?!\w)/i, weight: 3 },
|
|
1480
|
+
{ pattern: /(?<!\w)(quality|质量|best practice|最佳实践|standard|规范|convention|smell)(?!\w)/i, weight: 2 },
|
|
1481
|
+
{ pattern: /(?<!\w)(feedback|意见|suggestion|建议|comment|注释)(?!\w)/i, weight: 1 }
|
|
1482
|
+
],
|
|
1483
|
+
"code-reverse-design": [
|
|
1484
|
+
{ pattern: /(?<!\w)(reverse|逆向|理解|understand|architecture|架构|design doc|设计文档|document|文档化|逆向工程)(?!\w)/i, weight: 3 },
|
|
1485
|
+
{ pattern: /(?<!\w)(analyze|分析|explore|探索|overview|概览|structure|结构|module|模块|dependency|依赖)(?!\w)/i, weight: 2 },
|
|
1486
|
+
{ pattern: /(?<!\w)(prd|需求|规格|diagram|图表|flowchart|流程图)(?!\w)/i, weight: 1 }
|
|
1487
|
+
]
|
|
1488
|
+
};
|
|
1489
|
+
var PRIORITY = [
|
|
1490
|
+
"bugfix",
|
|
1491
|
+
"test-generation",
|
|
1492
|
+
"refactor",
|
|
1493
|
+
"code-review",
|
|
1494
|
+
"code-reverse-design"
|
|
1495
|
+
];
|
|
1496
|
+
function classifyTask(goal, fallback = "code-review") {
|
|
1497
|
+
const scores = {
|
|
1498
|
+
bugfix: 0,
|
|
1499
|
+
refactor: 0,
|
|
1500
|
+
"test-generation": 0,
|
|
1501
|
+
"code-review": 0,
|
|
1502
|
+
"code-reverse-design": 0
|
|
1503
|
+
};
|
|
1504
|
+
for (const [type, rules] of Object.entries(RULES)) {
|
|
1505
|
+
for (const { pattern, weight } of rules) {
|
|
1506
|
+
if (pattern.test(goal)) {
|
|
1507
|
+
scores[type] += weight;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
let bestType = fallback;
|
|
1512
|
+
let bestScore = 0;
|
|
1513
|
+
for (const type of PRIORITY) {
|
|
1514
|
+
const s = scores[type];
|
|
1515
|
+
if (s > bestScore) {
|
|
1516
|
+
bestScore = s;
|
|
1517
|
+
bestType = type;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return bestType;
|
|
1521
|
+
}
|
|
1522
|
+
function isValidTaskType(value) {
|
|
1523
|
+
return ["bugfix", "refactor", "test-generation", "code-review", "code-reverse-design"].includes(value);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// src/meta_skill/skill-survey.ts
|
|
1527
|
+
var BASELINE_MAP = {
|
|
1528
|
+
"bugfix": ["diagnose", "tdd"],
|
|
1529
|
+
"refactor": ["search-first", "plan", "improve-codebase-architecture"],
|
|
1530
|
+
"test-generation": ["tdd"],
|
|
1531
|
+
"code-review": ["zoom-out", "search-first", "security-review"],
|
|
1532
|
+
"code-reverse-design": ["zoom-out", "plan", "to-prd"]
|
|
1533
|
+
};
|
|
1534
|
+
function getBaselineSkills(taskType) {
|
|
1535
|
+
return [...BASELINE_MAP[taskType] ?? []];
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/meta_skill/workflow-extractor.ts
|
|
1539
|
+
var NUMBERED_HEADINGS_RE = /^#{1,4}\s+(?:Step\s+|Phase\s+)?(\d+)[.:\-–—\s]+(.+)$/gim;
|
|
1540
|
+
var NUMBERED_LIST_RE = /^\s*(\d+)\.\s+(.{10,})$/gm;
|
|
1541
|
+
var MAX_STEPS = 12;
|
|
1542
|
+
function extractWorkflowSteps(body) {
|
|
1543
|
+
if (!body.trim()) return [];
|
|
1544
|
+
const headingSteps = extractHeadingSteps(body);
|
|
1545
|
+
if (headingSteps.length >= 2) {
|
|
1546
|
+
return headingSteps.slice(0, MAX_STEPS);
|
|
1547
|
+
}
|
|
1548
|
+
const listSteps = extractListSteps(body);
|
|
1549
|
+
if (listSteps.length >= 2) {
|
|
1550
|
+
return listSteps.slice(0, MAX_STEPS);
|
|
1551
|
+
}
|
|
1552
|
+
return extractParagraphSteps(body);
|
|
1553
|
+
}
|
|
1554
|
+
function extractHeadingSteps(body) {
|
|
1555
|
+
var _a;
|
|
1556
|
+
const steps = [];
|
|
1557
|
+
let match;
|
|
1558
|
+
NUMBERED_HEADINGS_RE.lastIndex = 0;
|
|
1559
|
+
while ((match = NUMBERED_HEADINGS_RE.exec(body)) !== null) {
|
|
1560
|
+
const text = (_a = match[2]) == null ? void 0 : _a.trim();
|
|
1561
|
+
if (text) steps.push(cleanStepText(text));
|
|
1562
|
+
}
|
|
1563
|
+
return steps;
|
|
1564
|
+
}
|
|
1565
|
+
function extractListSteps(body) {
|
|
1566
|
+
var _a;
|
|
1567
|
+
const steps = [];
|
|
1568
|
+
NUMBERED_LIST_RE.lastIndex = 0;
|
|
1569
|
+
let match;
|
|
1570
|
+
while ((match = NUMBERED_LIST_RE.exec(body)) !== null) {
|
|
1571
|
+
const text = (_a = match[2]) == null ? void 0 : _a.trim();
|
|
1572
|
+
if (text) steps.push(cleanStepText(text));
|
|
1573
|
+
}
|
|
1574
|
+
return steps;
|
|
1575
|
+
}
|
|
1576
|
+
function extractParagraphSteps(body) {
|
|
1577
|
+
return body.split(/\n{2,}/).map((para) => {
|
|
1578
|
+
var _a;
|
|
1579
|
+
return ((_a = para.trim().split("\n")[0]) == null ? void 0 : _a.trim()) ?? "";
|
|
1580
|
+
}).filter((line) => line.length > 10 && !line.startsWith("#")).slice(0, MAX_STEPS).map(cleanStepText);
|
|
1581
|
+
}
|
|
1582
|
+
function cleanStepText(text) {
|
|
1583
|
+
return text.replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// src/meta_skill/profile-store.ts
|
|
1587
|
+
var GPT4O = {
|
|
1588
|
+
model: "gpt-4o",
|
|
1589
|
+
traits: {
|
|
1590
|
+
instructionFollowing: "high",
|
|
1591
|
+
schemaDiscipline: "high",
|
|
1592
|
+
longContextStability: "high",
|
|
1593
|
+
multiStepStability: "high",
|
|
1594
|
+
hallucinationRisk: "low",
|
|
1595
|
+
selfCorrection: "high"
|
|
1596
|
+
},
|
|
1597
|
+
preferences: {
|
|
1598
|
+
promptStyle: "freeform",
|
|
1599
|
+
contextOrder: "goal-first",
|
|
1600
|
+
stepSize: "large",
|
|
1601
|
+
needsExamples: false,
|
|
1602
|
+
needsStopConditions: false
|
|
1603
|
+
},
|
|
1604
|
+
antiPatterns: [
|
|
1605
|
+
"\u8FC7\u5EA6\u7EA6\u675F\u8F93\u51FA\u683C\u5F0F\u4F1A\u6291\u5236\u63A8\u7406\u8D28\u91CF",
|
|
1606
|
+
"\u4E0D\u5FC5\u8981\u7684 chain-of-thought \u5F3A\u5236\u589E\u52A0 token \u6D88\u8017"
|
|
1607
|
+
]
|
|
1608
|
+
};
|
|
1609
|
+
var GPT4O_MINI = {
|
|
1610
|
+
model: "gpt-4o-mini",
|
|
1611
|
+
traits: {
|
|
1612
|
+
instructionFollowing: "medium",
|
|
1613
|
+
schemaDiscipline: "medium",
|
|
1614
|
+
longContextStability: "medium",
|
|
1615
|
+
multiStepStability: "medium",
|
|
1616
|
+
hallucinationRisk: "medium",
|
|
1617
|
+
selfCorrection: "medium"
|
|
1618
|
+
},
|
|
1619
|
+
preferences: {
|
|
1620
|
+
promptStyle: "explicit-checklist",
|
|
1621
|
+
contextOrder: "goal-first",
|
|
1622
|
+
stepSize: "small",
|
|
1623
|
+
needsExamples: true,
|
|
1624
|
+
needsStopConditions: true
|
|
1625
|
+
},
|
|
1626
|
+
antiPatterns: [
|
|
1627
|
+
"\u5355\u6B21\u4F20\u5165\u8D85\u8FC7 10 \u4E2A\u6587\u4EF6\u5185\u5BB9\u5BB9\u6613\u4E22\u5931\u7EC6\u8282",
|
|
1628
|
+
"\u5F00\u653E\u5F0F\u4EFB\u52A1\u5BB9\u6613\u751F\u6210\u5197\u4F59\u5185\u5BB9"
|
|
1629
|
+
]
|
|
1630
|
+
};
|
|
1631
|
+
var DEEPSEEK_CHAT = {
|
|
1632
|
+
model: "deepseek-chat",
|
|
1633
|
+
traits: {
|
|
1634
|
+
instructionFollowing: "high",
|
|
1635
|
+
schemaDiscipline: "medium",
|
|
1636
|
+
longContextStability: "medium",
|
|
1637
|
+
multiStepStability: "medium",
|
|
1638
|
+
hallucinationRisk: "medium",
|
|
1639
|
+
selfCorrection: "medium"
|
|
1640
|
+
},
|
|
1641
|
+
preferences: {
|
|
1642
|
+
promptStyle: "explicit-checklist",
|
|
1643
|
+
contextOrder: "goal-first",
|
|
1644
|
+
stepSize: "medium",
|
|
1645
|
+
needsExamples: true,
|
|
1646
|
+
needsStopConditions: false
|
|
1647
|
+
},
|
|
1648
|
+
antiPatterns: [
|
|
1649
|
+
"\u957F\u4E0A\u4E0B\u6587\uFF08>32K token\uFF09\u65F6\u8F93\u51FA\u7A33\u5B9A\u6027\u4E0B\u964D",
|
|
1650
|
+
"\u591A\u6B65\u590D\u6742\u63A8\u7406\u94FE\u4E2D\u9014\u5BB9\u6613\u504F\u79BB\u76EE\u6807"
|
|
1651
|
+
]
|
|
1652
|
+
};
|
|
1653
|
+
var DEEPSEEK_REASONER = {
|
|
1654
|
+
model: "deepseek-reasoner",
|
|
1655
|
+
traits: {
|
|
1656
|
+
instructionFollowing: "high",
|
|
1657
|
+
schemaDiscipline: "medium",
|
|
1658
|
+
longContextStability: "medium",
|
|
1659
|
+
multiStepStability: "high",
|
|
1660
|
+
hallucinationRisk: "low",
|
|
1661
|
+
selfCorrection: "high"
|
|
1662
|
+
},
|
|
1663
|
+
preferences: {
|
|
1664
|
+
promptStyle: "freeform",
|
|
1665
|
+
contextOrder: "goal-first",
|
|
1666
|
+
stepSize: "large",
|
|
1667
|
+
needsExamples: false,
|
|
1668
|
+
needsStopConditions: false
|
|
1669
|
+
},
|
|
1670
|
+
antiPatterns: [
|
|
1671
|
+
"\u8FC7\u5EA6\u7EA6\u675F\u63A8\u7406\u6B65\u9AA4\u683C\u5F0F\u4F1A\u5E72\u6270\u5185\u90E8\u601D\u8003\u94FE",
|
|
1672
|
+
"\u4E0D\u9002\u5408\u7EAF\u68C0\u7D22\u7C7B\u4EFB\u52A1\uFF08\u63A8\u7406\u5F00\u9500\u8FDC\u5927\u4E8E\u6536\u76CA\uFF09"
|
|
1673
|
+
]
|
|
1674
|
+
};
|
|
1675
|
+
var MINIMAX_M25 = {
|
|
1676
|
+
model: "MiniMax-M2.5",
|
|
1677
|
+
traits: {
|
|
1678
|
+
instructionFollowing: "medium",
|
|
1679
|
+
schemaDiscipline: "low",
|
|
1680
|
+
longContextStability: "high",
|
|
1681
|
+
// 百万 token 上下文
|
|
1682
|
+
multiStepStability: "medium",
|
|
1683
|
+
hallucinationRisk: "medium",
|
|
1684
|
+
selfCorrection: "low"
|
|
1685
|
+
},
|
|
1686
|
+
preferences: {
|
|
1687
|
+
promptStyle: "strict-template",
|
|
1688
|
+
contextOrder: "template-first",
|
|
1689
|
+
stepSize: "small",
|
|
1690
|
+
needsExamples: true,
|
|
1691
|
+
needsStopConditions: true
|
|
1692
|
+
},
|
|
1693
|
+
antiPatterns: [
|
|
1694
|
+
"\u81EA\u7531\u683C\u5F0F\u8F93\u51FA\u5BB9\u6613\u4EA7\u751F\u5197\u4F59\u6216\u683C\u5F0F\u6DF7\u4E71",
|
|
1695
|
+
"\u7F3A\u4E4F\u660E\u786E\u505C\u6B62\u6761\u4EF6\u65F6\u5BB9\u6613\u8FC7\u5EA6\u8F93\u51FA",
|
|
1696
|
+
"\u590D\u6742\u63A8\u7406\u94FE\u9700\u8981\u66F4\u7EC6\u7C92\u5EA6\u7684\u6B65\u9AA4\u62C6\u5206"
|
|
1697
|
+
]
|
|
1698
|
+
};
|
|
1699
|
+
var MINIMAX_M25_HS = {
|
|
1700
|
+
...MINIMAX_M25,
|
|
1701
|
+
model: "MiniMax-M2.5-highspeed"
|
|
1702
|
+
};
|
|
1703
|
+
var CLAUDE_SONNET = {
|
|
1704
|
+
model: "claude-sonnet-4-6",
|
|
1705
|
+
traits: {
|
|
1706
|
+
instructionFollowing: "high",
|
|
1707
|
+
schemaDiscipline: "high",
|
|
1708
|
+
longContextStability: "high",
|
|
1709
|
+
multiStepStability: "high",
|
|
1710
|
+
hallucinationRisk: "low",
|
|
1711
|
+
selfCorrection: "high"
|
|
1712
|
+
},
|
|
1713
|
+
preferences: {
|
|
1714
|
+
promptStyle: "freeform",
|
|
1715
|
+
contextOrder: "goal-first",
|
|
1716
|
+
stepSize: "large",
|
|
1717
|
+
needsExamples: false,
|
|
1718
|
+
needsStopConditions: false
|
|
1719
|
+
},
|
|
1720
|
+
antiPatterns: [
|
|
1721
|
+
"\u8FC7\u5EA6\u6307\u5B9A\u8F93\u51FA\u683C\u5F0F\u53EF\u80FD\u6291\u5236\u81EA\u7136\u63A8\u7406"
|
|
1722
|
+
]
|
|
1723
|
+
};
|
|
1724
|
+
var CLAUDE_OPUS = {
|
|
1725
|
+
...CLAUDE_SONNET,
|
|
1726
|
+
model: "claude-opus-4-7"
|
|
1727
|
+
};
|
|
1728
|
+
var CLAUDE_HAIKU = {
|
|
1729
|
+
model: "claude-haiku-4-5-20251001",
|
|
1730
|
+
traits: {
|
|
1731
|
+
instructionFollowing: "high",
|
|
1732
|
+
schemaDiscipline: "high",
|
|
1733
|
+
longContextStability: "high",
|
|
1734
|
+
multiStepStability: "medium",
|
|
1735
|
+
hallucinationRisk: "low",
|
|
1736
|
+
selfCorrection: "medium"
|
|
1737
|
+
},
|
|
1738
|
+
preferences: {
|
|
1739
|
+
promptStyle: "explicit-checklist",
|
|
1740
|
+
contextOrder: "goal-first",
|
|
1741
|
+
stepSize: "medium",
|
|
1742
|
+
needsExamples: false,
|
|
1743
|
+
needsStopConditions: false
|
|
1744
|
+
},
|
|
1745
|
+
antiPatterns: [
|
|
1746
|
+
"\u6DF1\u5EA6\u63A8\u7406\u4EFB\u52A1\u8D85\u51FA\u80FD\u529B\u8FB9\u754C\u65F6\u5E94 escalate \u5230 Sonnet/Opus"
|
|
1747
|
+
]
|
|
1748
|
+
};
|
|
1749
|
+
var DEFAULT_PROFILE = {
|
|
1750
|
+
model: "unknown",
|
|
1751
|
+
traits: {
|
|
1752
|
+
instructionFollowing: "medium",
|
|
1753
|
+
schemaDiscipline: "medium",
|
|
1754
|
+
longContextStability: "medium",
|
|
1755
|
+
multiStepStability: "medium",
|
|
1756
|
+
hallucinationRisk: "medium",
|
|
1757
|
+
selfCorrection: "medium"
|
|
1758
|
+
},
|
|
1759
|
+
preferences: {
|
|
1760
|
+
promptStyle: "explicit-checklist",
|
|
1761
|
+
contextOrder: "goal-first",
|
|
1762
|
+
stepSize: "small",
|
|
1763
|
+
needsExamples: true,
|
|
1764
|
+
needsStopConditions: true
|
|
1765
|
+
},
|
|
1766
|
+
antiPatterns: []
|
|
1767
|
+
};
|
|
1768
|
+
var PROFILE_MAP = {
|
|
1769
|
+
"gpt-4o": GPT4O,
|
|
1770
|
+
"gpt-4o-mini": GPT4O_MINI,
|
|
1771
|
+
"deepseek-chat": DEEPSEEK_CHAT,
|
|
1772
|
+
"deepseek-reasoner": DEEPSEEK_REASONER,
|
|
1773
|
+
"MiniMax-M2.5": MINIMAX_M25,
|
|
1774
|
+
"MiniMax-M2.5-highspeed": MINIMAX_M25_HS,
|
|
1775
|
+
"claude-sonnet-4-6": CLAUDE_SONNET,
|
|
1776
|
+
"claude-opus-4-7": CLAUDE_OPUS,
|
|
1777
|
+
"claude-haiku-4-5-20251001": CLAUDE_HAIKU,
|
|
1778
|
+
// 历史别名
|
|
1779
|
+
"claude-3-5-sonnet-20241022": { ...CLAUDE_SONNET, model: "claude-3-5-sonnet-20241022" },
|
|
1780
|
+
"claude-3-5-haiku-20241022": { ...CLAUDE_HAIKU, model: "claude-3-5-haiku-20241022" }
|
|
1781
|
+
};
|
|
1782
|
+
function getModelProfile(modelName) {
|
|
1783
|
+
const exact = PROFILE_MAP[modelName];
|
|
1784
|
+
if (exact) return exact;
|
|
1785
|
+
for (const [key, profile] of Object.entries(PROFILE_MAP)) {
|
|
1786
|
+
if (modelName.startsWith(key) || key.startsWith(modelName)) {
|
|
1787
|
+
return { ...profile, model: modelName };
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return { ...DEFAULT_PROFILE, model: modelName };
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// src/meta_skill/recipe-resolver.ts
|
|
1794
|
+
function resolveInputStyle(promptStyle) {
|
|
1795
|
+
return promptStyle;
|
|
1796
|
+
}
|
|
1797
|
+
function resolveGranularity(stepSize) {
|
|
1798
|
+
switch (stepSize) {
|
|
1799
|
+
case "large":
|
|
1800
|
+
return "large-batch";
|
|
1801
|
+
case "small":
|
|
1802
|
+
return "stepwise";
|
|
1803
|
+
case "medium":
|
|
1804
|
+
default:
|
|
1805
|
+
return "file-level-first";
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
function resolveOutputFormat(promptStyle) {
|
|
1809
|
+
switch (promptStyle) {
|
|
1810
|
+
case "strict-template":
|
|
1811
|
+
return "strict-template";
|
|
1812
|
+
case "explicit-checklist":
|
|
1813
|
+
return "template";
|
|
1814
|
+
case "freeform":
|
|
1815
|
+
default:
|
|
1816
|
+
return "freeform";
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
function resolveContextStrategy(contextOrder) {
|
|
1820
|
+
switch (contextOrder) {
|
|
1821
|
+
case "goal-first":
|
|
1822
|
+
return "\u5148\u7ED9\u51FA\u4EFB\u52A1\u76EE\u6807\uFF0C\u518D\u9644\u4E0A\u4EE3\u7801\u6750\u6599\uFF0C\u6700\u540E\u7EA6\u675F\u8F93\u51FA\u683C\u5F0F";
|
|
1823
|
+
case "template-first":
|
|
1824
|
+
return "\u5148\u7ED9\u51FA\u8F93\u51FA\u6A21\u677F\uFF0C\u518D\u63CF\u8FF0\u4EFB\u52A1\uFF0C\u6700\u540E\u9644\u4E0A\u6750\u6599";
|
|
1825
|
+
default:
|
|
1826
|
+
return "\u5148\u9644\u4E0A\u6240\u6709\u76F8\u5173\u6750\u6599\uFF0C\u518D\u7ED9\u51FA\u4EFB\u52A1\u76EE\u6807";
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function resolveVerificationStyle(selfCorrection, hallucinationRisk) {
|
|
1830
|
+
if (selfCorrection === "high" && hallucinationRisk === "low") {
|
|
1831
|
+
return "\u81EA\u6821\u9A8C\uFF1A\u8981\u6C42\u6A21\u578B\u5217\u51FA\u7F6E\u4FE1\u5EA6\u4F4E\u7684\u7ED3\u8BBA";
|
|
1832
|
+
}
|
|
1833
|
+
if (hallucinationRisk === "high") {
|
|
1834
|
+
return "\u5F3A\u9A8C\u8BC1\uFF1A\u8981\u6C42\u5F15\u7528\u4EE3\u7801\u884C\u53F7\u4F5C\u4E3A\u6BCF\u6761\u7ED3\u8BBA\u7684\u8BC1\u636E";
|
|
1835
|
+
}
|
|
1836
|
+
return "\u8F7B\u9A8C\u8BC1\uFF1A\u8F93\u51FA\u7ED3\u8BBA\u540E\u7528\u793A\u4F8B\u4EE3\u7801\u7247\u6BB5\u786E\u8BA4";
|
|
1837
|
+
}
|
|
1838
|
+
function resolveFailureModes(profile, taskType) {
|
|
1839
|
+
const modes = [...profile.antiPatterns];
|
|
1840
|
+
if (profile.traits.longContextStability === "low" || profile.traits.longContextStability === "medium") {
|
|
1841
|
+
modes.push("\u957F\u4E0A\u4E0B\u6587\u65F6\u53EF\u80FD\u4E22\u5931\u65E9\u671F\u6307\u4EE4");
|
|
1842
|
+
}
|
|
1843
|
+
if (profile.traits.multiStepStability === "low") {
|
|
1844
|
+
modes.push("\u591A\u6B65\u63A8\u7406\u4E2D\u9014\u53EF\u80FD\u504F\u79BB\u76EE\u6807");
|
|
1845
|
+
}
|
|
1846
|
+
if (taskType === "bugfix") {
|
|
1847
|
+
modes.push("\u53EF\u80FD\u4FEE\u590D\u75C7\u72B6\u800C\u975E\u6839\u672C\u539F\u56E0");
|
|
1848
|
+
modes.push("\u5728\u7F3A\u4E4F\u5B8C\u6574\u4E0A\u4E0B\u6587\u65F6\u6613\u8BEF\u5224 bug \u6240\u5728\u4F4D\u7F6E");
|
|
1849
|
+
}
|
|
1850
|
+
if (taskType === "code-review") {
|
|
1851
|
+
modes.push("\u53EF\u80FD\u53EA\u5173\u6CE8\u4EE3\u7801\u98CE\u683C\u800C\u5FFD\u7565\u903B\u8F91\u7F3A\u9677");
|
|
1852
|
+
modes.push("\u5BF9\u590D\u6742\u4E1A\u52A1\u903B\u8F91\u7684\u8BC4\u5BA1\u6DF1\u5EA6\u4E0D\u8DB3");
|
|
1853
|
+
}
|
|
1854
|
+
return modes;
|
|
1855
|
+
}
|
|
1856
|
+
function resolveRepairStrategies(profile) {
|
|
1857
|
+
const strategies = [];
|
|
1858
|
+
if (profile.traits.multiStepStability === "low" || profile.traits.multiStepStability === "medium") {
|
|
1859
|
+
strategies.push("\u5C06\u4EFB\u52A1\u62C6\u5206\u4E3A\u66F4\u5C0F\u7684\u72EC\u7ACB\u6B65\u9AA4\u540E\u9010\u4E00\u91CD\u8BD5");
|
|
1860
|
+
}
|
|
1861
|
+
if (profile.traits.hallucinationRisk === "high") {
|
|
1862
|
+
strategies.push("\u8981\u6C42\u6A21\u578B\u5728\u91CD\u8BD5\u65F6\u4E3A\u6BCF\u6761\u7ED3\u8BBA\u9644\u4E0A\u4EE3\u7801\u884C\u53F7\u5F15\u7528");
|
|
1863
|
+
}
|
|
1864
|
+
if (profile.preferences.needsStopConditions) {
|
|
1865
|
+
strategies.push("\u5728\u91CD\u8BD5 prompt \u4E2D\u8865\u5145\u660E\u786E\u7684\u5B8C\u6210\u5224\u65AD\u6761\u4EF6\uFF0C\u907F\u514D\u6A21\u578B\u65E0\u9650\u5EF6\u4F38");
|
|
1866
|
+
}
|
|
1867
|
+
if (profile.traits.selfCorrection !== "high") {
|
|
1868
|
+
strategies.push("\u81EA\u52A8\u89E6\u53D1\u5916\u90E8 judge \u5BF9\u8F93\u51FA\u8FDB\u884C\u4E8C\u6B21\u6838\u9A8C\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u63A5\u53D7\u7ED3\u679C");
|
|
1869
|
+
}
|
|
1870
|
+
return strategies;
|
|
1871
|
+
}
|
|
1872
|
+
function resolveMaxRepeatedFailures(selfCorrection) {
|
|
1873
|
+
return selfCorrection === "high" ? 3 : 2;
|
|
1874
|
+
}
|
|
1875
|
+
function resolveRecipe(profile, taskType, baselineWorkflow, inheritedSkills) {
|
|
1876
|
+
return {
|
|
1877
|
+
model: profile.model,
|
|
1878
|
+
taskType,
|
|
1879
|
+
inheritedSkills,
|
|
1880
|
+
baselineWorkflow,
|
|
1881
|
+
inputStyle: resolveInputStyle(profile.preferences.promptStyle),
|
|
1882
|
+
contextStrategy: resolveContextStrategy(profile.preferences.contextOrder),
|
|
1883
|
+
granularity: resolveGranularity(profile.preferences.stepSize),
|
|
1884
|
+
outputContract: {
|
|
1885
|
+
format: resolveOutputFormat(profile.preferences.promptStyle),
|
|
1886
|
+
mustCiteEvidence: profile.traits.hallucinationRisk !== "low",
|
|
1887
|
+
mustListUncertainty: profile.traits.selfCorrection !== "high"
|
|
1888
|
+
},
|
|
1889
|
+
verificationStyle: resolveVerificationStyle(
|
|
1890
|
+
profile.traits.selfCorrection,
|
|
1891
|
+
profile.traits.hallucinationRisk
|
|
1892
|
+
),
|
|
1893
|
+
failureModes: resolveFailureModes(profile, taskType),
|
|
1894
|
+
repairStrategies: resolveRepairStrategies(profile),
|
|
1895
|
+
escalationThreshold: {
|
|
1896
|
+
maxRepeatedFailures: resolveMaxRepeatedFailures(
|
|
1897
|
+
profile.traits.selfCorrection
|
|
1898
|
+
),
|
|
1899
|
+
criticalFailures: [
|
|
1900
|
+
"\u8F93\u51FA\u4E3A\u7A7A",
|
|
1901
|
+
"\u65E0\u6CD5\u7406\u89E3\u4EFB\u52A1\u76EE\u6807",
|
|
1902
|
+
"\u5FAA\u73AF\u91CD\u590D\u76F8\u540C\u5185\u5BB9\u8D85\u8FC7 2 \u6B21"
|
|
1903
|
+
]
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/meta_skill/policy-builder.ts
|
|
1909
|
+
function buildContextOrder(contextStrategy, granularity, constraints, paths) {
|
|
1910
|
+
const hasConstraints = Boolean(constraints && constraints.length > 0);
|
|
1911
|
+
const hasPaths = Boolean(paths && paths.length > 0);
|
|
1912
|
+
let order;
|
|
1913
|
+
if (contextStrategy.includes("\u5148\u7ED9\u51FA\u4EFB\u52A1\u76EE\u6807")) {
|
|
1914
|
+
order = ["\u4EFB\u52A1\u76EE\u6807"];
|
|
1915
|
+
if (hasConstraints) order.push("\u7EA6\u675F\u6761\u4EF6");
|
|
1916
|
+
if (hasPaths) order.push("\u4EE3\u7801\u6750\u6599");
|
|
1917
|
+
order.push("\u8F93\u51FA\u683C\u5F0F\u8981\u6C42");
|
|
1918
|
+
} else if (contextStrategy.includes("\u5148\u7ED9\u51FA\u8F93\u51FA\u6A21\u677F")) {
|
|
1919
|
+
order = ["\u8F93\u51FA\u683C\u5F0F\u8981\u6C42", "\u4EFB\u52A1\u76EE\u6807"];
|
|
1920
|
+
if (hasConstraints) order.push("\u7EA6\u675F\u6761\u4EF6");
|
|
1921
|
+
if (hasPaths) order.push("\u4EE3\u7801\u6750\u6599");
|
|
1922
|
+
} else {
|
|
1923
|
+
order = [];
|
|
1924
|
+
if (hasPaths) order.push("\u4EE3\u7801\u6750\u6599");
|
|
1925
|
+
order.push("\u4EFB\u52A1\u76EE\u6807");
|
|
1926
|
+
if (hasConstraints) order.push("\u7EA6\u675F\u6761\u4EF6");
|
|
1927
|
+
order.push("\u8F93\u51FA\u683C\u5F0F\u8981\u6C42");
|
|
1928
|
+
}
|
|
1929
|
+
if (granularity === "stepwise") {
|
|
1930
|
+
order = order.filter((item) => item !== "\u4EFB\u52A1\u76EE\u6807");
|
|
1931
|
+
order = ["\u4EFB\u52A1\u76EE\u6807", ...order];
|
|
1932
|
+
if (!order.includes("\u5DE5\u4F5C\u6D41\u6B65\u9AA4")) order.push("\u5DE5\u4F5C\u6D41\u6B65\u9AA4");
|
|
1933
|
+
order = order.filter((item) => item !== "\u8F93\u51FA\u683C\u5F0F\u8981\u6C42");
|
|
1934
|
+
order.push("\u8F93\u51FA\u683C\u5F0F\u8981\u6C42");
|
|
1935
|
+
}
|
|
1936
|
+
return order;
|
|
1937
|
+
}
|
|
1938
|
+
function resolveMaxMaterials(granularity) {
|
|
1939
|
+
const map = {
|
|
1940
|
+
"large-batch": 10,
|
|
1941
|
+
"file-level-first": 5,
|
|
1942
|
+
"small-batch": 3,
|
|
1943
|
+
stepwise: 2
|
|
1944
|
+
};
|
|
1945
|
+
return map[granularity];
|
|
1946
|
+
}
|
|
1947
|
+
function buildPolicy(recipe, input) {
|
|
1948
|
+
var _a, _b, _c;
|
|
1949
|
+
const maxSteps = ((_b = (_a = input.runtimeContext) == null ? void 0 : _a.budget) == null ? void 0 : _b.maxSteps) ?? 8;
|
|
1950
|
+
const stepPlan = recipe.baselineWorkflow.slice(0, maxSteps);
|
|
1951
|
+
const mode = stepPlan.length > 1 ? "multi_step" : "single_step";
|
|
1952
|
+
const order = buildContextOrder(
|
|
1953
|
+
recipe.contextStrategy,
|
|
1954
|
+
recipe.granularity,
|
|
1955
|
+
input.task.constraints,
|
|
1956
|
+
(_c = input.task.scope) == null ? void 0 : _c.paths
|
|
1957
|
+
);
|
|
1958
|
+
const maxMaterialsPerStep = resolveMaxMaterials(recipe.granularity);
|
|
1959
|
+
return {
|
|
1960
|
+
mode,
|
|
1961
|
+
stepPlan,
|
|
1962
|
+
selectedSkills: recipe.inheritedSkills,
|
|
1963
|
+
contextPackaging: {
|
|
1964
|
+
order,
|
|
1965
|
+
maxMaterialsPerStep
|
|
1966
|
+
},
|
|
1967
|
+
outputContract: { ...recipe.outputContract },
|
|
1968
|
+
revisionPolicy: {
|
|
1969
|
+
maxRetries: recipe.escalationThreshold.maxRepeatedFailures,
|
|
1970
|
+
retryStyle: recipe.repairStrategies[0] ?? "\u7F29\u51CF\u8F93\u5165\u91CF\u540E\u91CD\u8BD5"
|
|
1971
|
+
},
|
|
1972
|
+
escalationPolicy: {
|
|
1973
|
+
escalateOn: recipe.escalationThreshold.criticalFailures
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// src/meta_skill/materializer.ts
|
|
1979
|
+
function buildTaskCard(policy, profile, input) {
|
|
1980
|
+
const taskType = input.task.type ?? "\uFF08\u672A\u6307\u5B9A\uFF0C\u5F85\u5206\u7C7B\uFF09";
|
|
1981
|
+
const modeLabel = policy.mode === "multi_step" ? "\u591A\u6B65\u6267\u884C" : "\u5355\u6B65\u6267\u884C";
|
|
1982
|
+
const lines = [
|
|
1983
|
+
`# \u4EFB\u52A1\u8BF4\u660E\u5361`,
|
|
1984
|
+
``,
|
|
1985
|
+
`| \u5B57\u6BB5 | \u503C |`,
|
|
1986
|
+
`|------|-----|`,
|
|
1987
|
+
`| \u6A21\u578B | ${profile.model} |`,
|
|
1988
|
+
`| \u4EFB\u52A1\u7C7B\u578B | ${taskType} |`,
|
|
1989
|
+
`| \u6267\u884C\u6A21\u5F0F | ${modeLabel} |`,
|
|
1990
|
+
``,
|
|
1991
|
+
`## \u76EE\u6807`,
|
|
1992
|
+
``,
|
|
1993
|
+
input.task.goal,
|
|
1994
|
+
``
|
|
1995
|
+
];
|
|
1996
|
+
if (input.task.constraints && input.task.constraints.length > 0) {
|
|
1997
|
+
lines.push(`## \u7EA6\u675F`);
|
|
1998
|
+
lines.push(``);
|
|
1999
|
+
for (const c of input.task.constraints) {
|
|
2000
|
+
lines.push(`- ${c}`);
|
|
2001
|
+
}
|
|
2002
|
+
lines.push(``);
|
|
2003
|
+
}
|
|
2004
|
+
lines.push(`## \u9009\u7528 Skills`);
|
|
2005
|
+
lines.push(``);
|
|
2006
|
+
if (policy.selectedSkills.length > 0) {
|
|
2007
|
+
for (const skill of policy.selectedSkills) {
|
|
2008
|
+
lines.push(`- ${skill}`);
|
|
2009
|
+
}
|
|
2010
|
+
} else {
|
|
2011
|
+
lines.push(`- \uFF08\u65E0\uFF09`);
|
|
2012
|
+
}
|
|
2013
|
+
lines.push(``);
|
|
2014
|
+
lines.push(`## \u6267\u884C\u6B65\u9AA4`);
|
|
2015
|
+
lines.push(``);
|
|
2016
|
+
policy.stepPlan.forEach((step, idx) => {
|
|
2017
|
+
lines.push(`${idx + 1}. ${step}`);
|
|
2018
|
+
});
|
|
2019
|
+
lines.push(``);
|
|
2020
|
+
lines.push(`## \u8F93\u51FA\u8981\u6C42`);
|
|
2021
|
+
lines.push(``);
|
|
2022
|
+
lines.push(`- \u683C\u5F0F\uFF1A${policy.outputContract.format}`);
|
|
2023
|
+
if (policy.outputContract.mustCiteEvidence) {
|
|
2024
|
+
lines.push(`- \u5FC5\u987B\u5F15\u7528\u4EE3\u7801\u8BC1\u636E`);
|
|
2025
|
+
}
|
|
2026
|
+
if (policy.outputContract.mustListUncertainty) {
|
|
2027
|
+
lines.push(`- \u5FC5\u987B\u5217\u51FA\u4E0D\u786E\u5B9A\u9879`);
|
|
2028
|
+
}
|
|
2029
|
+
return lines.join("\n");
|
|
2030
|
+
}
|
|
2031
|
+
function buildStrictTemplateWorkerPrompt(policy, input) {
|
|
2032
|
+
const sections = [];
|
|
2033
|
+
sections.push(`\u3010\u4EFB\u52A1\u76EE\u6807\u3011
|
|
2034
|
+
${input.task.goal}`);
|
|
2035
|
+
if (input.task.constraints && input.task.constraints.length > 0) {
|
|
2036
|
+
sections.push(
|
|
2037
|
+
`\u3010\u7EA6\u675F\u6761\u4EF6\u3011
|
|
2038
|
+
${input.task.constraints.map((c) => `- ${c}`).join("\n")}`
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
const stepsText = policy.stepPlan.map((step, i) => `${i + 1}. ${step}`).join("\n");
|
|
2042
|
+
sections.push(
|
|
2043
|
+
`\u3010\u6267\u884C\u6B65\u9AA4\u3011\uFF08\u5FC5\u987B\u6309\u5E8F\uFF0C\u4E0D\u5F97\u8DF3\u6B65\uFF09
|
|
2044
|
+
${stepsText}`
|
|
2045
|
+
);
|
|
2046
|
+
const evidenceNote = policy.outputContract.mustCiteEvidence ? "\n- \u6BCF\u9879\u7ED3\u8BBA\u5FC5\u987B\u9644\u4EE3\u7801\u884C\u53F7\u6216\u539F\u6587\u5F15\u7528" : "";
|
|
2047
|
+
const uncertaintyNote = policy.outputContract.mustListUncertainty ? "\n- \u4E0D\u786E\u5B9A\u9879\u987B\u5355\u72EC\u5217\u51FA\uFF0C\u4E0D\u5F97\u6DF7\u5165\u7ED3\u8BBA" : "";
|
|
2048
|
+
sections.push(
|
|
2049
|
+
`\u3010\u8F93\u51FA\u683C\u5F0F\u3011
|
|
2050
|
+
\u683C\u5F0F\u8981\u6C42\uFF1A${policy.outputContract.format}${evidenceNote}${uncertaintyNote}`
|
|
2051
|
+
);
|
|
2052
|
+
sections.push(
|
|
2053
|
+
`\u3010\u505C\u6B62\u6761\u4EF6\u3011
|
|
2054
|
+
\u5B8C\u6210\u5168\u90E8\u6B65\u9AA4\u4E14\u8F93\u51FA\u6EE1\u8DB3\u683C\u5F0F\u8981\u6C42\u540E\u505C\u6B62\uFF0C\u4E0D\u5F97\u8FFD\u52A0\u989D\u5916\u5185\u5BB9\u3002`
|
|
2055
|
+
);
|
|
2056
|
+
return sections.join("\n\n");
|
|
2057
|
+
}
|
|
2058
|
+
function buildExplicitChecklistWorkerPrompt(policy, input) {
|
|
2059
|
+
const lines = [];
|
|
2060
|
+
lines.push(`## \u4EFB\u52A1\u76EE\u6807
|
|
2061
|
+
|
|
2062
|
+
${input.task.goal}`);
|
|
2063
|
+
if (input.task.constraints && input.task.constraints.length > 0) {
|
|
2064
|
+
lines.push(
|
|
2065
|
+
`
|
|
2066
|
+
## \u7EA6\u675F\u6761\u4EF6
|
|
2067
|
+
|
|
2068
|
+
${input.task.constraints.map((c) => `- ${c}`).join("\n")}`
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
lines.push(`
|
|
2072
|
+
## \u6267\u884C\u6B65\u9AA4
|
|
2073
|
+
`);
|
|
2074
|
+
for (const step of policy.stepPlan) {
|
|
2075
|
+
lines.push(`- [ ] ${step}`);
|
|
2076
|
+
}
|
|
2077
|
+
lines.push(`
|
|
2078
|
+
## \u8F93\u51FA\u683C\u5F0F
|
|
2079
|
+
`);
|
|
2080
|
+
lines.push(`- \u683C\u5F0F\uFF1A${policy.outputContract.format}`);
|
|
2081
|
+
if (policy.outputContract.mustCiteEvidence) {
|
|
2082
|
+
lines.push(`- \u6CE8\u610F\uFF1A\u6BCF\u9879\u7ED3\u8BBA\u987B\u9644\u4EE3\u7801\u8BC1\u636E\uFF08\u884C\u53F7\u6216\u539F\u6587\u7247\u6BB5\uFF09`);
|
|
2083
|
+
}
|
|
2084
|
+
return lines.join("\n");
|
|
2085
|
+
}
|
|
2086
|
+
function buildFreeformWorkerPrompt(policy, input) {
|
|
2087
|
+
const paragraphs = [];
|
|
2088
|
+
paragraphs.push(`\u8BF7\u5B8C\u6210\u4EE5\u4E0B\u4EFB\u52A1\uFF1A${input.task.goal}`);
|
|
2089
|
+
if (input.task.constraints && input.task.constraints.length > 0) {
|
|
2090
|
+
paragraphs.push(
|
|
2091
|
+
`\u6267\u884C\u65F6\u9700\u9075\u5B88\u4EE5\u4E0B\u7EA6\u675F\uFF1A${input.task.constraints.join("\uFF1B")}\u3002`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
paragraphs.push(
|
|
2095
|
+
`\u6267\u884C\u987A\u5E8F\u5982\u4E0B\uFF1A${policy.stepPlan.map((s, i) => `\uFF08${i + 1}\uFF09${s}`).join(" \u2192 ")}\u3002`
|
|
2096
|
+
);
|
|
2097
|
+
const evidencePart = policy.outputContract.mustCiteEvidence ? " \u6BCF\u9879\u7ED3\u8BBA\u9700\u9644\u5F15\u7528\u8BC1\u636E\u3002" : "";
|
|
2098
|
+
const uncertaintyPart = policy.outputContract.mustListUncertainty ? " \u4E0D\u786E\u5B9A\u4E4B\u5904\u8BF7\u5355\u72EC\u5217\u660E\u3002" : "";
|
|
2099
|
+
paragraphs.push(
|
|
2100
|
+
`\u8F93\u51FA\u683C\u5F0F\u4E3A ${policy.outputContract.format}\u3002${evidencePart}${uncertaintyPart}`
|
|
2101
|
+
);
|
|
2102
|
+
return paragraphs.join("\n\n");
|
|
2103
|
+
}
|
|
2104
|
+
function buildWorkerPrompt(policy, profile, input) {
|
|
2105
|
+
switch (profile.preferences.promptStyle) {
|
|
2106
|
+
case "strict-template":
|
|
2107
|
+
return buildStrictTemplateWorkerPrompt(policy, input);
|
|
2108
|
+
case "explicit-checklist":
|
|
2109
|
+
return buildExplicitChecklistWorkerPrompt(policy, input);
|
|
2110
|
+
case "freeform":
|
|
2111
|
+
default:
|
|
2112
|
+
return buildFreeformWorkerPrompt(policy, input);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
function buildJudgePrompt(policy, input) {
|
|
2116
|
+
const lines = [
|
|
2117
|
+
`# Judge \u8BC4\u4F30 Prompt`,
|
|
2118
|
+
``,
|
|
2119
|
+
`\u4F60\u662F\u672C\u8F6E\u4EFB\u52A1\u7684\u9A8C\u8BC1\u65B9\u3002\u8BF7\u6309\u7167\u4EE5\u4E0B\u6E05\u5355\u9010\u9879\u6838\u67E5 worker \u7684\u8F93\u51FA\uFF0C\u7136\u540E\u7ED9\u51FA\u6700\u7EC8\u88C1\u5B9A\u3002`,
|
|
2120
|
+
``,
|
|
2121
|
+
`## \u6838\u67E5\u6E05\u5355`,
|
|
2122
|
+
``
|
|
2123
|
+
];
|
|
2124
|
+
policy.stepPlan.forEach((step, idx) => {
|
|
2125
|
+
lines.push(`- [ ] \u6B65\u9AA4 ${idx + 1} \u5DF2\u5B8C\u6210\uFF1A${step}`);
|
|
2126
|
+
});
|
|
2127
|
+
if (policy.outputContract.mustCiteEvidence) {
|
|
2128
|
+
lines.push(`- [ ] \u6BCF\u9879\u7ED3\u8BBA\u5747\u9644\u6709\u4EE3\u7801\u8BC1\u636E\uFF08\u884C\u53F7\u6216\u539F\u6587\u5F15\u7528\uFF09`);
|
|
2129
|
+
}
|
|
2130
|
+
if (policy.outputContract.mustListUncertainty) {
|
|
2131
|
+
lines.push(`- [ ] \u4E0D\u786E\u5B9A\u9879\u5DF2\u5355\u72EC\u5217\u51FA\uFF0C\u672A\u6DF7\u5165\u7ED3\u8BBA`);
|
|
2132
|
+
}
|
|
2133
|
+
lines.push(
|
|
2134
|
+
`- [ ] \u8F93\u51FA\u683C\u5F0F\u7B26\u5408\u8981\u6C42\uFF08\u671F\u671B\u683C\u5F0F\uFF1A${policy.outputContract.format}\uFF09`
|
|
2135
|
+
);
|
|
2136
|
+
lines.push(``);
|
|
2137
|
+
lines.push(`## \u8BC4\u5206\u7EF4\u5EA6\uFF08\u5404\u7EF4\u5EA6 0\u201310 \u5206\uFF09`);
|
|
2138
|
+
lines.push(``);
|
|
2139
|
+
lines.push(`| \u7EF4\u5EA6 | \u5F97\u5206 | \u8BF4\u660E |`);
|
|
2140
|
+
lines.push(`|------|------|------|`);
|
|
2141
|
+
lines.push(`| \u5B8C\u6574\u6027 | \u2014 | \u6240\u6709\u6B65\u9AA4\u662F\u5426\u5168\u90E8\u5B8C\u6210 |`);
|
|
2142
|
+
lines.push(`| \u8BC1\u636E\u8D28\u91CF | \u2014 | \u5F15\u7528\u662F\u5426\u51C6\u786E\u4E14\u5145\u5206 |`);
|
|
2143
|
+
lines.push(`| \u683C\u5F0F\u89C4\u8303 | \u2014 | \u8F93\u51FA\u662F\u5426\u7B26\u5408\u6307\u5B9A\u683C\u5F0F |`);
|
|
2144
|
+
lines.push(``);
|
|
2145
|
+
lines.push(`## \u6700\u7EC8\u88C1\u5B9A`);
|
|
2146
|
+
lines.push(``);
|
|
2147
|
+
lines.push(`\u4ECE\u4EE5\u4E0B\u4E09\u9879\u4E2D\u9009\u62E9\u4E00\u9879\uFF1A`);
|
|
2148
|
+
lines.push(``);
|
|
2149
|
+
lines.push(`- **pass**\uFF1A\u6240\u6709\u6E05\u5355\u9879\u901A\u8FC7\uFF0C\u4E09\u4E2A\u7EF4\u5EA6\u5747 \u2265 7 \u5206`);
|
|
2150
|
+
lines.push(
|
|
2151
|
+
`- **revise**\uFF1A\u5B58\u5728\u53EF\u4FEE\u590D\u7684\u95EE\u9898\u3002\u8BF7\u5728\u88C1\u5B9A\u540E\u5217\u51FA\u5177\u4F53\u4FEE\u6539\u610F\u89C1\uFF0Cworker \u9700\u5728 ${policy.revisionPolicy.maxRetries} \u6B21\u5185\u5B8C\u6210\u4FEE\u6539\u3002\u4FEE\u590D\u7B56\u7565\uFF1A${policy.revisionPolicy.retryStyle}`
|
|
2152
|
+
);
|
|
2153
|
+
if (policy.escalationPolicy.escalateOn.length > 0) {
|
|
2154
|
+
const triggers = policy.escalationPolicy.escalateOn.join("\u3001");
|
|
2155
|
+
lines.push(
|
|
2156
|
+
`- **escalate**\uFF1A\u9047\u5230\u4EE5\u4E0B\u60C5\u5F62\u65F6\u5FC5\u987B\u5347\u7EA7\uFF0C\u4E0D\u5F97\u5C1D\u8BD5\u4FEE\u590D\uFF1A${triggers}`
|
|
2157
|
+
);
|
|
2158
|
+
} else {
|
|
2159
|
+
lines.push(`- **escalate**\uFF1A\u9047\u5230\u65E0\u6CD5\u901A\u8FC7\u91CD\u8BD5\u4FEE\u590D\u7684\u6839\u672C\u6027\u95EE\u9898\u65F6\u5347\u7EA7`);
|
|
2160
|
+
}
|
|
2161
|
+
lines.push(``);
|
|
2162
|
+
lines.push(`> \u88C1\u5B9A\u683C\u5F0F\uFF1A\u5728\u6587\u672B\u5355\u72EC\u4E00\u884C\u5199 \`verdict: pass\` / \`verdict: revise\` / \`verdict: escalate\`\u3002`);
|
|
2163
|
+
return lines.join("\n");
|
|
2164
|
+
}
|
|
2165
|
+
function materialize(policy, profile, input) {
|
|
2166
|
+
return {
|
|
2167
|
+
taskCard: buildTaskCard(policy, profile, input),
|
|
2168
|
+
workerPrompt: buildWorkerPrompt(policy, profile, input),
|
|
2169
|
+
judgePrompt: buildJudgePrompt(policy, input)
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// src/meta_skill/learning-store.ts
|
|
2174
|
+
import * as fs2 from "fs";
|
|
2175
|
+
import * as path2 from "path";
|
|
2176
|
+
import * as os from "os";
|
|
2177
|
+
var STORE_PATH = path2.resolve(os.homedir(), ".ecode", "learning-records.jsonl");
|
|
2178
|
+
|
|
2179
|
+
// src/meta_skill/index.ts
|
|
2180
|
+
function resolveBuiltinSkillsDir() {
|
|
2181
|
+
try {
|
|
2182
|
+
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
2183
|
+
for (let i = 0; i < 4; i++) {
|
|
2184
|
+
const candidate = path3.join(dir, "skills");
|
|
2185
|
+
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
2186
|
+
const hasSkillContent = fs3.readdirSync(candidate).some(
|
|
2187
|
+
(entry) => fs3.existsSync(path3.join(candidate, entry, "SKILL.md"))
|
|
2188
|
+
);
|
|
2189
|
+
if (hasSkillContent) return candidate;
|
|
2190
|
+
}
|
|
2191
|
+
const parent = path3.dirname(dir);
|
|
2192
|
+
if (parent === dir) break;
|
|
2193
|
+
dir = parent;
|
|
2194
|
+
}
|
|
2195
|
+
} catch {
|
|
2196
|
+
}
|
|
2197
|
+
return null;
|
|
2198
|
+
}
|
|
2199
|
+
function runMetaAlign(input) {
|
|
2200
|
+
const taskType = input.task.type && isValidTaskType(input.task.type) ? input.task.type : classifyTask(input.task.goal);
|
|
2201
|
+
const inheritedSkills = getBaselineSkills(taskType);
|
|
2202
|
+
const builtinSkillsDir = resolveBuiltinSkillsDir();
|
|
2203
|
+
const allSteps = [];
|
|
2204
|
+
for (const skillName of inheritedSkills) {
|
|
2205
|
+
const candidates = [];
|
|
2206
|
+
if (builtinSkillsDir) {
|
|
2207
|
+
candidates.push(path3.join(builtinSkillsDir, skillName, "SKILL.md"));
|
|
2208
|
+
candidates.push(path3.join(builtinSkillsDir, skillName + ".md"));
|
|
2209
|
+
}
|
|
2210
|
+
candidates.push(path3.join(process.cwd(), "skills", skillName, "SKILL.md"));
|
|
2211
|
+
candidates.push(path3.join(process.cwd(), "skills", skillName + ".md"));
|
|
2212
|
+
let body = "";
|
|
2213
|
+
for (const p of candidates) {
|
|
2214
|
+
if (fs3.existsSync(p)) {
|
|
2215
|
+
body = fs3.readFileSync(p, { encoding: "utf-8" });
|
|
2216
|
+
break;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
const steps = extractWorkflowSteps(body);
|
|
2220
|
+
allSteps.push(...steps);
|
|
2221
|
+
}
|
|
2222
|
+
const baselineWorkflow = [...new Set(allSteps)].slice(0, 12);
|
|
2223
|
+
const profile = getModelProfile(input.model.name);
|
|
2224
|
+
const recipe = resolveRecipe(profile, taskType, baselineWorkflow, inheritedSkills);
|
|
2225
|
+
const policy = buildPolicy(recipe, input);
|
|
2226
|
+
const artifacts = materialize(policy, profile, input);
|
|
2227
|
+
const strengths = [];
|
|
2228
|
+
const weaknesses = [];
|
|
2229
|
+
const t = profile.traits;
|
|
2230
|
+
if (t.instructionFollowing === "high") strengths.push("\u6307\u4EE4\u8DDF\u968F\u80FD\u529B\u5F3A");
|
|
2231
|
+
else if (t.instructionFollowing === "low") weaknesses.push("\u6307\u4EE4\u8DDF\u968F\u80FD\u529B\u5F31");
|
|
2232
|
+
if (t.schemaDiscipline === "high") strengths.push("\u7ED3\u6784\u5316\u8F93\u51FA\u9075\u5B88\u7A0B\u5EA6\u9AD8");
|
|
2233
|
+
else if (t.schemaDiscipline === "low") weaknesses.push("\u7ED3\u6784\u5316\u8F93\u51FA\u9075\u5B88\u7A0B\u5EA6\u4F4E");
|
|
2234
|
+
if (t.longContextStability === "high") strengths.push("\u957F\u4E0A\u4E0B\u6587\u7A33\u5B9A\u6027\u597D");
|
|
2235
|
+
else if (t.longContextStability === "low") weaknesses.push("\u957F\u4E0A\u4E0B\u6587\u7A33\u5B9A\u6027\u5DEE");
|
|
2236
|
+
if (t.multiStepStability === "high") strengths.push("\u591A\u6B65\u63A8\u7406\u7A33\u5B9A");
|
|
2237
|
+
else if (t.multiStepStability === "low") weaknesses.push("\u591A\u6B65\u63A8\u7406\u7A33\u5B9A\u6027\u4F4E");
|
|
2238
|
+
if (t.hallucinationRisk === "low") strengths.push("\u5E7B\u89C9\u98CE\u9669\u4F4E");
|
|
2239
|
+
else if (t.hallucinationRisk === "high") weaknesses.push("\u5E7B\u89C9\u98CE\u9669\u9AD8");
|
|
2240
|
+
if (t.selfCorrection === "high") strengths.push("\u81EA\u6211\u7EA0\u9519\u80FD\u529B\u5F3A");
|
|
2241
|
+
else if (t.selfCorrection === "low") weaknesses.push("\u81EA\u6211\u7EA0\u9519\u80FD\u529B\u5F31");
|
|
2242
|
+
const modelProfileSummary = {
|
|
2243
|
+
strengths,
|
|
2244
|
+
weaknesses,
|
|
2245
|
+
promptStyle: profile.preferences.promptStyle
|
|
2246
|
+
};
|
|
2247
|
+
return {
|
|
2248
|
+
resolvedTaskType: taskType,
|
|
2249
|
+
modelProfileSummary,
|
|
2250
|
+
executionPolicy: policy,
|
|
2251
|
+
materializedArtifacts: artifacts,
|
|
2252
|
+
recipeCandidate: {
|
|
2253
|
+
model: recipe.model,
|
|
2254
|
+
taskType: recipe.taskType,
|
|
2255
|
+
inheritedSkills: recipe.inheritedSkills
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/ui/App.tsx
|
|
2261
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2262
|
+
async function handleAutomationCommand(input, manager, sessionContext) {
|
|
2263
|
+
if (!input.startsWith("/")) return null;
|
|
2264
|
+
const spaceIdx = input.indexOf(" ");
|
|
2265
|
+
const command = spaceIdx === -1 ? input : input.slice(0, spaceIdx);
|
|
2266
|
+
const args = spaceIdx === -1 ? "" : input.slice(spaceIdx + 1);
|
|
2267
|
+
switch (command) {
|
|
2268
|
+
case "/loop":
|
|
2269
|
+
return manager.cmdLoop(args);
|
|
2270
|
+
// 传入会话快照:goal blocked 时可生成 ecode --fork 回滚命令
|
|
2271
|
+
case "/goal":
|
|
2272
|
+
return manager.cmdGoal(args, sessionContext);
|
|
2273
|
+
case "/jobs":
|
|
2274
|
+
return manager.cmdJobs();
|
|
2275
|
+
case "/unloop":
|
|
2276
|
+
return manager.cmdUnloop(args.trim());
|
|
2277
|
+
case "/ungoal":
|
|
2278
|
+
return manager.cmdUngoal(args.trim());
|
|
2279
|
+
default:
|
|
2280
|
+
return null;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
function handleMetaAlignCommand(input) {
|
|
2284
|
+
if (!input.startsWith("/meta-align")) return null;
|
|
2285
|
+
const parts = input.slice("/meta-align".length).trim().split(/\s+/);
|
|
2286
|
+
if (parts.length === 0 || !parts[0]) {
|
|
2287
|
+
return [
|
|
2288
|
+
"**\u7528\u6CD5\uFF1A** `/meta-align <model> [task-type] [goal]`",
|
|
2289
|
+
"",
|
|
2290
|
+
"**\u793A\u4F8B\uFF1A**",
|
|
2291
|
+
"- `/meta-align gpt-4o bugfix \u4FEE\u590D\u767B\u5F55\u9875\u5D29\u6E83\u95EE\u9898`",
|
|
2292
|
+
"- `/meta-align MiniMax-M2.5 code-review`",
|
|
2293
|
+
"- `/meta-align deepseek-chat \u9006\u5411\u5206\u6790 src/ui \u76EE\u5F55\u7ED3\u6784`"
|
|
2294
|
+
].join("\n");
|
|
2295
|
+
}
|
|
2296
|
+
const VALID_TASK_TYPES = /* @__PURE__ */ new Set([
|
|
2297
|
+
"bugfix",
|
|
2298
|
+
"refactor",
|
|
2299
|
+
"test-generation",
|
|
2300
|
+
"code-review",
|
|
2301
|
+
"code-reverse-design"
|
|
2302
|
+
]);
|
|
2303
|
+
const model = parts[0];
|
|
2304
|
+
let taskType;
|
|
2305
|
+
let goalParts;
|
|
2306
|
+
if (parts.length >= 2 && VALID_TASK_TYPES.has(parts[1])) {
|
|
2307
|
+
taskType = parts[1];
|
|
2308
|
+
goalParts = parts.slice(2);
|
|
2309
|
+
} else {
|
|
2310
|
+
goalParts = parts.slice(1);
|
|
2311
|
+
}
|
|
2312
|
+
const goal = goalParts.join(" ") || `\u4F7F\u7528 ${model} \u5B8C\u6210\u4EFB\u52A1`;
|
|
2313
|
+
const alignInput = {
|
|
2314
|
+
model: { name: model },
|
|
2315
|
+
task: {
|
|
2316
|
+
type: taskType,
|
|
2317
|
+
goal
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
try {
|
|
2321
|
+
const result = runMetaAlign(alignInput);
|
|
2322
|
+
return formatMetaAlignOutput(result, model);
|
|
2323
|
+
} catch (err) {
|
|
2324
|
+
return `**[meta-align \u9519\u8BEF]** ${String(err)}`;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
function formatMetaAlignOutput(result, model) {
|
|
2328
|
+
const { resolvedTaskType, modelProfileSummary, executionPolicy, materializedArtifacts } = result;
|
|
2329
|
+
const lines = [
|
|
2330
|
+
`## meta-align \u7ED3\u679C`,
|
|
2331
|
+
``,
|
|
2332
|
+
`**\u6A21\u578B\uFF1A** ${model} `,
|
|
2333
|
+
`**\u4EFB\u52A1\u7C7B\u578B\uFF1A** ${resolvedTaskType} `,
|
|
2334
|
+
`**\u6267\u884C\u6A21\u5F0F\uFF1A** ${executionPolicy.mode === "multi_step" ? "\u591A\u6B65\u6267\u884C" : "\u5355\u6B65\u6267\u884C"}`,
|
|
2335
|
+
``
|
|
2336
|
+
];
|
|
2337
|
+
if (modelProfileSummary.strengths.length > 0) {
|
|
2338
|
+
lines.push(`### \u6A21\u578B\u4F18\u52BF`);
|
|
2339
|
+
for (const s of modelProfileSummary.strengths) lines.push(`- ${s}`);
|
|
2340
|
+
lines.push(``);
|
|
2341
|
+
}
|
|
2342
|
+
if (modelProfileSummary.weaknesses.length > 0) {
|
|
2343
|
+
lines.push(`### \u6A21\u578B\u5F31\u70B9`);
|
|
2344
|
+
for (const w of modelProfileSummary.weaknesses) lines.push(`- ${w}`);
|
|
2345
|
+
lines.push(``);
|
|
2346
|
+
}
|
|
2347
|
+
if (executionPolicy.selectedSkills.length > 0) {
|
|
2348
|
+
lines.push(`### \u9009\u7528 Skills`);
|
|
2349
|
+
for (const sk of executionPolicy.selectedSkills) lines.push(`- \`${sk}\``);
|
|
2350
|
+
lines.push(``);
|
|
2351
|
+
}
|
|
2352
|
+
if (executionPolicy.stepPlan.length > 0) {
|
|
2353
|
+
lines.push(`### \u6267\u884C\u6B65\u9AA4`);
|
|
2354
|
+
executionPolicy.stepPlan.forEach((step, i) => lines.push(`${i + 1}. ${step}`));
|
|
2355
|
+
lines.push(``);
|
|
2356
|
+
}
|
|
2357
|
+
lines.push(`### \u4E0A\u4E0B\u6587\u5C01\u88C5\u987A\u5E8F`);
|
|
2358
|
+
executionPolicy.contextPackaging.order.forEach((o, i) => lines.push(`${i + 1}. ${o}`));
|
|
2359
|
+
lines.push(``);
|
|
2360
|
+
lines.push(`### Worker Prompt`);
|
|
2361
|
+
lines.push(``);
|
|
2362
|
+
lines.push("```");
|
|
2363
|
+
lines.push(materializedArtifacts.workerPrompt);
|
|
2364
|
+
lines.push("```");
|
|
2365
|
+
lines.push(``);
|
|
2366
|
+
lines.push(
|
|
2367
|
+
`**Retry \u7B56\u7565\uFF1A** \u6700\u591A ${executionPolicy.revisionPolicy.maxRetries} \u6B21 `,
|
|
2368
|
+
`**Retry \u65B9\u5F0F\uFF1A** ${executionPolicy.revisionPolicy.retryStyle}`
|
|
2369
|
+
);
|
|
2370
|
+
return lines.join("\n");
|
|
2371
|
+
}
|
|
2372
|
+
function App({ config, version, autoMode = false, registry, trustedSkillDirs = [], initialMessages = [], llmClient }) {
|
|
2373
|
+
const { stdout } = useStdout();
|
|
2374
|
+
const { stdin } = useStdin();
|
|
2375
|
+
const historyMaxHeight = Math.max(5, ((stdout == null ? void 0 : stdout.rows) ?? 24) - 4);
|
|
2376
|
+
const [messages, setMessages] = useState3(initialMessages);
|
|
2377
|
+
const [status, setStatus] = useState3("idle");
|
|
2378
|
+
const contextLimit = getContextLimit(config.model, config.contextLimit);
|
|
2379
|
+
const [tokenUsage, setTokenUsage] = useState3({
|
|
2380
|
+
used: 0,
|
|
2381
|
+
estimated: true,
|
|
2382
|
+
limit: contextLimit
|
|
2383
|
+
});
|
|
2384
|
+
const [toolName, setToolName] = useState3(void 0);
|
|
2385
|
+
const [confirmPrompt, setConfirmPrompt] = useState3(void 0);
|
|
2386
|
+
const [expandTools, setExpandTools] = useState3(false);
|
|
2387
|
+
const [scrollOffset, setScrollOffset] = useState3(0);
|
|
2388
|
+
const [inputHistory, setInputHistory] = useState3([]);
|
|
2389
|
+
const inputHistoryRef = useRef2([]);
|
|
2390
|
+
inputHistoryRef.current = inputHistory;
|
|
2391
|
+
const historyIndexRef = useRef2(-1);
|
|
2392
|
+
const isNavigatingHistoryRef = useRef2(false);
|
|
2393
|
+
const totalLines = useMemo(() => {
|
|
2394
|
+
const visible = messages.filter((m) => m.role !== "system");
|
|
2395
|
+
return visible.reduce(
|
|
2396
|
+
(acc, msg) => acc + messageToLines(msg, expandTools, (stdout == null ? void 0 : stdout.columns) ?? 0).length,
|
|
2397
|
+
0
|
|
2398
|
+
);
|
|
2399
|
+
}, [messages, expandTools, stdout == null ? void 0 : stdout.columns]);
|
|
2400
|
+
const totalLinesRef = useRef2(totalLines);
|
|
2401
|
+
totalLinesRef.current = totalLines;
|
|
2402
|
+
const pendingConfirmRef = useRef2(null);
|
|
2403
|
+
const abortControllerRef = useRef2(null);
|
|
2404
|
+
const llmRef = useRef2(llmClient ?? createProvider(resolveActiveProfile(config)));
|
|
2405
|
+
const automationDataDir = config.logDir ? join4(config.logDir, "automation") : join4(homedir2(), ".config", "ecode", "automation");
|
|
2406
|
+
const automationManagerRef = useRef2(
|
|
2407
|
+
new AutomationManager({
|
|
2408
|
+
dataDir: automationDataDir,
|
|
2409
|
+
logDir: automationDataDir,
|
|
2410
|
+
llmClient: llmRef.current,
|
|
2411
|
+
onJobResult: (_jobId, message) => {
|
|
2412
|
+
setMessages((prev) => [...prev, { role: "assistant", content: message }]);
|
|
2413
|
+
}
|
|
2414
|
+
})
|
|
2415
|
+
);
|
|
2416
|
+
const inputRef = useRef2(null);
|
|
2417
|
+
const [skillTools, setSkillTools] = useState3([]);
|
|
2418
|
+
const skillToolsRef = useRef2([]);
|
|
2419
|
+
skillToolsRef.current = skillTools;
|
|
2420
|
+
const [acState, setAcState] = useState3(getInitialState());
|
|
2421
|
+
const [fileAcState, setFileAcState] = useState3(getInitialState2());
|
|
2422
|
+
const [fileSuggestions, setFileSuggestions] = useState3([]);
|
|
2423
|
+
const loggerRef = useRef2(null);
|
|
2424
|
+
const loggedCountRef = useRef2(0);
|
|
2425
|
+
const sessionMetaRef = useRef2(null);
|
|
2426
|
+
const finalTokensRef = useRef2(0);
|
|
2427
|
+
useEffect3(() => {
|
|
2428
|
+
if (config.logDir) {
|
|
2429
|
+
const now = /* @__PURE__ */ new Date();
|
|
2430
|
+
loggerRef.current = createLogger(config.logDir, now);
|
|
2431
|
+
const meta = createSessionMetadata(loggerRef.current.filePath, config.model);
|
|
2432
|
+
writeSessionMetadata(loggerRef.current.filePath, meta);
|
|
2433
|
+
sessionMetaRef.current = meta;
|
|
2434
|
+
}
|
|
2435
|
+
}, []);
|
|
2436
|
+
useEffect3(() => {
|
|
2437
|
+
if (!loggerRef.current) return;
|
|
2438
|
+
for (let i = loggedCountRef.current; i < messages.length; i++) {
|
|
2439
|
+
const msg = messages[i];
|
|
2440
|
+
loggerRef.current.append({
|
|
2441
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2442
|
+
role: msg.role,
|
|
2443
|
+
// content 可能为 null(纯工具调用的 assistant 消息),日志中保留 null
|
|
2444
|
+
content: typeof msg.content === "string" ? msg.content : null,
|
|
2445
|
+
// tool_call_id 只在 tool 角色消息中存在,用于关联工具调用与结果
|
|
2446
|
+
tool_call_id: "tool_call_id" in msg ? msg.tool_call_id : void 0,
|
|
2447
|
+
// tool_calls 只在 assistant 角色消息中存在(当 LLM 决定调用工具时)
|
|
2448
|
+
tool_calls: "tool_calls" in msg ? msg.tool_calls : void 0
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
loggedCountRef.current = messages.length;
|
|
2452
|
+
}, [messages]);
|
|
2453
|
+
useEffect3(() => {
|
|
2454
|
+
void automationManagerRef.current.start();
|
|
2455
|
+
return () => {
|
|
2456
|
+
automationManagerRef.current.stop();
|
|
2457
|
+
};
|
|
2458
|
+
}, []);
|
|
2459
|
+
useEffect3(() => {
|
|
2460
|
+
if (!stdin || !stdout) return;
|
|
2461
|
+
stdout.write("\x1B[?1000h\x1B[?1006h");
|
|
2462
|
+
const origEmit = stdin.emit;
|
|
2463
|
+
stdin.emit = function(event, ...args) {
|
|
2464
|
+
if (event === "readable") {
|
|
2465
|
+
const chunk = stdin.read();
|
|
2466
|
+
if (chunk !== null) {
|
|
2467
|
+
const s = typeof chunk === "string" ? chunk : chunk.toString("binary");
|
|
2468
|
+
const mouseLen = mouseEventLength(s);
|
|
2469
|
+
if (mouseLen > 0) {
|
|
2470
|
+
const mouseStr = s.slice(0, mouseLen);
|
|
2471
|
+
const scrollEvent = parseMouseScroll(mouseStr);
|
|
2472
|
+
if (scrollEvent) {
|
|
2473
|
+
if (scrollEvent.direction === "up") {
|
|
2474
|
+
setScrollOffset((prev) => Math.min(prev + 3, Math.max(0, totalLinesRef.current - 1)));
|
|
2475
|
+
} else {
|
|
2476
|
+
setScrollOffset((prev) => Math.max(0, prev - 3));
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
const remainder = s.slice(mouseLen);
|
|
2480
|
+
if (remainder.length > 0) {
|
|
2481
|
+
stdin.unshift(
|
|
2482
|
+
typeof chunk === "string" ? remainder : Buffer.from(remainder, "binary")
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
} else {
|
|
2486
|
+
stdin.unshift(chunk);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
return origEmit.apply(stdin, [event, ...args]);
|
|
2491
|
+
};
|
|
2492
|
+
return () => {
|
|
2493
|
+
stdout.write("\x1B[?1000l\x1B[?1006l");
|
|
2494
|
+
stdin.emit = origEmit;
|
|
2495
|
+
};
|
|
2496
|
+
}, [stdin, stdout]);
|
|
2497
|
+
useEffect3(() => {
|
|
2498
|
+
const atFragment = extractAtQuery(fileAcState.query);
|
|
2499
|
+
if (atFragment === null) {
|
|
2500
|
+
setFileSuggestions([]);
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
let cancelled = false;
|
|
2504
|
+
listFilesForQuery(atFragment, process.cwd()).then((entries) => {
|
|
2505
|
+
if (!cancelled) setFileSuggestions(entries);
|
|
2506
|
+
}).catch(() => {
|
|
2507
|
+
});
|
|
2508
|
+
return () => {
|
|
2509
|
+
cancelled = true;
|
|
2510
|
+
};
|
|
2511
|
+
}, [fileAcState.query]);
|
|
2512
|
+
useInput2((input, key) => {
|
|
2513
|
+
var _a, _b, _c, _d, _e;
|
|
2514
|
+
if (key.escape && status === "thinking" && abortControllerRef.current) {
|
|
2515
|
+
abortControllerRef.current.abort();
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
const fileOpen = isOpen2(fileAcState, fileSuggestions);
|
|
2519
|
+
if (fileOpen) {
|
|
2520
|
+
if (key.upArrow) {
|
|
2521
|
+
setFileAcState((prev) => moveUp2(prev, fileSuggestions.length));
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
if (key.downArrow) {
|
|
2525
|
+
setFileAcState((prev) => moveDown2(prev, fileSuggestions.length));
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (key.escape) {
|
|
2529
|
+
setFileAcState((prev) => dismiss2(prev));
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
if (key.tab) {
|
|
2533
|
+
const selected = fileSuggestions[fileAcState.selectedIndex];
|
|
2534
|
+
if (selected) {
|
|
2535
|
+
const newText = confirmSelection(fileAcState.query, selected.path);
|
|
2536
|
+
(_a = inputRef.current) == null ? void 0 : _a.fill(newText);
|
|
2537
|
+
}
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
const skillList = (registry == null ? void 0 : registry.list()) ?? [];
|
|
2542
|
+
const suggestions = computeSuggestions(skillList, acState);
|
|
2543
|
+
const open = isOpen(acState, suggestions);
|
|
2544
|
+
if (open) {
|
|
2545
|
+
if (key.upArrow) {
|
|
2546
|
+
setAcState((prev) => moveUp(prev, suggestions.length));
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
if (key.downArrow) {
|
|
2550
|
+
setAcState((prev) => moveDown(prev, suggestions.length));
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (key.escape) {
|
|
2554
|
+
setAcState((prev) => dismiss(prev));
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (key.tab) {
|
|
2558
|
+
const selected = suggestions[acState.selectedIndex];
|
|
2559
|
+
if (selected) {
|
|
2560
|
+
(_b = inputRef.current) == null ? void 0 : _b.fill(`/${selected.name} `);
|
|
2561
|
+
}
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
if (key.tab) {
|
|
2566
|
+
setExpandTools((prev) => !prev);
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
const scrollStep = Math.max(1, Math.floor(historyMaxHeight / 2));
|
|
2570
|
+
if (key.pageUp) {
|
|
2571
|
+
setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (key.pageDown) {
|
|
2575
|
+
setScrollOffset((prev) => Math.max(0, prev - scrollStep));
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
if (key.ctrl && input === "v") {
|
|
2579
|
+
setScrollOffset((prev) => Math.max(0, prev - scrollStep));
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
if (key.meta && input === "v") {
|
|
2583
|
+
setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
if (key.ctrl && input === "p") {
|
|
2587
|
+
const history = inputHistoryRef.current;
|
|
2588
|
+
const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
|
|
2589
|
+
if (newIndex >= 0 && newIndex < history.length) {
|
|
2590
|
+
historyIndexRef.current = newIndex;
|
|
2591
|
+
isNavigatingHistoryRef.current = true;
|
|
2592
|
+
(_c = inputRef.current) == null ? void 0 : _c.fill(history[newIndex]);
|
|
2593
|
+
}
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
if (key.ctrl && input === "n") {
|
|
2597
|
+
const history = inputHistoryRef.current;
|
|
2598
|
+
if (historyIndexRef.current > 0) {
|
|
2599
|
+
const newIndex = historyIndexRef.current - 1;
|
|
2600
|
+
historyIndexRef.current = newIndex;
|
|
2601
|
+
isNavigatingHistoryRef.current = true;
|
|
2602
|
+
(_d = inputRef.current) == null ? void 0 : _d.fill(history[newIndex]);
|
|
2603
|
+
} else if (historyIndexRef.current === 0) {
|
|
2604
|
+
historyIndexRef.current = -1;
|
|
2605
|
+
isNavigatingHistoryRef.current = true;
|
|
2606
|
+
(_e = inputRef.current) == null ? void 0 : _e.fill("");
|
|
2607
|
+
}
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
const confirm = useCallback((prompt) => {
|
|
2612
|
+
return new Promise((resolve2) => {
|
|
2613
|
+
setStatus("awaiting_confirm");
|
|
2614
|
+
setConfirmPrompt(prompt);
|
|
2615
|
+
pendingConfirmRef.current = { resolve: resolve2 };
|
|
2616
|
+
});
|
|
2617
|
+
}, []);
|
|
2618
|
+
const runLlmLoop = useCallback(
|
|
2619
|
+
async (history) => {
|
|
2620
|
+
const print = (_text) => {
|
|
2621
|
+
};
|
|
2622
|
+
const deps = {
|
|
2623
|
+
executeBash,
|
|
2624
|
+
confirm,
|
|
2625
|
+
print,
|
|
2626
|
+
dangerousPatterns: config.dangerousPatterns,
|
|
2627
|
+
// autoMode=true 时自动同意 normal 级工具调用,无需用户确认
|
|
2628
|
+
autoApproveNormal: autoMode
|
|
2629
|
+
};
|
|
2630
|
+
const abortController = new AbortController();
|
|
2631
|
+
abortControllerRef.current = abortController;
|
|
2632
|
+
let currentMessages = history;
|
|
2633
|
+
let continueLoop = true;
|
|
2634
|
+
let wasAborted = false;
|
|
2635
|
+
while (continueLoop && !wasAborted) {
|
|
2636
|
+
continueLoop = false;
|
|
2637
|
+
setStatus("thinking");
|
|
2638
|
+
let assistantText = "";
|
|
2639
|
+
let assistantReasoning;
|
|
2640
|
+
const toolCalls = [];
|
|
2641
|
+
const dynamicTools = skillToolsRef.current.map((t) => ({
|
|
2642
|
+
type: "function",
|
|
2643
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
2644
|
+
}));
|
|
2645
|
+
try {
|
|
2646
|
+
for await (const chunk of llmRef.current.stream(currentMessages, [BASH_TOOL, READ_TOOL, WRITE_TOOL, EDIT_TOOL, GLOB_TOOL, GREP_TOOL, APPLY_PATCH_TOOL, TODO_TOOL, TASK_TOOL, WEB_FETCH_TOOL, ...dynamicTools], abortController.signal)) {
|
|
2647
|
+
if (chunk.text) {
|
|
2648
|
+
assistantText += chunk.text;
|
|
2649
|
+
setMessages((prev) => {
|
|
2650
|
+
const last = prev[prev.length - 1];
|
|
2651
|
+
if ((last == null ? void 0 : last.role) === "assistant" && !last.tool_calls) {
|
|
2652
|
+
return [...prev.slice(0, -1), { ...last, content: assistantText }];
|
|
2653
|
+
}
|
|
2654
|
+
return [...prev, { role: "assistant", content: assistantText }];
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
if (chunk.done) {
|
|
2658
|
+
if (chunk.toolCalls) toolCalls.push(...chunk.toolCalls);
|
|
2659
|
+
if (chunk.reasoning) assistantReasoning = chunk.reasoning;
|
|
2660
|
+
if (chunk.usage) {
|
|
2661
|
+
setTokenUsage({
|
|
2662
|
+
used: chunk.usage.totalTokens,
|
|
2663
|
+
estimated: false,
|
|
2664
|
+
limit: getContextLimit(config.model, config.contextLimit)
|
|
2665
|
+
});
|
|
2666
|
+
finalTokensRef.current = chunk.usage.totalTokens;
|
|
2667
|
+
}
|
|
2668
|
+
} else {
|
|
2669
|
+
const estimatedUsed = Math.floor(
|
|
2670
|
+
currentMessages.reduce(
|
|
2671
|
+
(acc, m) => acc + (typeof m.content === "string" ? m.content.length : 0),
|
|
2672
|
+
0
|
|
2673
|
+
) * 0.25 + assistantText.length * 0.25
|
|
2674
|
+
);
|
|
2675
|
+
setTokenUsage((prev) => ({ ...prev, used: estimatedUsed, estimated: true }));
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
} catch (err) {
|
|
2679
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2680
|
+
if (assistantText) {
|
|
2681
|
+
setMessages((prev) => {
|
|
2682
|
+
const last = prev[prev.length - 1];
|
|
2683
|
+
const partial = { role: "assistant", content: assistantText };
|
|
2684
|
+
return (last == null ? void 0 : last.role) === "assistant" && !last.tool_calls ? [...prev.slice(0, -1), partial] : [...prev, partial];
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
wasAborted = true;
|
|
2688
|
+
setStatus("idle");
|
|
2689
|
+
setToolName(void 0);
|
|
2690
|
+
abortControllerRef.current = null;
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2693
|
+
throw err;
|
|
2694
|
+
}
|
|
2695
|
+
if (toolCalls.length > 0) {
|
|
2696
|
+
const assistantMsg = {
|
|
2697
|
+
role: "assistant",
|
|
2698
|
+
// content 可能为空字符串(纯工具调用),存 null 符合 OpenAI API 规范
|
|
2699
|
+
content: assistantText || null,
|
|
2700
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
2701
|
+
id: tc.id,
|
|
2702
|
+
type: "function",
|
|
2703
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
2704
|
+
})),
|
|
2705
|
+
// 仅在存在思考内容时扩展 reasoning_content 字段
|
|
2706
|
+
...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
|
|
2707
|
+
};
|
|
2708
|
+
setMessages((prev) => {
|
|
2709
|
+
const last = prev[prev.length - 1];
|
|
2710
|
+
const withoutStreaming = (last == null ? void 0 : last.role) === "assistant" && !last.tool_calls ? prev.slice(0, -1) : prev;
|
|
2711
|
+
return [...withoutStreaming, assistantMsg];
|
|
2712
|
+
});
|
|
2713
|
+
currentMessages = [...currentMessages, assistantMsg];
|
|
2714
|
+
setStatus("tool_calling");
|
|
2715
|
+
for (const tc of toolCalls) {
|
|
2716
|
+
setToolName(tc.name);
|
|
2717
|
+
let toolResult;
|
|
2718
|
+
if (tc.name === "bash") {
|
|
2719
|
+
let args;
|
|
2720
|
+
try {
|
|
2721
|
+
args = JSON.parse(tc.arguments);
|
|
2722
|
+
} catch {
|
|
2723
|
+
args = { command: "" };
|
|
2724
|
+
}
|
|
2725
|
+
toolResult = await handleBashTool(args.command, deps);
|
|
2726
|
+
} else if (tc.name === "read") {
|
|
2727
|
+
const args = JSON.parse(tc.arguments);
|
|
2728
|
+
toolResult = await readFile(args);
|
|
2729
|
+
} else if (tc.name === "write") {
|
|
2730
|
+
const args = JSON.parse(tc.arguments);
|
|
2731
|
+
toolResult = await writeFile(args);
|
|
2732
|
+
} else if (tc.name === "edit") {
|
|
2733
|
+
const args = JSON.parse(tc.arguments);
|
|
2734
|
+
toolResult = await editFile(args);
|
|
2735
|
+
} else if (tc.name === "glob") {
|
|
2736
|
+
const args = JSON.parse(tc.arguments);
|
|
2737
|
+
toolResult = await globFiles(args);
|
|
2738
|
+
} else if (tc.name === "grep") {
|
|
2739
|
+
const args = JSON.parse(tc.arguments);
|
|
2740
|
+
toolResult = await grepFiles(args);
|
|
2741
|
+
} else if (tc.name === "apply_patch") {
|
|
2742
|
+
const args = JSON.parse(tc.arguments);
|
|
2743
|
+
toolResult = await applyPatch(args);
|
|
2744
|
+
} else if (tc.name === "todo") {
|
|
2745
|
+
const args = JSON.parse(tc.arguments);
|
|
2746
|
+
toolResult = todo(args);
|
|
2747
|
+
} else if (tc.name === "task") {
|
|
2748
|
+
const args = JSON.parse(tc.arguments);
|
|
2749
|
+
toolResult = await handleTaskTool(args, {
|
|
2750
|
+
provider: llmRef.current,
|
|
2751
|
+
print: (text) => process.stdout.write(text),
|
|
2752
|
+
logDir: config.logDir
|
|
2753
|
+
});
|
|
2754
|
+
} else if (tc.name === "web_fetch") {
|
|
2755
|
+
const args = JSON.parse(tc.arguments);
|
|
2756
|
+
toolResult = await webFetch(args);
|
|
2757
|
+
} else {
|
|
2758
|
+
const skillTool = skillToolsRef.current.find((t) => t.name === tc.name);
|
|
2759
|
+
if (skillTool) {
|
|
2760
|
+
let toolArgs = {};
|
|
2761
|
+
try {
|
|
2762
|
+
toolArgs = JSON.parse(tc.arguments);
|
|
2763
|
+
} catch {
|
|
2764
|
+
}
|
|
2765
|
+
toolResult = await executeSkillTool(skillTool, toolArgs, trustedSkillDirs);
|
|
2766
|
+
} else {
|
|
2767
|
+
toolResult = `Unknown tool: ${tc.name}`;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
const toolMsg = {
|
|
2771
|
+
role: "tool",
|
|
2772
|
+
tool_call_id: tc.id,
|
|
2773
|
+
content: toolResult
|
|
2774
|
+
};
|
|
2775
|
+
setMessages((prev) => [...prev, toolMsg]);
|
|
2776
|
+
currentMessages = [...currentMessages, toolMsg];
|
|
2777
|
+
}
|
|
2778
|
+
continueLoop = true;
|
|
2779
|
+
} else {
|
|
2780
|
+
setMessages((prev) => {
|
|
2781
|
+
const last = prev[prev.length - 1];
|
|
2782
|
+
if ((last == null ? void 0 : last.role) === "assistant" && !last.tool_calls) {
|
|
2783
|
+
const updated = {
|
|
2784
|
+
role: "assistant",
|
|
2785
|
+
content: assistantText,
|
|
2786
|
+
...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
|
|
2787
|
+
};
|
|
2788
|
+
return [...prev.slice(0, -1), updated];
|
|
2789
|
+
}
|
|
2790
|
+
return prev;
|
|
2791
|
+
});
|
|
2792
|
+
if (assistantText) {
|
|
2793
|
+
currentMessages = [
|
|
2794
|
+
...currentMessages,
|
|
2795
|
+
{
|
|
2796
|
+
role: "assistant",
|
|
2797
|
+
content: assistantText,
|
|
2798
|
+
...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
|
|
2799
|
+
}
|
|
2800
|
+
];
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (loggerRef.current && sessionMetaRef.current) {
|
|
2805
|
+
const prev = sessionMetaRef.current;
|
|
2806
|
+
const updated = {
|
|
2807
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2808
|
+
totalTokens: finalTokensRef.current,
|
|
2809
|
+
turnCount: prev.turnCount + 1
|
|
2810
|
+
};
|
|
2811
|
+
updateSessionMetadata(loggerRef.current.filePath, updated);
|
|
2812
|
+
sessionMetaRef.current = { ...prev, ...updated };
|
|
2813
|
+
}
|
|
2814
|
+
setStatus("idle");
|
|
2815
|
+
setToolName(void 0);
|
|
2816
|
+
abortControllerRef.current = null;
|
|
2817
|
+
},
|
|
2818
|
+
[confirm, config.dangerousPatterns, autoMode]
|
|
2819
|
+
);
|
|
2820
|
+
const handleSubmit = useCallback(
|
|
2821
|
+
async (text) => {
|
|
2822
|
+
var _a, _b;
|
|
2823
|
+
const trimmed = text.trim();
|
|
2824
|
+
if (status === "awaiting_confirm") {
|
|
2825
|
+
const pending = pendingConfirmRef.current;
|
|
2826
|
+
if (pending) {
|
|
2827
|
+
pendingConfirmRef.current = null;
|
|
2828
|
+
setConfirmPrompt(void 0);
|
|
2829
|
+
setStatus("tool_calling");
|
|
2830
|
+
pending.resolve(trimmed.toLowerCase() === "y");
|
|
2831
|
+
}
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (!trimmed) return;
|
|
2835
|
+
setInputHistory((prev) => [trimmed, ...prev.slice(0, 99)]);
|
|
2836
|
+
historyIndexRef.current = -1;
|
|
2837
|
+
setScrollOffset(0);
|
|
2838
|
+
{
|
|
2839
|
+
const automationResult = await handleAutomationCommand(
|
|
2840
|
+
trimmed,
|
|
2841
|
+
automationManagerRef.current,
|
|
2842
|
+
// 注入会话快照:/goal blocked 时用于生成 ecode --fork 回滚命令
|
|
2843
|
+
{
|
|
2844
|
+
logFile: (_a = loggerRef.current) == null ? void 0 : _a.filePath,
|
|
2845
|
+
turnCount: (_b = sessionMetaRef.current) == null ? void 0 : _b.turnCount
|
|
2846
|
+
}
|
|
2847
|
+
);
|
|
2848
|
+
if (automationResult !== null) {
|
|
2849
|
+
setMessages((prev) => [
|
|
2850
|
+
...prev,
|
|
2851
|
+
{ role: "user", content: trimmed },
|
|
2852
|
+
{ role: "assistant", content: automationResult }
|
|
2853
|
+
]);
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
{
|
|
2858
|
+
const metaAlignResult = handleMetaAlignCommand(trimmed);
|
|
2859
|
+
if (metaAlignResult !== null) {
|
|
2860
|
+
setMessages((prev) => [
|
|
2861
|
+
...prev,
|
|
2862
|
+
{ role: "user", content: trimmed },
|
|
2863
|
+
{ role: "assistant", content: metaAlignResult }
|
|
2864
|
+
]);
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
if (registry) {
|
|
2869
|
+
const skillResult = handleSkillInput(trimmed, registry);
|
|
2870
|
+
if (skillResult.type === "error") {
|
|
2871
|
+
setMessages((prev) => [
|
|
2872
|
+
...prev,
|
|
2873
|
+
{ role: "assistant", content: skillResult.message }
|
|
2874
|
+
]);
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
if (skillResult.type === "skill") {
|
|
2878
|
+
const { skill } = skillResult;
|
|
2879
|
+
setSkillTools(skill.tools);
|
|
2880
|
+
if (skill.preScript) {
|
|
2881
|
+
try {
|
|
2882
|
+
await executePreScript(skill, trustedSkillDirs);
|
|
2883
|
+
} catch (preErr) {
|
|
2884
|
+
setMessages((prev) => [
|
|
2885
|
+
...prev,
|
|
2886
|
+
{ role: "assistant", content: `[pre.sh error] ${String(preErr)}` }
|
|
2887
|
+
]);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
const nextMessages2 = [...messages, skillResult.message];
|
|
2891
|
+
setMessages(nextMessages2);
|
|
2892
|
+
runLlmLoop(nextMessages2).catch((err) => {
|
|
2893
|
+
setStatus("idle");
|
|
2894
|
+
setToolName(void 0);
|
|
2895
|
+
setMessages((prev) => [
|
|
2896
|
+
...prev,
|
|
2897
|
+
{ role: "assistant", content: `[error] ${String(err)}` }
|
|
2898
|
+
]);
|
|
2899
|
+
});
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
let content = trimmed;
|
|
2904
|
+
try {
|
|
2905
|
+
content = await expandFileRefs(trimmed, process.cwd());
|
|
2906
|
+
} catch {
|
|
2907
|
+
}
|
|
2908
|
+
const userMsg = { role: "user", content };
|
|
2909
|
+
const nextMessages = [...messages, userMsg];
|
|
2910
|
+
setMessages(nextMessages);
|
|
2911
|
+
if (loggerRef.current && sessionMetaRef.current && !sessionMetaRef.current.title) {
|
|
2912
|
+
const title = generateTitle(content);
|
|
2913
|
+
updateSessionMetadata(loggerRef.current.filePath, { title });
|
|
2914
|
+
sessionMetaRef.current = { ...sessionMetaRef.current, title };
|
|
2915
|
+
}
|
|
2916
|
+
runLlmLoop(nextMessages).catch((err) => {
|
|
2917
|
+
setStatus("idle");
|
|
2918
|
+
setToolName(void 0);
|
|
2919
|
+
setMessages((prev) => [
|
|
2920
|
+
...prev,
|
|
2921
|
+
{ role: "assistant", content: `[error] ${String(err)}` }
|
|
2922
|
+
]);
|
|
2923
|
+
});
|
|
2924
|
+
},
|
|
2925
|
+
[status, messages, runLlmLoop]
|
|
2926
|
+
);
|
|
2927
|
+
const isInputActive = status === "idle" || status === "awaiting_confirm";
|
|
2928
|
+
const handleInputTextChange = useCallback((text) => {
|
|
2929
|
+
if (isNavigatingHistoryRef.current) {
|
|
2930
|
+
isNavigatingHistoryRef.current = false;
|
|
2931
|
+
} else {
|
|
2932
|
+
historyIndexRef.current = -1;
|
|
2933
|
+
}
|
|
2934
|
+
if (status !== "awaiting_confirm") {
|
|
2935
|
+
setAcState((prev) => handleInputChange(prev, text));
|
|
2936
|
+
setFileAcState((prev) => handleInputChange2(prev, text));
|
|
2937
|
+
}
|
|
2938
|
+
}, [status]);
|
|
2939
|
+
const skillSuggestions = registry ? computeSuggestions(registry.list(), acState) : [];
|
|
2940
|
+
const acOpen = isOpen(acState, skillSuggestions);
|
|
2941
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: "100%", children: [
|
|
2942
|
+
/* @__PURE__ */ jsx6(Box6, { flexGrow: 1, flexDirection: "column", justifyContent: "flex-end", children: /* @__PURE__ */ jsx6(
|
|
2943
|
+
ConversationHistory,
|
|
2944
|
+
{
|
|
2945
|
+
messages,
|
|
2946
|
+
expandTools,
|
|
2947
|
+
maxHeight: historyMaxHeight,
|
|
2948
|
+
terminalWidth: stdout == null ? void 0 : stdout.columns,
|
|
2949
|
+
scrollOffset
|
|
2950
|
+
}
|
|
2951
|
+
) }),
|
|
2952
|
+
/* @__PURE__ */ jsx6(
|
|
2953
|
+
StatusBar,
|
|
2954
|
+
{
|
|
2955
|
+
status,
|
|
2956
|
+
toolName,
|
|
2957
|
+
confirmPrompt,
|
|
2958
|
+
version,
|
|
2959
|
+
tokenUsage
|
|
2960
|
+
}
|
|
2961
|
+
),
|
|
2962
|
+
/* @__PURE__ */ jsx6(
|
|
2963
|
+
SkillAutocomplete,
|
|
2964
|
+
{
|
|
2965
|
+
suggestions: skillSuggestions,
|
|
2966
|
+
selectedIndex: acState.selectedIndex,
|
|
2967
|
+
isOpen: acOpen
|
|
2968
|
+
}
|
|
2969
|
+
),
|
|
2970
|
+
/* @__PURE__ */ jsx6(
|
|
2971
|
+
FileAutocomplete,
|
|
2972
|
+
{
|
|
2973
|
+
suggestions: fileSuggestions,
|
|
2974
|
+
selectedIndex: fileAcState.selectedIndex,
|
|
2975
|
+
isOpen: isOpen2(fileAcState, fileSuggestions)
|
|
2976
|
+
}
|
|
2977
|
+
),
|
|
2978
|
+
/* @__PURE__ */ jsx6(
|
|
2979
|
+
Input_default,
|
|
2980
|
+
{
|
|
2981
|
+
ref: inputRef,
|
|
2982
|
+
isActive: isInputActive,
|
|
2983
|
+
onSubmit: handleSubmit,
|
|
2984
|
+
onChange: handleInputTextChange,
|
|
2985
|
+
placeholder: status === "awaiting_confirm" ? "y / n" : void 0
|
|
2986
|
+
}
|
|
2987
|
+
)
|
|
2988
|
+
] });
|
|
2989
|
+
}
|
|
2990
|
+
export {
|
|
2991
|
+
App,
|
|
2992
|
+
default2 as React,
|
|
2993
|
+
render
|
|
2994
|
+
};
|