@trim21/personal-pi-extensions 0.1.611 → 0.1.613

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.
Files changed (75) hide show
  1. package/package.json +2 -2
  2. package/src/aft/config.ts +9 -3
  3. package/src/aft/index.ts +6 -2
  4. package/src/aft/logger.ts +9 -3
  5. package/src/aft/tools.ts +5 -3
  6. package/src/bwrap/approval-rules.ts +51 -18
  7. package/src/bwrap/approval-suggest.ts +12 -4
  8. package/src/bwrap/core.ts +39 -13
  9. package/src/bwrap/dcg-scan.ts +16 -6
  10. package/src/bwrap/holder.ts +3 -1
  11. package/src/bwrap/mihomo-config.ts +18 -6
  12. package/src/bwrap/network-stack.ts +30 -10
  13. package/src/bwrap/runtime.ts +36 -14
  14. package/src/claude-code/common.ts +6 -2
  15. package/src/claude-code/edit-utils.ts +3 -1
  16. package/src/claude-code/files.ts +40 -14
  17. package/src/claude-code/glob.ts +12 -4
  18. package/src/claude-code/grep.ts +41 -14
  19. package/src/claude-code/session-tools.ts +18 -6
  20. package/src/claude-code/shell.ts +9 -3
  21. package/src/gh/base.ts +72 -24
  22. package/src/gh/tools/download-release-assets.ts +12 -4
  23. package/src/gh/tools/list-releases.ts +3 -1
  24. package/src/gh/tools/list-workflow-runs.ts +9 -3
  25. package/src/gh/tools/read-ci-logs.ts +21 -7
  26. package/src/gh/tools/read-repo.ts +3 -1
  27. package/src/lib/cli-args.ts +6 -2
  28. package/src/lib/cli.ts +55 -22
  29. package/src/lib/file-reads.ts +24 -8
  30. package/src/lib/github.ts +35 -12
  31. package/src/lib/lsp/adapter.ts +6 -2
  32. package/src/lib/lsp/bin.ts +12 -4
  33. package/src/lib/lsp/client.ts +205 -69
  34. package/src/lib/lsp/diagnostic.ts +6 -2
  35. package/src/lib/lsp/inspect-tool.ts +25 -9
  36. package/src/lib/lsp/lsp.ts +154 -58
  37. package/src/lib/lsp/rename-tool.ts +11 -4
  38. package/src/lib/lsp/rename.ts +25 -8
  39. package/src/lib/lsp/server-config.ts +41 -14
  40. package/src/lib/lsp/watcher.ts +51 -17
  41. package/src/lib/path.ts +18 -6
  42. package/src/lib/proc.ts +3 -1
  43. package/src/lib/proxy.ts +9 -3
  44. package/src/lib/request-policy.ts +3 -1
  45. package/src/lib/seq-state.ts +3 -1
  46. package/src/lib/ui.ts +23 -8
  47. package/src/lib/write-guard.ts +21 -7
  48. package/src/openai-cost/config.ts +3 -1
  49. package/src/openai-cost/cost.ts +54 -18
  50. package/src/openai-cost/index.ts +3 -1
  51. package/src/openai-cost/provider.ts +6 -2
  52. package/src/opencode/bash.ts +9 -3
  53. package/src/opencode/edit-engine.ts +63 -41
  54. package/src/opencode/files.ts +30 -10
  55. package/src/opencode/glob.ts +3 -1
  56. package/src/opencode/grep.ts +12 -4
  57. package/src/opencode/question.ts +3 -1
  58. package/src/opencode/ripgrep.ts +23 -9
  59. package/src/opencode/todo.ts +3 -1
  60. package/src/session-name.ts +40 -16
  61. package/src/spawn-agent-agents.ts +18 -6
  62. package/src/spawn-agent.ts +58 -21
  63. package/src/system-prompt/index.ts +9 -3
  64. package/src/talk/core.ts +143 -48
  65. package/src/talk/format.ts +12 -4
  66. package/src/talk/group.ts +9 -3
  67. package/src/talk/index.ts +30 -10
  68. package/src/talk/mailbox.ts +30 -10
  69. package/src/talk/policy.ts +10 -5
  70. package/src/talk/registry.ts +35 -12
  71. package/src/talk/storage.ts +3 -1
  72. package/src/vision-agent.ts +41 -14
  73. package/src/web/config.ts +3 -1
  74. package/src/web/fetch.ts +75 -25
  75. package/src/web/search.ts +30 -10
@@ -28,7 +28,9 @@ export function normalizeToLF(text: string): string {
28
28
 
29
29
  /** Convert LF text to the file's line ending. Input must already be LF. */
30
30
  export function convertToLineEnding(text: string, ending: "\r\n" | "\n"): string {
31
- if (ending === "\n") return text;
31
+ if (ending === "\n") {
32
+ return text;
33
+ }
32
34
  return text.replaceAll("\n", "\r\n");
33
35
  }
34
36
 
@@ -119,20 +121,21 @@ const LineTrimmedReplacer: Replacer = function* (content, find) {
119
121
  break;
120
122
  }
121
123
  }
122
- if (matches) {
123
- let matchStartIndex = 0;
124
- for (let k = 0; k < i; k++) {
125
- matchStartIndex += originalLines[k].length + 1;
126
- }
127
- let matchEndIndex = matchStartIndex;
128
- for (let k = 0; k < searchLines.length; k++) {
129
- matchEndIndex += originalLines[i + k].length;
130
- if (k < searchLines.length - 1) {
131
- matchEndIndex += 1;
132
- }
124
+ if (!matches) {
125
+ continue;
126
+ }
127
+ let matchStartIndex = 0;
128
+ for (let k = 0; k < i; k++) {
129
+ matchStartIndex += originalLines[k].length + 1;
130
+ }
131
+ let matchEndIndex = matchStartIndex;
132
+ for (let k = 0; k < searchLines.length; k++) {
133
+ matchEndIndex += originalLines[i + k].length;
134
+ if (k < searchLines.length - 1) {
135
+ matchEndIndex += 1;
133
136
  }
134
- yield content.slice(matchStartIndex, matchEndIndex);
135
137
  }
138
+ yield content.slice(matchStartIndex, matchEndIndex);
136
139
  }
137
140
  };
138
141
 
@@ -230,26 +233,29 @@ const BlockAnchorReplacer: Replacer = function* (content, find) {
230
233
  } else {
231
234
  similarity = 1;
232
235
  }
233
- if (similarity > maxSimilarity) {
234
- maxSimilarity = similarity;
235
- bestMatch = candidate;
236
+ if (similarity <= maxSimilarity) {
237
+ continue;
236
238
  }
239
+
240
+ maxSimilarity = similarity;
241
+ bestMatch = candidate;
237
242
  }
238
- if (bestMatch && maxSimilarity >= MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD) {
239
- const { startLine, endLine } = bestMatch;
240
- let matchStartIndex = 0;
241
- for (let k = 0; k < startLine; k++) {
242
- matchStartIndex += originalLines[k].length + 1;
243
- }
244
- let matchEndIndex = matchStartIndex;
245
- for (let k = startLine; k <= endLine; k++) {
246
- matchEndIndex += originalLines[k].length;
247
- if (k < endLine) {
248
- matchEndIndex += 1;
249
- }
243
+ if (!bestMatch || maxSimilarity < MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD) {
244
+ return;
245
+ }
246
+ const { startLine, endLine } = bestMatch;
247
+ let matchStartIndex = 0;
248
+ for (let k = 0; k < startLine; k++) {
249
+ matchStartIndex += originalLines[k].length + 1;
250
+ }
251
+ let matchEndIndex = matchStartIndex;
252
+ for (let k = startLine; k <= endLine; k++) {
253
+ matchEndIndex += originalLines[k].length;
254
+ if (k < endLine) {
255
+ matchEndIndex += 1;
250
256
  }
251
- yield content.slice(matchStartIndex, matchEndIndex);
252
257
  }
258
+ yield content.slice(matchStartIndex, matchEndIndex);
253
259
  };
254
260
 
255
261
  function normalizeWhitespace(text: string): string {
@@ -297,7 +303,9 @@ const WhitespaceNormalizedReplacer: Replacer = function* (content, find) {
297
303
  function removeIndentation(text: string): string {
298
304
  const lines = text.split("\n");
299
305
  const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
300
- if (nonEmptyLines.length === 0) return text;
306
+ if (nonEmptyLines.length === 0) {
307
+ return text;
308
+ }
301
309
  const minIndent = Math.min(
302
310
  ...nonEmptyLines.map((line) => {
303
311
  const match = /^(\s*)/.exec(line);
@@ -323,7 +331,9 @@ const MultiOccurrenceReplacer: Replacer = function* (content, find) {
323
331
  let startIndex = 0;
324
332
  for (;;) {
325
333
  const index = content.indexOf(find, startIndex);
326
- if (index === -1) break;
334
+ if (index === -1) {
335
+ break;
336
+ }
327
337
  yield find;
328
338
  startIndex = index + find.length;
329
339
  }
@@ -359,7 +369,9 @@ const ContextAwareReplacer: Replacer = function* (content, find) {
359
369
  const firstLine = findLines[0].trim();
360
370
  const lastLine = findLines.at(-1)?.trim() ?? "";
361
371
  for (let i = 0; i < contentLines.length; i++) {
362
- if (contentLines[i].trim() !== firstLine) continue;
372
+ if (contentLines[i].trim() !== firstLine) {
373
+ continue;
374
+ }
363
375
  for (let j = i + 2; j < contentLines.length; j++) {
364
376
  if (contentLines[j].trim() === lastLine) {
365
377
  const blockLines = contentLines.slice(i, j + 1);
@@ -370,11 +382,13 @@ const ContextAwareReplacer: Replacer = function* (content, find) {
370
382
  for (let k = 1; k < blockLines.length - 1; k++) {
371
383
  const blockLine = blockLines[k].trim();
372
384
  const findLine = findLines[k].trim();
373
- if (blockLine.length > 0 || findLine.length > 0) {
374
- totalNonEmptyLines++;
375
- if (blockLine === findLine) {
376
- matchingLines++;
377
- }
385
+ if (blockLine.length === 0 && findLine.length === 0) {
386
+ continue;
387
+ }
388
+
389
+ totalNonEmptyLines++;
390
+ if (blockLine === findLine) {
391
+ matchingLines++;
378
392
  }
379
393
  }
380
394
  if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
@@ -391,8 +405,12 @@ const ContextAwareReplacer: Replacer = function* (content, find) {
391
405
  function isDisproportionateMatch(search: string, oldString: string) {
392
406
  const oldLines = oldString.split("\n").length;
393
407
  const searchLines = search.split("\n").length;
394
- if (searchLines >= Math.max(oldLines + 3, oldLines * 2)) return true;
395
- if (oldLines === 1) return false;
408
+ if (searchLines >= Math.max(oldLines + 3, oldLines * 2)) {
409
+ return true;
410
+ }
411
+ if (oldLines === 1) {
412
+ return false;
413
+ }
396
414
  return (
397
415
  search.trim().length > Math.max(oldString.trim().length + 500, oldString.trim().length * 4)
398
416
  );
@@ -432,7 +450,9 @@ export function replace(
432
450
  ]) {
433
451
  for (const search of replacer(content, oldString)) {
434
452
  const index = content.indexOf(search);
435
- if (index === -1) continue;
453
+ if (index === -1) {
454
+ continue;
455
+ }
436
456
  notFound = false;
437
457
  if (isDisproportionateMatch(search, oldString)) {
438
458
  throw new Error(
@@ -444,7 +464,9 @@ export function replace(
444
464
  return content.replaceAll(search, newString);
445
465
  }
446
466
  const lastIndex = content.lastIndexOf(search);
447
- if (index !== lastIndex) continue;
467
+ if (index !== lastIndex) {
468
+ continue;
469
+ }
448
470
  return (
449
471
  content.slice(0, Math.max(0, index)) +
450
472
  newString +
@@ -134,9 +134,13 @@ const IMAGE_SIGNATURES: {
134
134
  ];
135
135
 
136
136
  function startsWithAscii(buf: Uint8Array, offset: number, text: string): boolean {
137
- if (buf.length < offset + text.length) return false;
137
+ if (buf.length < offset + text.length) {
138
+ return false;
139
+ }
138
140
  for (let i = 0; i < text.length; i++) {
139
- if (buf[offset + i] !== text.codePointAt(i)) return false;
141
+ if (buf[offset + i] !== text.codePointAt(i)) {
142
+ return false;
143
+ }
140
144
  }
141
145
  return true;
142
146
  }
@@ -151,7 +155,9 @@ function detectImageMimeType(buffer: Uint8Array): string | null {
151
155
  }
152
156
 
153
157
  function startsWith(buffer: Uint8Array, bytes: Uint8Array): boolean {
154
- if (buffer.length < bytes.length) return false;
158
+ if (buffer.length < bytes.length) {
159
+ return false;
160
+ }
155
161
  return bytes.every((b, i) => buffer[i] === b);
156
162
  }
157
163
 
@@ -179,11 +185,17 @@ function isBinaryExtension(filePath: string): boolean {
179
185
  }
180
186
 
181
187
  function isBinaryFileBySample(sample: Uint8Array): boolean {
182
- if (sample.length === 0) return false;
188
+ if (sample.length === 0) {
189
+ return false;
190
+ }
183
191
  let nonPrintableCount = 0;
184
192
  for (const byte of sample) {
185
- if (byte === 0) return true;
186
- if (byte < 9 || (byte > 13 && byte < 32)) nonPrintableCount++;
193
+ if (byte === 0) {
194
+ return true;
195
+ }
196
+ if (byte < 9 || (byte > 13 && byte < 32)) {
197
+ nonPrintableCount++;
198
+ }
187
199
  }
188
200
  return nonPrintableCount / sample.length > 0.3;
189
201
  }
@@ -222,7 +234,9 @@ export async function readLines(
222
234
  filePath: string,
223
235
  opts: { offset: number; limit: number; maxBytes?: number; signal?: AbortSignal },
224
236
  ): Promise<LinePage> {
225
- if (opts.offset < 0) return readTailLines(filePath, opts);
237
+ if (opts.offset < 0) {
238
+ return readTailLines(filePath, opts);
239
+ }
226
240
  const start = opts.offset - 1;
227
241
  const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
228
242
  const raw: string[] = [];
@@ -238,7 +252,9 @@ export async function readLines(
238
252
  // 大文件的全量扫描可能持续数秒:每行检查一次取消,别等到读完才响应
239
253
  opts.signal?.throwIfAborted();
240
254
  count += 1;
241
- if (count <= start) continue;
255
+ if (count <= start) {
256
+ continue;
257
+ }
242
258
 
243
259
  if (raw.length >= opts.limit) {
244
260
  more = true;
@@ -287,7 +303,9 @@ async function readTailLines(
287
303
  const line =
288
304
  text.length > MAX_LINE_LENGTH ? text.slice(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX : text;
289
305
  window.push(line);
290
- if (window.length > windowSize) head += 1;
306
+ if (window.length > windowSize) {
307
+ head += 1;
308
+ }
291
309
  }
292
310
  } finally {
293
311
  rl.close();
@@ -808,7 +826,9 @@ function registerWriteTool(
808
826
  try {
809
827
  existing = Buffer.alloc(3);
810
828
  const { bytesRead } = await fh.read(existing, 0, 3, 0);
811
- if (bytesRead < 3) existing = undefined;
829
+ if (bytesRead < 3) {
830
+ existing = undefined;
831
+ }
812
832
  } finally {
813
833
  await fh.close();
814
834
  }
@@ -34,7 +34,9 @@ function normalizeRipgrepPath(text: string): string {
34
34
  }
35
35
 
36
36
  export function renderGlobOutput(files: readonly string[], truncated: boolean): string {
37
- if (files.length === 0) return "No files found";
37
+ if (files.length === 0) {
38
+ return "No files found";
39
+ }
38
40
  const output = [...files];
39
41
  if (truncated) {
40
42
  output.push(
@@ -57,7 +57,9 @@ function normalizeRipgrepPath(text: string): string {
57
57
  /** 行文本:去掉行尾换行,超长截断(不留半个 surrogate pair)。 */
58
58
  function normalizeLineText(text: string): string {
59
59
  const line = text.replace(/[\r\n]+$/u, "");
60
- if (line.length <= MAX_LINE_LENGTH) return line;
60
+ if (line.length <= MAX_LINE_LENGTH) {
61
+ return line;
62
+ }
61
63
  return line.slice(0, MAX_LINE_LENGTH).replace(/[\uD800-\uDBFF]$/u, "") + "...";
62
64
  }
63
65
 
@@ -106,12 +108,16 @@ export function buildGrepArgs(
106
108
 
107
109
  /** 渲染搜索结果:上游格式,按文件分组。 */
108
110
  export function renderGrepOutput(matches: readonly GrepMatch[], truncated: boolean): string {
109
- if (matches.length === 0) return "No files found";
111
+ if (matches.length === 0) {
112
+ return "No files found";
113
+ }
110
114
  const output = [`Found ${matches.length} matches${truncated ? " (more matches available)" : ""}`];
111
115
  let current = "";
112
116
  for (const match of matches) {
113
117
  if (current !== match.path) {
114
- if (current !== "") output.push("");
118
+ if (current !== "") {
119
+ output.push("");
120
+ }
115
121
  current = match.path;
116
122
  output.push(`${match.path}:`);
117
123
  }
@@ -125,7 +131,9 @@ export function renderGrepOutput(matches: readonly GrepMatch[], truncated: boole
125
131
 
126
132
  /** 搜索根:绝对路径原样,相对路径按调用 cwd 解析。 */
127
133
  export function resolveSearchRoot(path: string | undefined, cwd: string): string {
128
- if (path === undefined || path === "") return cwd;
134
+ if (path === undefined || path === "") {
135
+ return cwd;
136
+ }
129
137
  return isAbsolute(path) ? path : join(cwd, path);
130
138
  }
131
139
 
@@ -125,7 +125,9 @@ async function askSingle(
125
125
  ctx.ui,
126
126
  { signal },
127
127
  );
128
- if (result === undefined) return [];
128
+ if (result === undefined) {
129
+ return [];
130
+ }
129
131
  return result.prompted ? (result.input ? [result.input] : []) : [result.label];
130
132
  }
131
133
 
@@ -62,21 +62,27 @@ export function runRipgrep<R>(
62
62
  const items: R[] = [];
63
63
 
64
64
  const stop = () => {
65
- if (state.done) return;
65
+ if (state.done) {
66
+ return;
67
+ }
66
68
  state.done = true;
67
69
  child.kill("SIGTERM");
68
70
  };
69
71
 
70
72
  /** 收尾(resolve/reject 只会发生一次,并摘掉 abort 监听)。 */
71
73
  const settle = (finish: () => void) => {
72
- if (state.settled) return;
74
+ if (state.settled) {
75
+ return;
76
+ }
73
77
  state.settled = true;
74
78
  signal?.removeEventListener("abort", onAbort);
75
79
  finish();
76
80
  };
77
81
 
78
82
  const parseLine = (line: string) => {
79
- if (state.done || line.length === 0) return;
83
+ if (state.done || line.length === 0) {
84
+ return;
85
+ }
80
86
  let item: R | undefined;
81
87
  try {
82
88
  item = options.parse(line);
@@ -86,17 +92,23 @@ export function runRipgrep<R>(
86
92
  stop();
87
93
  return;
88
94
  }
89
- if (item === undefined) return;
95
+ if (item === undefined) {
96
+ return;
97
+ }
90
98
  items.push(item);
91
- if (items.length > options.limit) {
92
- state.truncated = true;
93
- stop();
99
+ if (items.length <= options.limit) {
100
+ return;
94
101
  }
102
+
103
+ state.truncated = true;
104
+ stop();
95
105
  };
96
106
 
97
107
  child.stderr.setEncoding("utf8");
98
108
  child.stderr.on("data", (chunk: string) => {
99
- if (stderr.length < ERROR_BYTES) stderr += chunk;
109
+ if (stderr.length < ERROR_BYTES) {
110
+ stderr += chunk;
111
+ }
100
112
  });
101
113
 
102
114
  child.stdout.setEncoding("utf8");
@@ -105,7 +117,9 @@ export function runRipgrep<R>(
105
117
  const lines = pending.split("\n");
106
118
  // 末尾没有换行的部分是不完整记录,留到下一块
107
119
  pending = lines.pop() ?? "";
108
- for (const line of lines) parseLine(line);
120
+ for (const line of lines) {
121
+ parseLine(line);
122
+ }
109
123
  });
110
124
 
111
125
  child.on("error", (error) => {
@@ -148,7 +148,9 @@ export function buildTodoMarkdown(todos: readonly TodoInfo[]): string {
148
148
  */
149
149
  export function buildTodoWidgetLines(todos: readonly TodoInfo[]): string[] | undefined {
150
150
  const visible = todos;
151
- if (visible.length === 0) return undefined;
151
+ if (visible.length === 0) {
152
+ return undefined;
153
+ }
152
154
  return visible.map((t) => formatTodoLine(t));
153
155
  }
154
156
 
@@ -132,7 +132,9 @@ export function loadSessionNameConfig(settingsPath = SETTINGS_PATH): SessionName
132
132
  return undefined; // 结构不符(非 object / sessionName 非 object 等)
133
133
  }
134
134
  const sn = settings.sessionName;
135
- if (!sn) return undefined; // 未配置 sessionName
135
+ if (!sn) {
136
+ return undefined; // 未配置 sessionName
137
+ }
136
138
  return {
137
139
  provider: nonEmpty(sn.provider) ?? nonEmpty(settings.defaultProvider),
138
140
  model: nonEmpty(sn.model),
@@ -150,12 +152,20 @@ function nonEmpty(value: string | undefined): string | undefined {
150
152
 
151
153
  /** 从消息 content(字符串或分片数组)提取文本 */
152
154
  function messageText(content: unknown): string {
153
- if (typeof content === "string") return content.trim();
154
- if (!Array.isArray(content)) return "";
155
+ if (typeof content === "string") {
156
+ return content.trim();
157
+ }
158
+ if (!Array.isArray(content)) {
159
+ return "";
160
+ }
155
161
  return content
156
162
  .map((part: unknown) => {
157
- if (typeof part === "string") return part;
158
- if (isRecord(part) && typeof part.text === "string") return part.text;
163
+ if (typeof part === "string") {
164
+ return part;
165
+ }
166
+ if (isRecord(part) && typeof part.text === "string") {
167
+ return part.text;
168
+ }
159
169
  return "";
160
170
  })
161
171
  .join("")
@@ -171,11 +181,13 @@ export function extractFirstUserPrompt(
171
181
  currentPrompt: string,
172
182
  ): string | undefined {
173
183
  const trimmed = currentPrompt.trim();
174
- if (branch.length > 0) {
175
- for (const entry of branch) {
176
- if (entry.type !== "message") continue;
177
- const text = messageText(entry.message?.content);
178
- if (text) return text;
184
+ for (const entry of branch) {
185
+ if (entry.type !== "message") {
186
+ continue;
187
+ }
188
+ const text = messageText(entry.message?.content);
189
+ if (text) {
190
+ return text;
179
191
  }
180
192
  }
181
193
  return trimmed || undefined;
@@ -184,8 +196,12 @@ export function extractFirstUserPrompt(
184
196
  /** 折叠空白、限制长度;空结果返回 undefined */
185
197
  export function sanitizeName(raw: string, maxLength = DEFAULT_MAX_LENGTH): string | undefined {
186
198
  const collapsed = raw.replaceAll(/\s+/g, " ").trim();
187
- if (!collapsed) return undefined;
188
- if (collapsed.length <= maxLength) return collapsed;
199
+ if (!collapsed) {
200
+ return undefined;
201
+ }
202
+ if (collapsed.length <= maxLength) {
203
+ return collapsed;
204
+ }
189
205
  return `${collapsed.slice(0, maxLength - 1).trimEnd()}…`;
190
206
  }
191
207
 
@@ -269,7 +285,9 @@ export async function generateSessionName(
269
285
  );
270
286
  if (raw) {
271
287
  const name = sanitizeName(raw, maxLength);
272
- if (name) return name;
288
+ if (name) {
289
+ return name;
290
+ }
273
291
  }
274
292
  options.onModelFallback?.("model-error");
275
293
  } else {
@@ -309,7 +327,9 @@ export async function nameSession(
309
327
  }
310
328
  },
311
329
  });
312
- if (!name) return;
330
+ if (!name) {
331
+ return;
332
+ }
313
333
  pi.setSessionName(name);
314
334
  if (ctx.hasUI) {
315
335
  ctx.notify?.(`会话已命名为: ${name}`);
@@ -323,9 +343,13 @@ export default function sessionNameExtension(pi: ExtensionAPI) {
323
343
  // 会话切换 / reload 后捕获的 pi 会抛 stale 错误,被 catch 忽略,名字
324
344
  // 绝不会写到错误的 session。
325
345
  pi.on("before_agent_start", (event, ctx) => {
326
- if (pi.getSessionName()) return;
346
+ if (pi.getSessionName()) {
347
+ return;
348
+ }
327
349
  const text = extractFirstUserPrompt(ctx.sessionManager.getBranch(), event.prompt);
328
- if (!text) return;
350
+ if (!text) {
351
+ return;
352
+ }
329
353
  void nameSession(pi, text, {
330
354
  hasUI: ctx.hasUI,
331
355
  notify: (message, level = "info") => ctx.ui.notify(message, level),
@@ -118,8 +118,12 @@ export function discoverAgents(dir = join(getAgentDir(), "agents")): AgentConfig
118
118
 
119
119
  const agents: AgentConfig[] = [];
120
120
  for (const entry of entries) {
121
- if (!entry.name.endsWith(".md")) continue;
122
- if (!entry.isFile() && !entry.isSymbolicLink()) continue;
121
+ if (!entry.name.endsWith(".md")) {
122
+ continue;
123
+ }
124
+ if (!entry.isFile() && !entry.isSymbolicLink()) {
125
+ continue;
126
+ }
123
127
 
124
128
  const filePath = join(dir, entry.name);
125
129
  let content: string;
@@ -131,7 +135,9 @@ export function discoverAgents(dir = join(getAgentDir(), "agents")): AgentConfig
131
135
 
132
136
  const { frontmatter, body } = parseFrontmatter(content);
133
137
  const fm = parseAgentFrontmatter(frontmatter);
134
- if (!fm) continue; // missing name/description or wrong field types → not an agent
138
+ if (!fm) {
139
+ continue; // missing name/description or wrong field types → not an agent
140
+ }
135
141
 
136
142
  agents.push({
137
143
  name: fm.name,
@@ -172,7 +178,9 @@ export function loadSpawnAgentConfig(
172
178
  } catch {
173
179
  return undefined; // 结构不符(非 object / thinkingLevel 非法等)
174
180
  }
175
- if (!config.provider && !config.model && !config.thinkingLevel) return undefined;
181
+ if (!config.provider && !config.model && !config.thinkingLevel) {
182
+ return undefined;
183
+ }
176
184
 
177
185
  let fallback: SpawnAgentSettingsFallback | undefined;
178
186
  try {
@@ -225,7 +233,9 @@ export function applyAgentDefaults(
225
233
  agents: AgentConfig[],
226
234
  defaults: SpawnAgentDefaults | undefined,
227
235
  ): AgentConfig[] {
228
- if (!defaults) return agents;
236
+ if (!defaults) {
237
+ return agents;
238
+ }
229
239
  return agents.map((agent) => ({
230
240
  ...agent,
231
241
  provider: agent.provider ?? defaults.provider,
@@ -235,6 +245,8 @@ export function applyAgentDefaults(
235
245
  }
236
246
 
237
247
  export function formatAgentList(agents: AgentConfig[]): string {
238
- if (agents.length === 0) return "none";
248
+ if (agents.length === 0) {
249
+ return "none";
250
+ }
239
251
  return agents.map((a) => `${a.name}: ${a.description}`).join("; ");
240
252
  }