u1s1-cli 1.13.2 → 1.14.1

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.
@@ -1,179 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
- import { defineTool } from "@earendil-works/pi-coding-agent";
4
- import { Text } from "@earendil-works/pi-tui";
5
- import { Type } from "typebox";
6
- import { tr } from "../i18n.js";
7
- import { contextProjectTrust } from "../subagent.js";
8
- import { compactResultRender, truncate } from "../tools.js";
9
- import { runWorkflow, saveWorkflowScript, validateScript, workflowsDir, WORKFLOW_DEFAULT_BUDGET_TOKENS, WORKFLOW_TIMEOUT_MS, } from "./runner.js";
10
- import { buildFromTemplate, TEMPLATES } from "./templates.js";
11
- function assertValidWorkflowCode(code) {
12
- const errors = validateScript(code);
13
- if (errors.length > 0) {
14
- throw new Error(tr(`脚本未通过静态校验,请修复后重试:\n- ${errors.join("\n- ")}`, `The script failed static validation; fix it and try again:\n- ${errors.join("\n- ")}`));
15
- }
16
- }
17
- function buildTemplateSource(template, input) {
18
- try {
19
- return buildFromTemplate(template, input);
20
- }
21
- catch (error) {
22
- throw new Error(tr(`${error.message}\n模板 input 说明:${TEMPLATES[template]?.inputHint ?? "—"}`, `${error.message}\nTemplate input: ${TEMPLATES[template]?.inputHint ?? "—"}`), { cause: error });
23
- }
24
- }
25
- function readWorkflowSource(scriptPath) {
26
- try {
27
- return readFileSync(scriptPath, "utf8");
28
- }
29
- catch {
30
- throw new Error(tr(`读不到脚本文件:${scriptPath}`, `Can't read the script file: ${scriptPath}`));
31
- }
32
- }
33
- function resolveWorkflowSource(params) {
34
- const saveAs = params.save_as?.trim();
35
- if (saveAs && !/^[a-zA-Z0-9_-]+$/.test(saveAs)) {
36
- throw new Error(tr("save_as 只允许字母、数字、横线和下划线", "save_as may only contain letters, digits, hyphens, and underscores"));
37
- }
38
- if (saveAs && params.script_path?.trim()) {
39
- throw new Error(tr("script_path 指向的脚本已经落盘,不能再配 save_as;要另存新名字请用 script 传入源码", "The script at script_path is already saved, so save_as can't be used with it; to save under a new name, pass the source via script"));
40
- }
41
- const inlineCode = params.script?.trim();
42
- if (inlineCode) {
43
- assertValidWorkflowCode(inlineCode);
44
- return { code: inlineCode, saveAs };
45
- }
46
- const template = params.template?.trim();
47
- if (template) {
48
- return { code: buildTemplateSource(template, params.input ?? {}), saveAs };
49
- }
50
- const requestedPath = params.script_path?.trim();
51
- if (requestedPath) {
52
- const scriptPath = resolve(requestedPath);
53
- return { code: readWorkflowSource(scriptPath), scriptPath };
54
- }
55
- throw new Error(tr("script、template、script_path 至少填一个", "Provide at least one of script, template, or script_path"));
56
- }
57
- function persistWorkflowSource(source) {
58
- if (source.scriptPath)
59
- return source.scriptPath;
60
- if (!source.saveAs)
61
- return saveWorkflowScript(source.code);
62
- const scriptPath = resolve(workflowsDir(), `${source.saveAs}.mjs`);
63
- if (existsSync(scriptPath)) {
64
- throw new Error(tr(`工作流 "${source.saveAs}" 已存在(${scriptPath})。要重跑它请用 script_path 指向该文件(可加 resume:true 续跑);要新建请换个名字`, `Workflow "${source.saveAs}" already exists (${scriptPath}). To rerun it, point script_path at that file (add resume:true to continue); to create a new one, pick another name`));
65
- }
66
- mkdirSync(dirname(scriptPath), { recursive: true });
67
- writeFileSync(scriptPath, source.code);
68
- return scriptPath;
69
- }
70
- /**
71
- * run_workflow 工具:主 agent 把模型生成的编排脚本交给 WorkflowRunner 执行。
72
- * 脚本在 vm 沙箱里跑,只能用注入的 subagent/parallel/pipeline 原语。
73
- */
74
- export function createRunWorkflowTool(getParentModel) {
75
- return defineTool({
76
- name: "run_workflow",
77
- label: tr("工作流", "Workflow"),
78
- description: "Execute a JavaScript orchestration script that runs many sub-agents in structured patterns (fan-out/pipeline/loop). " +
79
- "The script runs in a sandbox with ONLY these APIs: `subagent(task | {task, model})` returns the sub-agent's final output; " +
80
- "`parallel([thunks])` fans out concurrently, a failed item becomes null (barrier); " +
81
- "`pipeline(items, [stageFns])` streams each item through stages independently; plus `log(...)` and standard JS builtins. " +
82
- "No require/import/process/fetch/fs access. Use for large parallelizable work (e.g. translate N locale files); " +
83
- "don't use it for small tasks — write the script, pass it as `script`, then verify the report.",
84
- promptSnippet: "Run a generated orchestration script that drives many sub-agents in parallel/pipeline patterns",
85
- promptGuidelines: [
86
- "Use run_workflow only when a task decomposes into many independent self-contained pieces; small jobs are cheaper done directly.",
87
- "Every task string inside the script must be fully self-contained — sub-agents start with zero conversation context.",
88
- "If the report shows failures, fix the script or call again with script_path + resume:true to continue from saved progress.",
89
- ],
90
- parameters: Type.Object({
91
- script: Type.Optional(Type.String({ description: "Full JavaScript source of the orchestration script (top-level await allowed)." })),
92
- template: Type.Optional(Type.String({
93
- description: `Use a built-in template instead of writing a script: ${Object.keys(TEMPLATES).join(" / ")}. ` +
94
- "Provide `input` to parametrize it. Prefer this over hand-writing a script when a template fits.",
95
- })),
96
- input: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {
97
- description: 'Parameters for `template`, e.g. i18n: { "langs": ["en", "ja"] }; review/refactor: { "files": [...] } or omit for git-diff files; research: { "questions": [...] }.',
98
- })),
99
- script_path: Type.Optional(Type.String({
100
- description: "Path to an existing workflow script under .u1s1/workflows/ — rerun it instead of passing `script`.",
101
- })),
102
- save_as: Type.Optional(Type.String({
103
- description: 'Save this run\'s script as .u1s1/workflows/<name>.mjs (letters/digits/-/_) for easy reuse; rerun later via script_path. Fails if the name already exists. Only valid with `script` or `template`, not `script_path`.',
104
- pattern: "^[a-zA-Z0-9_-]+$",
105
- })),
106
- resume: Type.Optional(Type.Boolean({
107
- description: "Skip sub-tasks already recorded as succeeded in the progress file (for crash/interrupt recovery).",
108
- })),
109
- timeout_minutes: Type.Optional(Type.Number({
110
- description: `Wall-clock limit for the whole run in minutes (default ${WORKFLOW_TIMEOUT_MS / 60_000}, max 180).`,
111
- minimum: 1,
112
- maximum: 180,
113
- })),
114
- budget_tokens: Type.Optional(Type.Number({
115
- description: `Total token ceiling for the whole run across all sub-agents (default ${WORKFLOW_DEFAULT_BUDGET_TOKENS}). Once exceeded, no new sub-agents are spawned; running ones finish. Use 0 for unlimited.`,
116
- minimum: 0,
117
- maximum: 1_000_000_000,
118
- })),
119
- }),
120
- // 精简展示与 spawn_subagent 同款:收起一行摘要,ctrl+o 展开报告
121
- renderShell: "self",
122
- renderCall() {
123
- return new Text("", 0, 0);
124
- },
125
- renderResult(...args) {
126
- const [result, options, theme, context] = args;
127
- const d = result.details;
128
- const summary = d && typeof d.ok === "number"
129
- ? tr("🧭 工作流", "🧭 Workflow") +
130
- ` · ✓${d.ok}${d.failed ? ` ✗${d.failed}` : ""}` +
131
- (typeof d.seconds === "number" ? ` · ${d.seconds}s` : "") +
132
- (d.aborted ? tr(" · 已中止", " · aborted") : "")
133
- : tr("🧭 工作流", "🧭 Workflow");
134
- return compactResultRender({ result, options, theme, context, summaryLine: summary });
135
- },
136
- async execute(...args) {
137
- const [, params, signal, onUpdate, ctx] = args;
138
- const source = resolveWorkflowSource(params);
139
- const scriptPath = persistWorkflowSource(source);
140
- const progressPath = scriptPath.replace(/\.mjs$/, ".progress.jsonl");
141
- // 流式进度:onProgress 高频触发,节流到 ≥2s 一次才推给 TUI
142
- let lastPush = 0;
143
- const onProgress = onUpdate
144
- ? (p) => {
145
- const now = Date.now();
146
- if (now - lastPush < 2_000)
147
- return;
148
- lastPush = now;
149
- onUpdate({
150
- content: [
151
- {
152
- type: "text",
153
- text: tr(`🧭 子任务 ✓${p.ok} ✗${p.failed}${p.cached ? ` 缓存${p.cached}` : ""} · 在跑 ${p.running}`, `🧭 Subtasks ✓${p.ok} ✗${p.failed}${p.cached ? ` cached ${p.cached}` : ""} · running ${p.running}`) +
154
- ` · ${(p.spentTokens / 1000).toFixed(0)}k${p.budgetTokens > 0 ? ` / ${(p.budgetTokens / 1000).toFixed(0)}k tokens` : ""}`,
155
- },
156
- ],
157
- details: p,
158
- });
159
- }
160
- : undefined;
161
- const result = await runWorkflow({
162
- code: source.code,
163
- progressPath,
164
- resume: params.resume === true,
165
- parentModel: getParentModel(),
166
- // 子 agent(含临时 worktree)沿用主会话的项目信任裁决
167
- projectTrusted: contextProjectTrust(ctx),
168
- signal,
169
- timeoutMs: Math.min(180, params.timeout_minutes ?? WORKFLOW_TIMEOUT_MS / 60_000) * 60_000,
170
- budgetTokens: params.budget_tokens,
171
- onProgress,
172
- });
173
- return {
174
- content: [{ type: "text", text: truncate(result.report) }],
175
- details: { ...result.details, scriptPath, workflowsDir: workflowsDir() },
176
- };
177
- },
178
- });
179
- }