@gavin-hu/my-pi-agent 0.14.0 → 0.15.0

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 (222) hide show
  1. package/README.md +55 -33
  2. package/README.zh-CN.md +44 -27
  3. package/extensions/bytefly/README.md +193 -140
  4. package/extensions/bytefly/block/component.ts +7 -15
  5. package/extensions/bytefly/block/register.ts +6 -1
  6. package/extensions/bytefly/doctor/register.ts +24 -5
  7. package/extensions/bytefly/doctor/tools.ts +4 -0
  8. package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
  9. package/extensions/bytefly/footer/config.ts +10 -6
  10. package/extensions/bytefly/footer/price/price.ts +5 -3
  11. package/extensions/bytefly/footer/price/rates.ts +63 -26
  12. package/extensions/bytefly/footer/render/status.ts +3 -3
  13. package/extensions/bytefly/header/component.ts +3 -2
  14. package/extensions/bytefly/index.ts +3 -3
  15. package/extensions/bytefly/notice/component.ts +8 -3
  16. package/extensions/bytefly/notify/config.ts +6 -11
  17. package/extensions/bytefly/notify/dispatch.ts +13 -5
  18. package/extensions/bytefly/panel/commands.ts +21 -1
  19. package/extensions/bytefly/panel/format.ts +5 -2
  20. package/extensions/bytefly/panel/settings-runtime.ts +26 -6
  21. package/extensions/bytefly/panel/settings.ts +41 -4
  22. package/extensions/bytefly/panel/themes.ts +32 -0
  23. package/extensions/bytefly/panel/tui/editing.ts +77 -0
  24. package/extensions/bytefly/panel/tui/entries.ts +24 -0
  25. package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
  26. package/extensions/bytefly/panel/tui/frame.ts +121 -4
  27. package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
  28. package/extensions/bytefly/panel/tui.ts +94 -152
  29. package/extensions/bytefly/picker/discovery.ts +1 -0
  30. package/extensions/bytefly/picker/types.ts +2 -0
  31. package/extensions/bytefly/separator/component.ts +14 -4
  32. package/extensions/bytefly/update/cache.ts +58 -19
  33. package/extensions/bytefly/update/check.ts +35 -0
  34. package/extensions/bytefly/update/format.ts +76 -35
  35. package/extensions/bytefly/update/register.ts +144 -55
  36. package/extensions/bytefly/update/state.ts +4 -2
  37. package/extensions/bytefly/update/suppress.ts +66 -0
  38. package/extensions/bytefly/update/types.ts +15 -0
  39. package/extensions/check/README.md +253 -0
  40. package/extensions/check/commands.ts +128 -0
  41. package/extensions/check/config.ts +39 -0
  42. package/extensions/check/detect.ts +269 -0
  43. package/extensions/check/failures.ts +230 -0
  44. package/extensions/check/format.ts +135 -0
  45. package/extensions/check/index.ts +26 -0
  46. package/extensions/check/render.ts +185 -0
  47. package/extensions/check/runner.ts +176 -0
  48. package/extensions/check/runtime.ts +244 -0
  49. package/extensions/check/source.ts +80 -0
  50. package/extensions/check/tools.ts +173 -0
  51. package/extensions/check/tui.ts +195 -0
  52. package/extensions/check/types.ts +99 -0
  53. package/extensions/document/README.md +1 -1
  54. package/extensions/document/extract/pdf.ts +8 -16
  55. package/extensions/document/formats.ts +2 -2
  56. package/extensions/file-browse/README.md +66 -32
  57. package/extensions/file-browse/client.ts +95 -32
  58. package/extensions/file-browse/config.ts +9 -9
  59. package/extensions/file-browse/git.ts +37 -47
  60. package/extensions/file-browse/html.ts +39 -58
  61. package/extensions/file-browse/markdown.ts +110 -92
  62. package/extensions/file-browse/raw-html.ts +478 -0
  63. package/extensions/file-browse/router.ts +32 -70
  64. package/extensions/file-browse/server.ts +18 -56
  65. package/extensions/file-browse/urls.ts +80 -0
  66. package/extensions/goal/README.md +103 -47
  67. package/extensions/goal/clarify-parse.ts +4 -9
  68. package/extensions/goal/clarify.ts +27 -4
  69. package/extensions/goal/commands.ts +24 -11
  70. package/extensions/goal/format.ts +41 -10
  71. package/extensions/goal/index.ts +39 -16
  72. package/extensions/goal/plan-route.ts +51 -0
  73. package/extensions/goal/schema.ts +6 -23
  74. package/extensions/goal/state.ts +39 -57
  75. package/extensions/goal/tools.ts +26 -20
  76. package/extensions/goal/types.ts +11 -1
  77. package/extensions/job/README.md +20 -6
  78. package/extensions/job/format.ts +18 -68
  79. package/extensions/job/index.ts +26 -41
  80. package/extensions/job/log-writer.ts +208 -0
  81. package/extensions/job/logs.ts +3 -2
  82. package/extensions/job/process.ts +6 -99
  83. package/extensions/job/runtime.ts +42 -181
  84. package/extensions/job/schema.ts +1 -4
  85. package/extensions/job/tools.ts +1 -15
  86. package/extensions/job/tui.ts +117 -307
  87. package/extensions/job/ui.ts +7 -33
  88. package/extensions/memory/README.md +1 -1
  89. package/extensions/memory/errors.ts +5 -6
  90. package/extensions/memory/tui.ts +14 -24
  91. package/extensions/plan/README.md +94 -18
  92. package/extensions/plan/commands.ts +9 -5
  93. package/extensions/plan/format.ts +87 -0
  94. package/extensions/plan/index.ts +31 -15
  95. package/extensions/plan/plans.ts +2 -3
  96. package/extensions/plan/policy.ts +0 -6
  97. package/extensions/plan/runtime.ts +4 -2
  98. package/extensions/plan/steps.ts +37 -8
  99. package/extensions/plan/todos.ts +44 -3
  100. package/extensions/plan/tools/enter.ts +94 -0
  101. package/extensions/plan/tools/exit.ts +296 -0
  102. package/extensions/plan/tools/nested.ts +21 -0
  103. package/extensions/plan/tools/steps.ts +136 -0
  104. package/extensions/plan/tools/todo-seed.ts +76 -0
  105. package/extensions/plan/tools/write.ts +127 -0
  106. package/extensions/plan/tools.ts +18 -703
  107. package/extensions/plan/web-client.ts +6 -22
  108. package/extensions/plan/web-page.ts +19 -30
  109. package/extensions/plan/web-router.ts +34 -71
  110. package/extensions/plan/web.ts +13 -40
  111. package/extensions/question/answers.ts +3 -8
  112. package/extensions/question/tui.ts +6 -6
  113. package/extensions/rewind/README.md +95 -22
  114. package/extensions/rewind/checklist.ts +185 -0
  115. package/extensions/rewind/commands.ts +8 -0
  116. package/extensions/rewind/config.ts +3 -11
  117. package/extensions/rewind/format.ts +220 -2
  118. package/extensions/rewind/git.ts +74 -7
  119. package/extensions/rewind/hunk-picker.ts +114 -0
  120. package/extensions/rewind/patch.ts +88 -0
  121. package/extensions/rewind/path-picker.ts +67 -100
  122. package/extensions/rewind/paths.ts +93 -19
  123. package/extensions/rewind/restore.ts +53 -10
  124. package/extensions/rewind/review-tui.ts +320 -0
  125. package/extensions/rewind/review.ts +397 -0
  126. package/extensions/rewind/rewind-tui.ts +4 -3
  127. package/extensions/rewind/runtime.ts +94 -17
  128. package/extensions/subagent/README.md +61 -6
  129. package/extensions/subagent/background-tui.ts +208 -0
  130. package/extensions/subagent/background.ts +470 -0
  131. package/extensions/subagent/commands.ts +80 -0
  132. package/extensions/subagent/config.ts +4 -0
  133. package/extensions/subagent/format.ts +24 -1
  134. package/extensions/subagent/index.ts +91 -2
  135. package/extensions/subagent/render.ts +13 -0
  136. package/extensions/subagent/run.ts +17 -16
  137. package/extensions/subagent/schema.ts +12 -0
  138. package/extensions/subagent/types.ts +11 -0
  139. package/extensions/subagent/ui.ts +50 -0
  140. package/extensions/todo/README.md +18 -13
  141. package/extensions/todo/format.ts +3 -2
  142. package/extensions/todo/runtime.ts +3 -2
  143. package/extensions/todo/schema.ts +8 -84
  144. package/extensions/todo/state.ts +5 -46
  145. package/extensions/todo/tools.ts +2 -1
  146. package/extensions/todo/types.ts +6 -15
  147. package/extensions/watch/README.md +14 -6
  148. package/extensions/watch/child.ts +150 -0
  149. package/extensions/watch/format.ts +8 -48
  150. package/extensions/watch/index.ts +42 -58
  151. package/extensions/watch/runtime.ts +49 -128
  152. package/extensions/watch/schema.ts +1 -4
  153. package/extensions/watch/tools.ts +1 -11
  154. package/extensions/watch/tui.ts +103 -273
  155. package/extensions/watch/ui.ts +5 -32
  156. package/extensions/web-access/README.md +1 -1
  157. package/extensions/web-access/fetch/page.ts +1 -1
  158. package/extensions/web-access/fetch/tool.ts +6 -2
  159. package/extensions/web-access/search/search.ts +2 -5
  160. package/extensions/wechat/README.md +1 -1
  161. package/extensions/wechat/client.ts +23 -0
  162. package/extensions/wechat/commands.ts +57 -42
  163. package/extensions/wechat/deliver.ts +5 -9
  164. package/extensions/wechat/errors.ts +3 -6
  165. package/extensions/wechat/inbound.ts +2 -5
  166. package/extensions/wechat/index.ts +2 -8
  167. package/extensions/wechat/outbound.ts +2 -4
  168. package/extensions/wechat/peer-worker.ts +3 -8
  169. package/extensions/wechat/poll.ts +10 -9
  170. package/extensions/wechat/presence.ts +35 -62
  171. package/extensions/wechat/reply-pipeline.ts +3 -1
  172. package/extensions/wechat/status-screen.ts +1 -1
  173. package/extensions/wechat/timer.ts +57 -0
  174. package/extensions/worktree/config.ts +8 -20
  175. package/extensions/worktree/enter.ts +7 -12
  176. package/extensions/worktree/git.ts +38 -69
  177. package/extensions/worktree/index.ts +5 -4
  178. package/extensions/worktree/registry.ts +7 -7
  179. package/extensions/worktree/root-tools.ts +2 -2
  180. package/extensions/worktree/status.ts +3 -2
  181. package/lib/README.md +22 -8
  182. package/lib/config.ts +50 -3
  183. package/lib/errors.ts +12 -0
  184. package/lib/format.ts +50 -0
  185. package/lib/git/read.ts +8 -0
  186. package/lib/goal-state.ts +23 -8
  187. package/lib/http/headers.ts +54 -0
  188. package/lib/http/index.ts +13 -0
  189. package/lib/http/response.ts +46 -0
  190. package/lib/http/router.ts +52 -0
  191. package/lib/http/server.ts +91 -0
  192. package/lib/lines.ts +77 -0
  193. package/lib/pdf.ts +82 -0
  194. package/lib/plan-entry.ts +71 -0
  195. package/lib/plan-state.ts +34 -0
  196. package/lib/report.ts +99 -0
  197. package/lib/requirements.ts +25 -19
  198. package/lib/spawn.ts +170 -0
  199. package/lib/todo-state.ts +110 -23
  200. package/lib/tool-names.ts +7 -0
  201. package/lib/tool-result.ts +33 -0
  202. package/lib/tui/README.md +68 -3
  203. package/lib/tui/chrome.ts +28 -4
  204. package/lib/tui/columns.ts +6 -1
  205. package/lib/tui/confirm.ts +65 -0
  206. package/lib/tui/dock-screen.ts +410 -0
  207. package/lib/tui/glyphs.ts +2 -1
  208. package/lib/tui/indent.ts +1 -1
  209. package/lib/tui/index.ts +2 -0
  210. package/lib/tui/rows.ts +21 -0
  211. package/lib/tui/status-controller.ts +35 -1
  212. package/lib/tui/tool.ts +59 -1
  213. package/lib/web/components.ts +20 -1
  214. package/lib/web/markup.ts +32 -0
  215. package/lib/web/runtime.ts +50 -0
  216. package/package.json +22 -5
  217. package/skills/init/SKILL.md +165 -0
  218. package/skills/init/references/agents-md-quality.md +103 -0
  219. package/skills/init/references/interview-questions.md +81 -0
  220. package/skills/refactor/SKILL.md +141 -0
  221. package/extensions/watch/process.ts +0 -63
  222. package/extensions/web-access/fetch/pdf.ts +0 -45
