u1s1-cli 0.19.0 → 0.19.2

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/loop.js CHANGED
@@ -59,12 +59,15 @@ function parseArgs(ui, raw) {
59
59
  const tokens = raw.trim().split(/\s+/).filter(Boolean);
60
60
  let intervalMs;
61
61
  let intervalIdx = -1;
62
+ // 命中的是不是末尾 "every 30m" 这种占两个 token 的形式(删除时要一起删掉 "every")
63
+ let matchedEveryForm = false;
62
64
  // 末尾的 "every 30m" 形式占两个 token,先于前置匹配检查
63
65
  if (tokens.length >= 2 && tokens[tokens.length - 2].toLowerCase() === "every") {
64
66
  const parsed = parseIntervalMs(tokens[tokens.length - 1]);
65
67
  if (parsed !== undefined) {
66
68
  intervalMs = parsed;
67
69
  intervalIdx = tokens.length - 2;
70
+ matchedEveryForm = true;
68
71
  }
69
72
  }
70
73
  if (intervalMs === undefined) {
@@ -79,7 +82,7 @@ function parseArgs(ui, raw) {
79
82
  }
80
83
  const rest = tokens.slice();
81
84
  if (intervalMs !== undefined) {
82
- rest.splice(intervalIdx, intervalIdx === tokens.length - 2 ? 2 : 1);
85
+ rest.splice(intervalIdx, matchedEveryForm ? 2 : 1);
83
86
  }
84
87
  const prompt = rest.join(" ").trim();
85
88
  if (!prompt) {
@@ -12,11 +12,17 @@ function strArray(v) {
12
12
  function lit(v) {
13
13
  return JSON.stringify(v ?? null);
14
14
  }
15
- /** git 改动文件列表(模板构建期在主进程执行,不进沙箱)。 */
15
+ /** git 改动文件列表(模板构建期在主进程执行,不进沙箱)。
16
+ * 剔除已删除文件(评审/重构 agent 没法打开),并入未跟踪的新文件。 */
16
17
  function gitDiffFiles() {
17
- try {
18
- const out = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
18
+ const run = (args) => {
19
+ const out = execFileSync("git", args, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
19
20
  return out.split("\n").map((s) => s.trim()).filter(Boolean);
21
+ };
22
+ try {
23
+ const changed = run(["diff", "--name-only", "--diff-filter=d", "HEAD"]);
24
+ const untracked = run(["ls-files", "--others", "--exclude-standard"]);
25
+ return [...new Set([...changed, ...untracked])];
20
26
  }
21
27
  catch {
22
28
  return [];
@@ -33,20 +39,23 @@ export const TEMPLATES = {
33
39
  throw new Error('i18n 模板需要 langs 数组,例如 { "langs": ["en", "ja"] }');
34
40
  const source = str(input.source, "src/locales/zh.json");
35
41
  const dir = str(input.dir, source.includes("/") ? source.slice(0, source.lastIndexOf("/")) : "src/locales");
42
+ // 用户输入一律经 lit() 进脚本,再在运行期拼接——裸内插遇到引号/反斜杠/换行会把生成的脚本弄成语法错误
36
43
  return `
37
44
  const langs = ${lit(langs)};
38
- log("i18n:翻译 " + langs.length + " 个语言,源文件 ${source}");
45
+ const source = ${lit(source)};
46
+ const dir = ${lit(dir)};
47
+ log("i18n:翻译 " + langs.length + " 个语言,源文件 " + source);
39
48
  const results = await parallel(langs.map(lang => () =>
40
49
  subagent({
41
- task: "读取 ${source},把所有文案翻译成对应语言(保持所有 key 不变、输出合法 JSON、译文自然地道)," +
42
- "写入 ${dir}/" + lang + ".json。写完后重新读一遍文件,确认是合法 JSON 且 key 与源文件完全一致。" +
50
+ task: "读取 " + source + ",把所有文案翻译成对应语言(保持所有 key 不变、输出合法 JSON、译文自然地道)," +
51
+ "写入 " + dir + "/" + lang + ".json。写完后重新读一遍文件,确认是合法 JSON 且 key 与源文件完全一致。" +
43
52
  "目标语言代码:" + lang
44
53
  })
45
54
  ));
46
55
  const okLangs = langs.filter((_, i) => results[i] !== null);
47
56
  if (okLangs.length > 0) {
48
57
  await subagent({
49
- task: "对照 ${source} 检查 ${dir}/ 目录下这些语言的翻译文件:" + okLangs.join(",") + "." +
58
+ task: "对照 " + source + " 检查 " + dir + "/ 目录下这些语言的翻译文件:" + okLangs.join(",") + "." +
50
59
  "逐个核对 key 是否与源文件完全一致、JSON 是否合法、译文是否有明显机翻痕迹,发现问题直接修复。最后汇报每个语言的状态"
51
60
  });
52
61
  }
@@ -95,11 +104,12 @@ return { verified };
95
104
  const contextLine = context ? `调查背景:${context}。` : "";
96
105
  return `
97
106
  const questions = ${lit(questions)};
107
+ const contextLine = ${lit(contextLine)};
98
108
  log("research:并行调查 " + questions.length + " 个方向");
99
109
  const reports = await parallel(questions.map(q => () =>
100
110
  subagent({
101
111
  task: "调查以下问题,可以用 web_search/web_fetch 联网,也可以读本地代码和文档。" +
102
- "输出:关键事实(带来源)、不同观点、你的结论。${contextLine}问题:" + q
112
+ "输出:关键事实(带来源)、不同观点、你的结论。" + contextLine + "问题:" + q
103
113
  })
104
114
  ));
105
115
  const merged = reports
@@ -123,17 +133,15 @@ return { synthesis };
123
133
  const instruction = str(input.instruction);
124
134
  if (!instruction)
125
135
  throw new Error('refactor 模板需要 instruction,例如 { "instruction": "把 var 全部改为 const" }');
126
- const files = strArray(input.files);
136
+ // review 模板一致:构建期取 git 改动文件,不花一个 subagent 在沙箱里跑 git
137
+ let files = strArray(input.files);
138
+ if (files.length === 0)
139
+ files = gitDiffFiles();
140
+ if (files.length === 0)
141
+ throw new Error("refactor 模板需要 files 数组,或当前仓库有未提交改动");
127
142
  return `
128
143
  const instruction = ${lit(instruction)};
129
- let targetFiles = ${lit(files)};
130
- if (targetFiles.length === 0) {
131
- const listed = await subagent({
132
- task: "运行 git diff --name-only HEAD,把输出的文件路径一行一个原样返回,不要加任何其他文字。"
133
- });
134
- targetFiles = listed.split("\\n").map(s => s.trim()).filter(Boolean);
135
- }
136
- if (targetFiles.length === 0) throw new Error("没有可处理的文件:files 为空且 git 无改动");
144
+ const targetFiles = ${lit(files)};
137
145
  log("refactor:共 " + targetFiles.length + " 个文件,先制定统一规则");
138
146
  const rule = await subagent({
139
147
  task: "我们要对一批文件做统一重构:" + instruction + "。" +
@@ -39,7 +39,7 @@ export function createRunWorkflowTool(getParentModel) {
39
39
  description: "Path to an existing workflow script under .u1s1/workflows/ — rerun it instead of passing `script`.",
40
40
  })),
41
41
  save_as: Type.Optional(Type.String({
42
- 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.',
42
+ 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`.',
43
43
  pattern: "^[a-zA-Z0-9_-]+$",
44
44
  })),
45
45
  resume: Type.Optional(Type.Boolean({
@@ -77,6 +77,9 @@ export function createRunWorkflowTool(getParentModel) {
77
77
  if (named && !/^[a-zA-Z0-9_-]+$/.test(named)) {
78
78
  throw new Error("save_as 只允许字母、数字、横线和下划线");
79
79
  }
80
+ if (named && params.script_path?.trim()) {
81
+ throw new Error("script_path 指向的脚本已经落盘,不能再配 save_as;要另存新名字请用 script 传入源码");
82
+ }
80
83
  if (code) {
81
84
  const errors = validateScript(code);
82
85
  if (errors.length > 0) {
@@ -107,12 +110,17 @@ export function createRunWorkflowTool(getParentModel) {
107
110
  // 落盘:save_as 用语义化名字(同名报错防覆盖进度配对),否则时间戳名;
108
111
  // script_path 分支上面已读入,不重复落盘
109
112
  if (!scriptPath) {
110
- scriptPath = named ? resolve(workflowsDir(), `${named}.mjs`) : saveWorkflowScript(code ?? "");
111
- if (named && existsSync(scriptPath)) {
112
- throw new Error(`工作流 "${named}" 已存在(${scriptPath})。要重跑它请用 script_path 指向该文件(可加 resume:true 续跑);要新建请换个名字`);
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 ?? "");
113
123
  }
114
- mkdirSync(dirname(scriptPath), { recursive: true });
115
- writeFileSync(scriptPath, code ?? "");
116
124
  }
117
125
  const progressPath = scriptPath.replace(/\.mjs$/, ".progress.jsonl");
118
126
  // 流式进度:onProgress 高频触发,节流到 ≥2s 一次才推给 TUI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {