u1s1-cli 1.2.2 → 1.2.4
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/agent-setup.d.ts +17 -2
- package/dist/agent-setup.js +81 -10
- package/dist/api.d.ts +8 -2
- package/dist/api.js +6 -6
- package/dist/bench-report.d.ts +22 -0
- package/dist/bench-report.js +158 -0
- package/dist/bench-scoring.d.ts +8 -0
- package/dist/bench-scoring.js +101 -0
- package/dist/bench-types.d.ts +50 -0
- package/dist/bench-types.js +1 -0
- package/dist/bench.js +101 -367
- package/dist/brand.d.ts +9 -3
- package/dist/brand.js +12 -12
- package/dist/config.d.ts +28 -6
- package/dist/config.js +37 -2
- package/dist/deploy.js +72 -49
- package/dist/import/claude.d.ts +2 -1
- package/dist/import/claude.js +162 -129
- package/dist/import/codex.d.ts +2 -1
- package/dist/import/codex.js +184 -157
- package/dist/import/index.js +62 -47
- package/dist/index.js +16 -13
- package/dist/login.js +2 -2
- package/dist/style.js +7 -1
- package/dist/subagent.d.ts +6 -1
- package/dist/subagent.js +4 -4
- package/dist/tools.d.ts +18 -12
- package/dist/tools.js +22 -16
- package/dist/usage.js +95 -77
- package/dist/web.js +2 -1
- package/dist/workflow/runner.js +36 -26
- package/dist/workflow/tool.js +67 -55
- package/package.json +1 -1
- package/scripts/render-regression.mjs +3 -1
package/dist/tools.d.ts
CHANGED
|
@@ -4,18 +4,24 @@ import { Type } from "typebox";
|
|
|
4
4
|
import type { CliConfig } from "./config.js";
|
|
5
5
|
export declare function truncate(text: string): string;
|
|
6
6
|
/** 联网工具通用渲染:调用行不占位,收起时只显一行摘要,ctrl+o 展开全文,出错显一行 ✗ */
|
|
7
|
-
export declare function compactResultRender(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
export declare function compactResultRender(input: {
|
|
8
|
+
result: {
|
|
9
|
+
content: Array<{
|
|
10
|
+
type: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
options: {
|
|
15
|
+
expanded: boolean;
|
|
16
|
+
};
|
|
17
|
+
theme: {
|
|
18
|
+
fg: (color: any, text: string) => string;
|
|
19
|
+
};
|
|
20
|
+
context: {
|
|
21
|
+
isError: boolean;
|
|
22
|
+
};
|
|
23
|
+
summaryLine: string;
|
|
24
|
+
}): Text;
|
|
19
25
|
/** 联网搜索工具:走 u1s1 网关代理,上游 key 不落到用户机器上。 */
|
|
20
26
|
export declare function createSearchTool(cfg: Pick<CliConfig, "baseUrl" | "apiKey">): import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{
|
|
21
27
|
query: Type.TString;
|
package/dist/tools.js
CHANGED
|
@@ -88,10 +88,8 @@ function resultText(result) {
|
|
|
88
88
|
return c && c.type === "text" ? (c.text ?? "") : "";
|
|
89
89
|
}
|
|
90
90
|
/** 联网工具通用渲染:调用行不占位,收起时只显一行摘要,ctrl+o 展开全文,出错显一行 ✗ */
|
|
91
|
-
export function compactResultRender(
|
|
92
|
-
|
|
93
|
-
// 实际传入的就是 pi 的 Theme,取值只用 "warning"/"muted" 两色
|
|
94
|
-
theme, context, summaryLine) {
|
|
91
|
+
export function compactResultRender(input) {
|
|
92
|
+
const { result, options, theme, context, summaryLine } = input;
|
|
95
93
|
if (context.isError) {
|
|
96
94
|
// 低调处理:✗ 用警示色,内容用灰色弱化(与内置工具的错误展示一致)
|
|
97
95
|
const msg = previewLine(resultText(result) || "error", 160);
|
|
@@ -123,14 +121,15 @@ export function createSearchTool(cfg) {
|
|
|
123
121
|
renderCall() {
|
|
124
122
|
return new Text("", 0, 0);
|
|
125
123
|
},
|
|
126
|
-
renderResult(
|
|
124
|
+
renderResult(...args) {
|
|
125
|
+
const [result, options, theme, context] = args;
|
|
127
126
|
const d = result.details;
|
|
128
127
|
const q = d?.query ? `"${previewLine(d.query, 80)}"` : "…";
|
|
129
128
|
const n = typeof d?.count === "number" ? ` · ${d.count} 条结果` : "";
|
|
130
|
-
return compactResultRender(result, options, theme, context, `🔍 搜索 ${q}${n}`);
|
|
129
|
+
return compactResultRender({ result, options, theme, context, summaryLine: `🔍 搜索 ${q}${n}` });
|
|
131
130
|
},
|
|
132
131
|
async execute(_toolCallId, params, signal) {
|
|
133
|
-
const data = await searchWeb(cfg, params.query, params.maxResults, signal);
|
|
132
|
+
const data = await searchWeb(cfg, { query: params.query, maxResults: params.maxResults, signal });
|
|
134
133
|
const lines = [];
|
|
135
134
|
if (data.answer)
|
|
136
135
|
lines.push(`Answer: ${data.answer}`, "");
|
|
@@ -187,14 +186,15 @@ export function createSubagentTool(getParentModel) {
|
|
|
187
186
|
renderCall() {
|
|
188
187
|
return new Text("", 0, 0);
|
|
189
188
|
},
|
|
190
|
-
renderResult(
|
|
189
|
+
renderResult(...args) {
|
|
190
|
+
const [result, options, theme, context] = args;
|
|
191
191
|
const d = result.details;
|
|
192
192
|
const summary = d && typeof d.count === "number"
|
|
193
193
|
? `🤖 子任务 ×${d.count}` +
|
|
194
194
|
(typeof d.ok === "number" && d.failed ? ` · ✓${d.ok} ✗${d.failed}` : "") +
|
|
195
195
|
(typeof d.seconds === "number" ? ` · ${d.seconds}s` : "")
|
|
196
196
|
: "🤖 子任务";
|
|
197
|
-
return compactResultRender(result, options, theme, context, summary);
|
|
197
|
+
return compactResultRender({ result, options, theme, context, summaryLine: summary });
|
|
198
198
|
},
|
|
199
199
|
async execute(_toolCallId, params, signal) {
|
|
200
200
|
const list = (params.tasks?.length ? params.tasks : params.task ? [params.task] : [])
|
|
@@ -210,11 +210,16 @@ export function createSubagentTool(getParentModel) {
|
|
|
210
210
|
const prevFlag = process.env.U1S1_IN_SUBAGENT;
|
|
211
211
|
process.env.U1S1_IN_SUBAGENT = "1";
|
|
212
212
|
const started = Date.now();
|
|
213
|
-
const outcomes =
|
|
213
|
+
const outcomes = Array.from({ length: list.length });
|
|
214
214
|
try {
|
|
215
|
-
await runPool(
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
await runPool({
|
|
216
|
+
items: list,
|
|
217
|
+
limit: SUBAGENT_CONCURRENCY,
|
|
218
|
+
run: async (task, i) => {
|
|
219
|
+
outcomes[i] = await runSubagent({ task, parentModel, model: params.model, timeoutMs, signal }).catch((e) => ({ ok: false, text: e.message, usage: { totalTokens: 0, costUsd: 0 } }));
|
|
220
|
+
},
|
|
221
|
+
signal,
|
|
222
|
+
});
|
|
218
223
|
}
|
|
219
224
|
finally {
|
|
220
225
|
if (prevFlag === undefined)
|
|
@@ -458,12 +463,13 @@ export function createFetchTool(cfg) {
|
|
|
458
463
|
renderCall() {
|
|
459
464
|
return new Text("", 0, 0);
|
|
460
465
|
},
|
|
461
|
-
renderResult(
|
|
466
|
+
renderResult(...args) {
|
|
467
|
+
const [result, options, theme, context] = args;
|
|
462
468
|
const d = result.details;
|
|
463
469
|
const u = previewLine(d?.url ?? "…", 90);
|
|
464
470
|
const n = typeof d?.chars === "number" ? ` · ${d.chars} 字符` : "";
|
|
465
471
|
const cloud = d?.via === "render" ? " · ☁ 浏览器渲染" : "";
|
|
466
|
-
return compactResultRender(result, options, theme, context, `🌐 ${u}${n}${cloud}`);
|
|
472
|
+
return compactResultRender({ result, options, theme, context, summaryLine: `🌐 ${u}${n}${cloud}` });
|
|
467
473
|
},
|
|
468
474
|
async execute(_toolCallId, params, signal) {
|
|
469
475
|
let url;
|
|
@@ -492,7 +498,7 @@ export function createFetchTool(cfg) {
|
|
|
492
498
|
return ok(await renderViaGateway(url, signal), "render", "text/markdown");
|
|
493
499
|
}
|
|
494
500
|
catch (re) {
|
|
495
|
-
throw new Error(`${e.message};云端浏览器渲染也失败: ${re.message}
|
|
501
|
+
throw new Error(`${e.message};云端浏览器渲染也失败: ${re.message}`, { cause: re });
|
|
496
502
|
}
|
|
497
503
|
}
|
|
498
504
|
// 直连 200 但正文近乎空:大概率是 JS 渲染的 SPA,换浏览器再试;渲染失败就退回空壳
|
package/dist/usage.js
CHANGED
|
@@ -20,92 +20,91 @@ function fmtTokensCn(tokens) {
|
|
|
20
20
|
return sig2(t / 1e4).toLocaleString("en-US") + " 万";
|
|
21
21
|
return sig2(t).toLocaleString("en-US");
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const PACKAGE_LABELS = {
|
|
24
|
+
free_first: "首月免费包",
|
|
25
|
+
free_yearly: "年度免费包",
|
|
26
|
+
invite: "邀请赠送",
|
|
27
|
+
new_user: "新用户赠送",
|
|
28
|
+
login_checkin: "登录打卡",
|
|
29
|
+
payment_delay_gift: "临时加量包",
|
|
30
|
+
topup_daily: "每日加量包",
|
|
31
|
+
admin_grant: "官方赠送",
|
|
32
|
+
};
|
|
33
|
+
function sumOptional(a, b) {
|
|
34
|
+
return a == null && b == null ? null : (a ?? 0) + (b ?? 0);
|
|
35
|
+
}
|
|
36
|
+
function groupPackages(packages) {
|
|
37
|
+
const groups = new Map();
|
|
38
|
+
for (const pkg of packages) {
|
|
39
|
+
const key = `${pkg.kind}|${pkg.scope}|${pkg.daily_tokens != null}|${pkg.expires_at || ""}`;
|
|
40
|
+
const group = groups.get(key);
|
|
41
|
+
if (!group) {
|
|
42
|
+
groups.set(key, { ...pkg, count: 1 });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
group.count += 1;
|
|
46
|
+
group.daily_tokens = sumOptional(group.daily_tokens, pkg.daily_tokens);
|
|
47
|
+
group.total_tokens = sumOptional(group.total_tokens, pkg.total_tokens);
|
|
48
|
+
group.remaining += pkg.remaining;
|
|
28
49
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
return [...groups.values()];
|
|
51
|
+
}
|
|
52
|
+
function packageScopeNote(pkg) {
|
|
53
|
+
if (pkg.kind === "login_checkin") {
|
|
54
|
+
return "仅限 u1s1 客户端使用 · 全模型可用";
|
|
55
|
+
}
|
|
56
|
+
return pkg.scope === "free"
|
|
57
|
+
? "免费包适用模型 · 0 点恢复"
|
|
58
|
+
: "仅限 u1s1 客户端使用 · 免费包适用模型";
|
|
59
|
+
}
|
|
60
|
+
function packageLabel(pkg) {
|
|
61
|
+
const count = pkg.count > 1 ? ` ×${pkg.count}` : "";
|
|
62
|
+
return `${PACKAGE_LABELS[pkg.kind] ?? pkg.kind}${count}`.padEnd(5, " ");
|
|
63
|
+
}
|
|
64
|
+
function printPackage(pkg) {
|
|
65
|
+
const isDaily = pkg.daily_tokens != null;
|
|
66
|
+
const total = isDaily ? (pkg.daily_tokens ?? 0) : (pkg.total_tokens ?? 0);
|
|
67
|
+
const ratio = total > 0 ? pkg.remaining / total : 0;
|
|
68
|
+
console.log(` ${packageLabel(pkg)} 还剩 ${fmtTokensCn(pkg.remaining)} / ${fmtTokensCn(total)}${isDaily ? "/天" : ""} ${bar(ratio)}`);
|
|
69
|
+
const expiry = pkg.expires_at ? `${pkg.expires_at.slice(0, 10)} 到期` : "永不过期";
|
|
70
|
+
console.log(` ${packageScopeNote(pkg)} · ${expiry}`);
|
|
71
|
+
}
|
|
72
|
+
function printPackageUsage(me, tokensPerUsd) {
|
|
73
|
+
const packages = me.packages ?? [];
|
|
74
|
+
if (packages.length === 0)
|
|
75
|
+
console.log(" 用量包 (无生效中的用量包)");
|
|
76
|
+
for (const pkg of groupPackages(packages))
|
|
77
|
+
printPackage(pkg);
|
|
78
|
+
if (me.bonus_balance_usd > 0) {
|
|
79
|
+
const balance = tokensPerUsd > 0
|
|
80
|
+
? `${fmtTokensCn(me.bonus_balance_usd * tokensPerUsd)} Token`
|
|
81
|
+
: `$${me.bonus_balance_usd.toFixed(2)}`;
|
|
82
|
+
console.log(` 余额(按量) ${balance}`);
|
|
83
|
+
}
|
|
84
|
+
const monthlyUsage = tokensPerUsd > 0
|
|
85
|
+
? `约 ${fmtTokensCn(me.mtd_usd * tokensPerUsd)} Token($${me.mtd_usd.toFixed(2)})`
|
|
86
|
+
: `$${me.mtd_usd.toFixed(2)}`;
|
|
87
|
+
console.log(` 本月已用 ${monthlyUsage}`);
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log(" 免费用量包覆盖 DeepSeek V4 Flash / Vision、GLM-5.3 Flash、Qwen3.8 Flash 和联网搜索。");
|
|
90
|
+
console.log(" DeepSeek V4 Pro 仅可使用余额 → https://u1s1.io/dashboard");
|
|
91
|
+
console.log(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
|
|
92
|
+
console.log("");
|
|
93
|
+
}
|
|
94
|
+
function printLegacyUsage(me, tokensPerUsd) {
|
|
33
95
|
const freeRemain = me.daily_free_remaining_usd;
|
|
34
96
|
const freeTotal = me.daily_free_usd;
|
|
35
97
|
const freeRatio = freeTotal > 0 ? freeRemain / freeTotal : 0;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (me.packages) {
|
|
40
|
-
// 新网关:按用量包逐个展示,token 原生数字
|
|
41
|
-
const PKG_LABEL = {
|
|
42
|
-
free_first: "首月免费包",
|
|
43
|
-
free_yearly: "年度免费包",
|
|
44
|
-
invite: "邀请赠送",
|
|
45
|
-
topup_daily: "每日加量包",
|
|
46
|
-
admin_grant: "官方赠送",
|
|
47
|
-
};
|
|
48
|
-
if (me.packages.length === 0)
|
|
49
|
-
console.log(" 用量包 (无生效中的用量包)");
|
|
50
|
-
// 同种类、同范围、同有效期的包合并成一条展示(比如邀请多个好友拿到的多份邀请赠送)
|
|
51
|
-
const groups = new Map();
|
|
52
|
-
for (const p of me.packages) {
|
|
53
|
-
const key = `${p.kind}|${p.scope}|${p.daily_tokens != null}|${p.expires_at || ""}`;
|
|
54
|
-
const g = groups.get(key);
|
|
55
|
-
// 可空字段求和:两边都是 null 就保持 null,别把「一次性包没有日额度」污染成 0
|
|
56
|
-
const sumOpt = (a, b) => (a == null && b == null ? null : (a ?? 0) + (b ?? 0));
|
|
57
|
-
if (g) {
|
|
58
|
-
g.count += 1;
|
|
59
|
-
g.daily_tokens = sumOpt(g.daily_tokens, p.daily_tokens);
|
|
60
|
-
g.total_tokens = sumOpt(g.total_tokens, p.total_tokens);
|
|
61
|
-
g.remaining += p.remaining;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
groups.set(key, { ...p, count: 1 });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
for (const p of groups.values()) {
|
|
68
|
-
const daily = p.daily_tokens != null;
|
|
69
|
-
const total = daily ? (p.daily_tokens ?? 0) : (p.total_tokens ?? 0);
|
|
70
|
-
const ratio = total > 0 ? p.remaining / total : 0;
|
|
71
|
-
const label = ((PKG_LABEL[p.kind] ?? p.kind) + (p.count > 1 ? ` ×${p.count}` : "")).padEnd(5, " ");
|
|
72
|
-
const per = daily ? "/天" : "";
|
|
73
|
-
console.log(` ${label} 还剩 ${fmtTokensCn(p.remaining)} / ${fmtTokensCn(total)}${per} ${bar(ratio)}`);
|
|
74
|
-
const scopeNote = p.kind === "invite"
|
|
75
|
-
? "仅限 u1s1 客户端使用 · 全模型可用"
|
|
76
|
-
: p.scope === "free" ? "仅默认模型和搜索 · 0 点恢复" : "全模型可用";
|
|
77
|
-
console.log(` ${scopeNote} · ${p.expires_at ? `${p.expires_at.slice(0, 10)} 到期` : "永不过期"}`);
|
|
78
|
-
}
|
|
79
|
-
if (me.bonus_balance_usd > 0) {
|
|
80
|
-
const balText = tpu > 0 ? `${fmtTokensCn(me.bonus_balance_usd * tpu)} Token` : `$${me.bonus_balance_usd.toFixed(2)}`;
|
|
81
|
-
console.log(` 余额(按量) ${balText}`);
|
|
82
|
-
}
|
|
83
|
-
console.log(` 本月已用 ${tpu > 0 ? `约 ${fmtTokensCn(me.mtd_usd * tpu)} Token($${me.mtd_usd.toFixed(2)})` : `$${me.mtd_usd.toFixed(2)}`}`);
|
|
84
|
-
console.log("");
|
|
85
|
-
if (me.free_claim === "first") {
|
|
86
|
-
console.log(" → 你有免费用量包没领:首月每天 1 亿 Token,去 https://u1s1.io/dashboard 点「领取」");
|
|
87
|
-
}
|
|
88
|
-
else if (me.free_claim === "renew") {
|
|
89
|
-
console.log(" → 免费用量包到期了,去 https://u1s1.io/dashboard 续领:每天 3000 万 Token,一年有效");
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
console.log(" 免费包每天 0 点恢复;邀请礼包需受邀人手动领取,仅限 u1s1 客户端 → https://u1s1.io/dashboard");
|
|
93
|
-
}
|
|
94
|
-
console.log(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
|
|
95
|
-
console.log("");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (tpu > 0) {
|
|
99
|
-
const tok = (usd) => `${fmtTokensCn(usd * tpu)} Token`;
|
|
100
|
-
console.log(` 今日免费 还剩 ${fmtTokensCn(freeRemain * tpu)} / ${tok(freeTotal)} ${bar(freeRatio)}`);
|
|
98
|
+
if (tokensPerUsd > 0) {
|
|
99
|
+
const tokens = (usd) => `${fmtTokensCn(usd * tokensPerUsd)} Token`;
|
|
100
|
+
console.log(` 今日免费 还剩 ${fmtTokensCn(freeRemain * tokensPerUsd)} / ${tokens(freeTotal)} ${bar(freeRatio)}`);
|
|
101
101
|
console.log(" DeepSeek V4 Flash · 北京时间 0 点恢复");
|
|
102
|
-
console.log(` 永久余额 ${
|
|
103
|
-
console.log(` 本月已用 约 ${
|
|
102
|
+
console.log(` 永久余额 ${tokens(me.remaining_usd)}`);
|
|
103
|
+
console.log(` 本月已用 约 ${tokens(me.mtd_usd)}($${me.mtd_usd.toFixed(2)})`);
|
|
104
104
|
console.log("");
|
|
105
105
|
console.log(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
// 老网关没下发 tokens_per_usd,退回金额显示
|
|
109
108
|
console.log(` 今日免费 $${freeRemain.toFixed(2)} / $${freeTotal.toFixed(2)} ${bar(freeRatio)}`);
|
|
110
109
|
console.log(" DeepSeek V4 Flash · 北京时间 0 点恢复");
|
|
111
110
|
console.log(` 永久余额 $${me.remaining_usd.toFixed(2)}`);
|
|
@@ -115,3 +114,22 @@ export async function usage() {
|
|
|
115
114
|
console.log(" 免费额度每天自动恢复;邀请朋友双方各得永久加量 → https://u1s1.io/dashboard");
|
|
116
115
|
console.log("");
|
|
117
116
|
}
|
|
117
|
+
export async function usage() {
|
|
118
|
+
const cfg = loadConfig();
|
|
119
|
+
if (!hasDeviceCredential(cfg)) {
|
|
120
|
+
console.error("还没登录,先跑 u1s1 login");
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const me = await fetchMe(cfg).catch((e) => {
|
|
124
|
+
console.error(e.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
127
|
+
const tokensPerUsd = me.tokens_per_usd ?? 0;
|
|
128
|
+
console.log("");
|
|
129
|
+
console.log(` 账号 ${me.email ?? "(未绑定邮箱)"}`);
|
|
130
|
+
if (me.packages) {
|
|
131
|
+
printPackageUsage(me, tokensPerUsd);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
printLegacyUsage(me, tokensPerUsd);
|
|
135
|
+
}
|
package/dist/web.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { cleanupBrandThemes, endpointKeyEnv, ensureAuthCredential, ensureBrandPrompt, ensureWorkflowPromptTemplate, ensureDefaultSettings, ensureProviderModels, scrubForeignProviderEnv, writeAttributionExtension, writeWebToolsExtension, } from "./agent-setup.js";
|
|
4
|
-
import { agentDir, apiModelToDef, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
|
|
4
|
+
import { agentDir, apiModelToDef, MODELS, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
|
|
5
5
|
import { ensureSearchTools } from "./search-tools.js";
|
|
6
6
|
import { ensureUsableShell } from "./shell-doctor.js";
|
|
7
7
|
import { fetchModels, loadCustomEndpoints } from "./api.js";
|
|
@@ -38,6 +38,7 @@ export async function prepareWebEnv(cfg) {
|
|
|
38
38
|
console.error(" 获取模型列表失败,使用内置列表:", e.message);
|
|
39
39
|
}
|
|
40
40
|
await endpointsReady;
|
|
41
|
+
ensureDefaultSettings(MODELS);
|
|
41
42
|
const signing = await ensureSigningProxy(cfg, "desktop");
|
|
42
43
|
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
43
44
|
ensureBrandPrompt(await shellReady);
|
package/dist/workflow/runner.js
CHANGED
|
@@ -189,12 +189,12 @@ export async function runWorkflow(opts) {
|
|
|
189
189
|
store.append({ key, task: task.slice(0, 120), ok: true, text: out.text, ms: Date.now() - t0 });
|
|
190
190
|
return out.text;
|
|
191
191
|
}
|
|
192
|
-
catch (
|
|
192
|
+
catch (error) {
|
|
193
193
|
stats.failed++;
|
|
194
194
|
progress.failed = stats.failed;
|
|
195
|
-
const msg =
|
|
195
|
+
const msg = error.message || "未知错误";
|
|
196
196
|
store.append({ key, task: task.slice(0, 120), ok: false, text: msg, ms: Date.now() - t0 });
|
|
197
|
-
throw new Error(msg);
|
|
197
|
+
throw new Error(msg, { cause: error });
|
|
198
198
|
}
|
|
199
199
|
finally {
|
|
200
200
|
if (worktreeDir)
|
|
@@ -221,15 +221,20 @@ export async function runWorkflow(opts) {
|
|
|
221
221
|
async function parallel(thunks) {
|
|
222
222
|
if (!Array.isArray(thunks))
|
|
223
223
|
throw new Error("parallel() 需要一个 thunk 数组");
|
|
224
|
-
const results =
|
|
225
|
-
await runPool(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
const results = [];
|
|
225
|
+
await runPool({
|
|
226
|
+
items: thunks,
|
|
227
|
+
limit: SUBAGENT_CONCURRENCY,
|
|
228
|
+
run: async (thunk, i) => {
|
|
229
|
+
results[i] = await Promise.resolve()
|
|
230
|
+
.then(thunk)
|
|
231
|
+
.catch((e) => {
|
|
232
|
+
logs.push(`✗ 任务 ${i + 1} 失败:${e.message}`);
|
|
233
|
+
return null;
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
signal: internal.signal,
|
|
237
|
+
});
|
|
233
238
|
return results;
|
|
234
239
|
}
|
|
235
240
|
/** 无屏障流水线:每个 item 独立穿过所有 stage,item 失败只废自己。 */
|
|
@@ -238,21 +243,26 @@ export async function runWorkflow(opts) {
|
|
|
238
243
|
throw new Error("pipeline() 第一个参数需要数组");
|
|
239
244
|
if (!Array.isArray(stages) || stages.length === 0)
|
|
240
245
|
throw new Error("pipeline() 第二个参数需要非空 stage 数组");
|
|
241
|
-
const results =
|
|
242
|
-
await runPool(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
const results = [];
|
|
247
|
+
await runPool({
|
|
248
|
+
items: items,
|
|
249
|
+
limit: SUBAGENT_CONCURRENCY,
|
|
250
|
+
run: async (item, i) => {
|
|
251
|
+
let current = item;
|
|
252
|
+
for (let s = 0; s < stages.length; s++) {
|
|
253
|
+
try {
|
|
254
|
+
current = await stages[s](current);
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
logs.push(`✗ item ${i + 1} 在第 ${s + 1} 阶段失败:${e.message}`);
|
|
258
|
+
current = null;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
252
261
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
262
|
+
results[i] = current;
|
|
263
|
+
},
|
|
264
|
+
signal: internal.signal,
|
|
265
|
+
});
|
|
256
266
|
return results;
|
|
257
267
|
}
|
|
258
268
|
// 沙箱:只有编排原语 + 受限 log/timer,标准 JS 内建(Promise/JSON/Math…)天然可用
|
package/dist/workflow/tool.js
CHANGED
|
@@ -6,6 +6,65 @@ import { Type } from "typebox";
|
|
|
6
6
|
import { compactResultRender, truncate } from "../tools.js";
|
|
7
7
|
import { runWorkflow, saveWorkflowScript, validateScript, workflowsDir, WORKFLOW_DEFAULT_BUDGET_TOKENS, WORKFLOW_TIMEOUT_MS, } from "./runner.js";
|
|
8
8
|
import { buildFromTemplate, TEMPLATES } from "./templates.js";
|
|
9
|
+
function assertValidWorkflowCode(code) {
|
|
10
|
+
const errors = validateScript(code);
|
|
11
|
+
if (errors.length > 0) {
|
|
12
|
+
throw new Error(`脚本未通过静态校验,请修复后重试:\n- ${errors.join("\n- ")}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function buildTemplateSource(template, input) {
|
|
16
|
+
try {
|
|
17
|
+
return buildFromTemplate(template, input);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw new Error(`${error.message}\n模板 input 说明:${TEMPLATES[template]?.inputHint ?? "—"}`, { cause: error });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readWorkflowSource(scriptPath) {
|
|
24
|
+
try {
|
|
25
|
+
return readFileSync(scriptPath, "utf8");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error(`读不到脚本文件:${scriptPath}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function resolveWorkflowSource(params) {
|
|
32
|
+
const saveAs = params.save_as?.trim();
|
|
33
|
+
if (saveAs && !/^[a-zA-Z0-9_-]+$/.test(saveAs)) {
|
|
34
|
+
throw new Error("save_as 只允许字母、数字、横线和下划线");
|
|
35
|
+
}
|
|
36
|
+
if (saveAs && params.script_path?.trim()) {
|
|
37
|
+
throw new Error("script_path 指向的脚本已经落盘,不能再配 save_as;要另存新名字请用 script 传入源码");
|
|
38
|
+
}
|
|
39
|
+
const inlineCode = params.script?.trim();
|
|
40
|
+
if (inlineCode) {
|
|
41
|
+
assertValidWorkflowCode(inlineCode);
|
|
42
|
+
return { code: inlineCode, saveAs };
|
|
43
|
+
}
|
|
44
|
+
const template = params.template?.trim();
|
|
45
|
+
if (template) {
|
|
46
|
+
return { code: buildTemplateSource(template, params.input ?? {}), saveAs };
|
|
47
|
+
}
|
|
48
|
+
const requestedPath = params.script_path?.trim();
|
|
49
|
+
if (requestedPath) {
|
|
50
|
+
const scriptPath = resolve(requestedPath);
|
|
51
|
+
return { code: readWorkflowSource(scriptPath), scriptPath };
|
|
52
|
+
}
|
|
53
|
+
throw new Error("script、template、script_path 至少填一个");
|
|
54
|
+
}
|
|
55
|
+
function persistWorkflowSource(source) {
|
|
56
|
+
if (source.scriptPath)
|
|
57
|
+
return source.scriptPath;
|
|
58
|
+
if (!source.saveAs)
|
|
59
|
+
return saveWorkflowScript(source.code);
|
|
60
|
+
const scriptPath = resolve(workflowsDir(), `${source.saveAs}.mjs`);
|
|
61
|
+
if (existsSync(scriptPath)) {
|
|
62
|
+
throw new Error(`工作流 "${source.saveAs}" 已存在(${scriptPath})。要重跑它请用 script_path 指向该文件(可加 resume:true 续跑);要新建请换个名字`);
|
|
63
|
+
}
|
|
64
|
+
mkdirSync(dirname(scriptPath), { recursive: true });
|
|
65
|
+
writeFileSync(scriptPath, source.code);
|
|
66
|
+
return scriptPath;
|
|
67
|
+
}
|
|
9
68
|
/**
|
|
10
69
|
* run_workflow 工具:主 agent 把模型生成的编排脚本交给 WorkflowRunner 执行。
|
|
11
70
|
* 脚本在 vm 沙箱里跑,只能用注入的 subagent/parallel/pipeline 原语。
|
|
@@ -61,67 +120,20 @@ export function createRunWorkflowTool(getParentModel) {
|
|
|
61
120
|
renderCall() {
|
|
62
121
|
return new Text("", 0, 0);
|
|
63
122
|
},
|
|
64
|
-
renderResult(
|
|
123
|
+
renderResult(...args) {
|
|
124
|
+
const [result, options, theme, context] = args;
|
|
65
125
|
const d = result.details;
|
|
66
126
|
const summary = d && typeof d.ok === "number"
|
|
67
127
|
? `🧭 工作流 · ✓${d.ok}${d.failed ? ` ✗${d.failed}` : ""}` +
|
|
68
128
|
(typeof d.seconds === "number" ? ` · ${d.seconds}s` : "") +
|
|
69
129
|
(d.aborted ? " · 已中止" : "")
|
|
70
130
|
: "🧭 工作流";
|
|
71
|
-
return compactResultRender(result, options, theme, context, summary);
|
|
131
|
+
return compactResultRender({ result, options, theme, context, summaryLine: summary });
|
|
72
132
|
},
|
|
73
|
-
async execute(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
if (named && !/^[a-zA-Z0-9_-]+$/.test(named)) {
|
|
78
|
-
throw new Error("save_as 只允许字母、数字、横线和下划线");
|
|
79
|
-
}
|
|
80
|
-
if (named && params.script_path?.trim()) {
|
|
81
|
-
throw new Error("script_path 指向的脚本已经落盘,不能再配 save_as;要另存新名字请用 script 传入源码");
|
|
82
|
-
}
|
|
83
|
-
if (code) {
|
|
84
|
-
const errors = validateScript(code);
|
|
85
|
-
if (errors.length > 0) {
|
|
86
|
-
throw new Error(`脚本未通过静态校验,请修复后重试:\n- ${errors.join("\n- ")}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else if (params.template?.trim()) {
|
|
90
|
-
// 内置模板:模型只填参数,脚本由模板生成(生成后自检过静态校验)
|
|
91
|
-
try {
|
|
92
|
-
code = buildFromTemplate(params.template.trim(), (params.input ?? {}));
|
|
93
|
-
}
|
|
94
|
-
catch (e) {
|
|
95
|
-
throw new Error(`${e.message}\n模板 input 说明:${TEMPLATES[params.template.trim()]?.inputHint ?? "—"}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else if (params.script_path?.trim()) {
|
|
99
|
-
scriptPath = resolve(params.script_path.trim());
|
|
100
|
-
try {
|
|
101
|
-
code = readFileSync(scriptPath, "utf8");
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
throw new Error(`读不到脚本文件:${scriptPath}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
throw new Error("script、template、script_path 至少填一个");
|
|
109
|
-
}
|
|
110
|
-
// 落盘:save_as 用语义化名字(同名报错防覆盖进度配对),否则时间戳名;
|
|
111
|
-
// script_path 分支上面已读入,不重复落盘
|
|
112
|
-
if (!scriptPath) {
|
|
113
|
-
if (named) {
|
|
114
|
-
scriptPath = resolve(workflowsDir(), `${named}.mjs`);
|
|
115
|
-
if (existsSync(scriptPath)) {
|
|
116
|
-
throw new Error(`工作流 "${named}" 已存在(${scriptPath})。要重跑它请用 script_path 指向该文件(可加 resume:true 续跑);要新建请换个名字`);
|
|
117
|
-
}
|
|
118
|
-
mkdirSync(dirname(scriptPath), { recursive: true });
|
|
119
|
-
writeFileSync(scriptPath, code ?? "");
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
scriptPath = saveWorkflowScript(code ?? "");
|
|
123
|
-
}
|
|
124
|
-
}
|
|
133
|
+
async execute(...args) {
|
|
134
|
+
const [, params, signal, onUpdate] = args;
|
|
135
|
+
const source = resolveWorkflowSource(params);
|
|
136
|
+
const scriptPath = persistWorkflowSource(source);
|
|
125
137
|
const progressPath = scriptPath.replace(/\.mjs$/, ".progress.jsonl");
|
|
126
138
|
// 流式进度:onProgress 高频触发,节流到 ≥2s 一次才推给 TUI
|
|
127
139
|
let lastPush = 0;
|
|
@@ -143,7 +155,7 @@ export function createRunWorkflowTool(getParentModel) {
|
|
|
143
155
|
}
|
|
144
156
|
: undefined;
|
|
145
157
|
const result = await runWorkflow({
|
|
146
|
-
code: code
|
|
158
|
+
code: source.code,
|
|
147
159
|
progressPath,
|
|
148
160
|
resume: params.resume === true,
|
|
149
161
|
parentModel: getParentModel(),
|
package/package.json
CHANGED
|
@@ -60,7 +60,9 @@ function hiddenToolDef(orig) {
|
|
|
60
60
|
renderCall() {
|
|
61
61
|
return new Text("", 0, 0);
|
|
62
62
|
},
|
|
63
|
-
|
|
63
|
+
// pi owns this positional callback contract; keep the adapter narrow here.
|
|
64
|
+
renderResult(...args) {
|
|
65
|
+
const [result, { expanded }, theme, context] = args;
|
|
64
66
|
if (context.isError) {
|
|
65
67
|
const c = result.content.find((x) => x.type === "text");
|
|
66
68
|
return new Text(theme.fg("warning", "✗ ") + theme.fg("muted", c?.text ?? "error"), 0, 0);
|