@@ -12,17 +12,18 @@ import { closeSync, openSync, readSync, statSync } from "node:fs";
12
12
  import { isAbsolute, resolve } from "node:path";
13
13
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
14
14
  import { resolveEffectiveCwd } from "../../lib/env.ts";
15
+ import { LineSplitter } from "../../lib/lines.ts";
16
+ import type { KillTreeFn, SpawnFn, SpawnedProcess } from "../../lib/spawn.ts";
17
+ import { createChildSupervisor } from "./child.ts";
15
18
  import { DEFAULT_CONFIG, loadConfig, type WatchConfig } from "./config.ts";
16
- import { compileRegex, sanitizeWatchLine, sourceLabel } from "./format.ts";
17
- import { defaultKillTree, defaultSpawn, type KillTreeFn, type SpawnFn, type SpawnedProcess } from "./process.ts";
19
+ import { sanitizeUntrustedLine } from "../../lib/format.ts";
20
+ import { compileRegex, sourceLabel } from "./format.ts";
18
21
  import { createUiController, type UiController } from "./ui.ts";
19
22
  import type { PendingMatch, WatchMatch, WatchRecord, WatchSource } from "./types.ts";
20
23
 
21
24
  /** Largest slice read from a file per tick. */
22
25
  const MAX_CHUNK = 1024 * 1024;
23
- /** A line without a newline is flushed once it grows past this. */
24
- const MAX_LINE_BYTES = 1024 * 1024;
25
- /** Grace period before a stopped command is escalated to SIGKILL. */
26
+ /** Grace before a stopped command escalates to SIGKILL; `child.ts` defaults the same. */
26
27
  const KILL_GRACE_MS = 2000;
27
28
 
28
29
  /** Internal watcher state: the public record plus non-serializable fields. */
@@ -48,7 +49,7 @@ interface Internal {
48
49
  regex: RegExp;
49
50
  excludes: RegExp[];
50
51
  offset: number;
51
- pending: Buffer;
52
+ lines: LineSplitter;
52
53
  rawLines: string[];
53
54
  matches: WatchMatch[];
54
55
  child?: SpawnedProcess;
@@ -103,8 +104,11 @@ export interface WatchRuntimeOptions {
103
104
  }
104
105
 
105
106
  export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRuntime {
106
- const spawnFn = options.spawn ?? defaultSpawn;
107
- const killTree = options.killTree ?? defaultKillTree;
107
+ const children = createChildSupervisor({
108
+ spawn: options.spawn,
109
+ killTree: options.killTree,
110
+ graceMs: options.killGraceMs,
111
+ });
108
112
  const now = options.now ?? (() => Date.now());
109
113
  const fixedConfig = options.config;
110
114
  const autoClock = options.autoClock ?? true;
@@ -208,7 +212,7 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
208
212
  for (const exclude of w.excludes) if (exclude.test(line)) return false;
209
213
  if (!w.regex.test(line)) return false;
210
214
 
211
- const clean = sanitizeWatchLine(line);
215
+ const clean = sanitizeUntrustedLine(line);
212
216
  w.matches.push({ seq: ++w.seq, line: clean, at: time, seen: false });
213
217
  if (w.matches.length > config.maxMatches) {
214
218
  w.matches.splice(0, w.matches.length - config.maxMatches);
@@ -224,85 +228,16 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
224
228
  };
225
229
 
226
230
  const consume = (w: Internal, chunk: Buffer, time: number): void => {
227
- const combined = w.pending.length > 0 ? Buffer.concat([w.pending, chunk]) : chunk;
228
- let start = 0;
229
231
  let matched = false;
230
- while (true) {
231
- const newline = combined.indexOf(0x0a, start);
232
- if (newline < 0) break;
233
- if (emit(w, combined.subarray(start, newline).toString("utf-8"), time)) matched = true;
234
- start = newline + 1;
235
- }
236
- let rest = combined.subarray(start);
237
- if (rest.length > MAX_LINE_BYTES) {
238
- if (emit(w, rest.toString("utf-8"), time)) matched = true;
239
- rest = Buffer.alloc(0);
232
+ for (const line of w.lines.push(chunk)) {
233
+ if (emit(w, line, time)) matched = true;
240
234
  }
241
- w.pending = Buffer.from(rest);
242
235
  if (matched && w.wake) fireOnMatch();
243
236
  };
244
237
 
238
+ /** A source that ended mid-line still reports that line. */
245
239
  const flushPending = (w: Internal, time: number): void => {
246
- if (w.pending.length === 0) return;
247
- const rest = w.pending.toString("utf-8");
248
- w.pending = Buffer.alloc(0);
249
- emit(w, rest, time);
250
- };
251
-
252
- const clearKillTimer = (w: Internal): void => {
253
- if (w.killTimer) {
254
- clearTimeout(w.killTimer);
255
- w.killTimer = undefined;
256
- }
257
- };
258
-
259
- const killChild = (w: Internal, signal: "SIGTERM" | "SIGKILL"): void => {
260
- const pid = w.pid;
261
- try {
262
- if (pid !== undefined) killTree(pid, signal, signal === "SIGKILL");
263
- else w.child?.kill(signal);
264
- } catch {
265
- // Best-effort: the child may already be gone.
266
- }
267
- };
268
-
269
- /** SIGTERM a command, escalating to SIGKILL when it has not closed in time. */
270
- const terminateChild = (w: Internal): void => {
271
- clearKillTimer(w);
272
- killChild(w, "SIGTERM");
273
- if (w.pid === undefined && !w.child) return;
274
- w.killTimer = setTimeout(() => {
275
- w.killTimer = undefined;
276
- if (w.child) killChild(w, "SIGKILL");
277
- }, killGraceMs);
278
- w.killTimer.unref?.();
279
- };
280
-
281
- /** Resolve when every command has closed, or after `ms`, whichever comes first. */
282
- const waitForChildren = (watches: Internal[], ms: number): Promise<void> => {
283
- const alive = watches.filter((w) => w.child !== undefined);
284
- if (alive.length === 0) return Promise.resolve();
285
- return new Promise((resolve) => {
286
- let remaining = alive.length;
287
- let timer: ReturnType<typeof setTimeout> | undefined;
288
- const settle = (): void => {
289
- if (--remaining > 0) return;
290
- if (timer !== undefined) clearTimeout(timer);
291
- resolve();
292
- };
293
- for (const w of alive) {
294
- let done = false;
295
- const once = (): void => {
296
- if (done) return;
297
- done = true;
298
- settle();
299
- };
300
- w.child?.on("close", once);
301
- w.child?.on("error", once);
302
- }
303
- timer = setTimeout(resolve, ms);
304
- timer.unref?.();
305
- });
240
+ for (const line of w.lines.flush()) emit(w, line, time);
306
241
  };
307
242
 
308
243
  const pollFile = (w: Internal, time: number): void => {
@@ -317,7 +252,7 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
317
252
  }
318
253
  if (size < w.offset) {
319
254
  w.offset = 0;
320
- w.pending = Buffer.alloc(0);
255
+ w.lines.reset();
321
256
  }
322
257
  if (w.offset >= size) return;
323
258
 
@@ -344,42 +279,28 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
344
279
  };
345
280
 
346
281
  const startCommand = (w: Internal): void => {
347
- let child: SpawnedProcess;
348
- try {
349
- child = spawnFn(w.source.command ?? "", { cwd: w.source.cwd ?? process.cwd() });
350
- } catch (error) {
351
- w.status = "error";
352
- w.error = (error as Error).message;
353
- w.endedAt = now();
354
- return;
355
- }
356
- w.child = child;
357
- w.pid = child.pid;
358
- const onData = (chunk: Buffer): void => consume(w, chunk, now());
359
- child.stdout?.on("data", onData);
360
- child.stderr?.on("data", onData);
361
- child.on("close", (code, signal) => {
362
- clearKillTimer(w);
363
- w.child = undefined;
364
- if (w.status !== "running") return;
365
- flushPending(w, now());
366
- w.status = "exited";
367
- w.exitCode = code;
368
- w.signal = signal;
369
- w.endedAt = now();
370
- ui.paint();
371
- if (runningCount() === 0) stopClock();
372
- });
373
- child.on("error", (error) => {
374
- clearKillTimer(w);
375
- w.child = undefined;
376
- if (w.status !== "running") return;
377
- w.status = "error";
378
- w.error = error.message;
379
- w.endedAt = now();
380
- ui.paint();
381
- if (runningCount() === 0) stopClock();
282
+ const error = children.start(w, w.source.command ?? "", w.source.cwd ?? process.cwd(), {
283
+ onData: (chunk) => consume(w, chunk, now()),
284
+ onSettled: (outcome) => {
285
+ if (w.status !== "running") return;
286
+ if (outcome.kind === "closed") {
287
+ flushPending(w, now());
288
+ w.status = "exited";
289
+ w.exitCode = outcome.code;
290
+ w.signal = outcome.signal;
291
+ } else {
292
+ w.status = "error";
293
+ w.error = outcome.message;
294
+ }
295
+ w.endedAt = now();
296
+ ui.paint();
297
+ if (runningCount() === 0) stopClock();
298
+ },
382
299
  });
300
+ if (error === undefined) return;
301
+ w.status = "error";
302
+ w.error = error;
303
+ w.endedAt = now();
383
304
  };
384
305
 
385
306
  const tick = (): void => {
@@ -443,7 +364,7 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
443
364
  regex,
444
365
  excludes,
445
366
  offset,
446
- pending: Buffer.alloc(0),
367
+ lines: new LineSplitter(),
447
368
  rawLines: [],
448
369
  matches: [],
449
370
  notified: false,
@@ -476,13 +397,13 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
476
397
 
477
398
  const matches = (id: string, lines?: number) => tail(id, lines, (w) => w.matches.map((m) => m.line));
478
399
  const logs = (id: string, lines?: number) =>
479
- tail(id, lines, (w) => w.rawLines.map((line) => sanitizeWatchLine(line)));
400
+ tail(id, lines, (w) => w.rawLines.map((line) => sanitizeUntrustedLine(line)));
480
401
 
481
402
  const stop = (id: string): boolean => {
482
403
  const w = watches.get(id);
483
404
  if (!w || w.status !== "running") return false;
484
405
  flushPending(w, now());
485
- terminateChild(w);
406
+ children.terminate(w);
486
407
  w.status = "stopped";
487
408
  w.endedAt = now();
488
409
  ui.paint();
@@ -535,8 +456,8 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
535
456
  for (const w of watches.values()) {
536
457
  if (w.status !== "running") continue;
537
458
  w.status = "stopped";
538
- clearKillTimer(w);
539
- killChild(w, "SIGKILL");
459
+ children.clearTimer(w);
460
+ children.kill(w, "SIGKILL");
540
461
  w.child = undefined;
541
462
  }
542
463
  watches.clear();
@@ -554,13 +475,13 @@ export function createWatchRuntime(options: WatchRuntimeOptions = {}): WatchRunt
554
475
  flushPending(w, now());
555
476
  w.status = "stopped";
556
477
  w.endedAt = now();
557
- clearKillTimer(w);
558
- killChild(w, "SIGTERM");
478
+ children.clearTimer(w);
479
+ children.kill(w, "SIGTERM");
559
480
  }
560
- await waitForChildren(running, killGraceMs);
481
+ await children.wait(running, killGraceMs);
561
482
  for (const w of running) {
562
- clearKillTimer(w);
563
- if (w.child) killChild(w, "SIGKILL");
483
+ children.clearTimer(w);
484
+ if (w.child) children.kill(w, "SIGKILL");
564
485
  }
565
486
  watches.clear();
566
487
  ui.detach();
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { StringEnum } from "@earendil-works/pi-ai";
11
11
  import { Type, type Static } from "typebox";
12
+ import { optionalString } from "../../lib/config.ts";
12
13
  import { WATCH_ACTIONS, type WatchAction } from "./types.ts";
13
14
 
14
15
  /** Longest accepted label. */
@@ -79,10 +80,6 @@ export type WatchCall =
79
80
  | { action: "stop"; id: string }
80
81
  | { action: "clear"; id?: string; all?: boolean };
81
82
 
82
- function optionalString(value: unknown): string | undefined {
83
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
84
- }
85
-
86
83
  function assertRegex(value: string, field: string): void {
87
84
  if (value.length > MAX_PATTERN) throw new Error(`${field} is longer than ${MAX_PATTERN} characters.`);
88
85
  try {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
11
11
  import { sanitizeLine } from "../../lib/format.ts";
12
+ import { errorResult } from "../../lib/tool-result.ts";
12
13
  import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
13
14
  import { rejectionLine, reuseCallText, toolCallTitle } from "../../lib/tui/tool.ts";
14
15
  import { SEPARATORS } from "../../lib/tui/glyphs.ts";
@@ -32,17 +33,6 @@ export const TOOL_NAME = "watch";
32
33
  /** Collapsed transcript rows before the result is truncated. */
33
34
  const RESULT_ROWS = 8;
34
35
 
35
- function errorResult(
36
- action: WatchDetails["action"],
37
- message: string,
38
- ): { content: Array<{ type: "text"; text: string }>; details: WatchDetails; isError: true } {
39
- return {
40
- content: [{ type: "text", text: `Error: ${message}` }],
41
- details: { action, error: message },
42
- isError: true,
43
- };
44
- }
45
-
46
36
  const GUIDELINES = [
47
37
  "Use `watch add` to tail a log file or a long-running command and surface lines matching a pattern.",
48
38
  "Read matches with `watch matches <id>`; use `watch logs <id>` for the raw output tail.",