@oh-my-pi/pi-coding-agent 3.25.0 → 3.30.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 (85) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +4 -4
  3. package/src/core/tools/complete.ts +2 -4
  4. package/src/core/tools/jtd-to-json-schema.ts +174 -196
  5. package/src/core/tools/read.ts +4 -4
  6. package/src/core/tools/task/executor.ts +146 -20
  7. package/src/core/tools/task/name-generator.ts +1544 -214
  8. package/src/core/tools/task/types.ts +19 -5
  9. package/src/core/tools/task/worker.ts +103 -13
  10. package/src/core/tools/web-fetch-handlers/academic.test.ts +239 -0
  11. package/src/core/tools/web-fetch-handlers/artifacthub.ts +210 -0
  12. package/src/core/tools/web-fetch-handlers/arxiv.ts +84 -0
  13. package/src/core/tools/web-fetch-handlers/aur.ts +171 -0
  14. package/src/core/tools/web-fetch-handlers/biorxiv.ts +136 -0
  15. package/src/core/tools/web-fetch-handlers/bluesky.ts +277 -0
  16. package/src/core/tools/web-fetch-handlers/brew.ts +173 -0
  17. package/src/core/tools/web-fetch-handlers/business.test.ts +82 -0
  18. package/src/core/tools/web-fetch-handlers/cheatsh.ts +73 -0
  19. package/src/core/tools/web-fetch-handlers/chocolatey.ts +153 -0
  20. package/src/core/tools/web-fetch-handlers/coingecko.ts +179 -0
  21. package/src/core/tools/web-fetch-handlers/crates-io.ts +123 -0
  22. package/src/core/tools/web-fetch-handlers/dev-platforms.test.ts +254 -0
  23. package/src/core/tools/web-fetch-handlers/devto.ts +173 -0
  24. package/src/core/tools/web-fetch-handlers/discogs.ts +303 -0
  25. package/src/core/tools/web-fetch-handlers/dockerhub.ts +156 -0
  26. package/src/core/tools/web-fetch-handlers/documentation.test.ts +85 -0
  27. package/src/core/tools/web-fetch-handlers/finance-media.test.ts +144 -0
  28. package/src/core/tools/web-fetch-handlers/git-hosting.test.ts +272 -0
  29. package/src/core/tools/web-fetch-handlers/github-gist.ts +64 -0
  30. package/src/core/tools/web-fetch-handlers/github.ts +424 -0
  31. package/src/core/tools/web-fetch-handlers/gitlab.ts +444 -0
  32. package/src/core/tools/web-fetch-handlers/go-pkg.ts +271 -0
  33. package/src/core/tools/web-fetch-handlers/hackage.ts +89 -0
  34. package/src/core/tools/web-fetch-handlers/hackernews.ts +208 -0
  35. package/src/core/tools/web-fetch-handlers/hex.ts +121 -0
  36. package/src/core/tools/web-fetch-handlers/huggingface.ts +385 -0
  37. package/src/core/tools/web-fetch-handlers/iacr.ts +82 -0
  38. package/src/core/tools/web-fetch-handlers/index.ts +69 -0
  39. package/src/core/tools/web-fetch-handlers/lobsters.ts +186 -0
  40. package/src/core/tools/web-fetch-handlers/mastodon.ts +302 -0
  41. package/src/core/tools/web-fetch-handlers/maven.ts +147 -0
  42. package/src/core/tools/web-fetch-handlers/mdn.ts +174 -0
  43. package/src/core/tools/web-fetch-handlers/media.test.ts +138 -0
  44. package/src/core/tools/web-fetch-handlers/metacpan.ts +247 -0
  45. package/src/core/tools/web-fetch-handlers/npm.ts +107 -0
  46. package/src/core/tools/web-fetch-handlers/nuget.ts +201 -0
  47. package/src/core/tools/web-fetch-handlers/nvd.ts +238 -0
  48. package/src/core/tools/web-fetch-handlers/opencorporates.ts +273 -0
  49. package/src/core/tools/web-fetch-handlers/openlibrary.ts +313 -0
  50. package/src/core/tools/web-fetch-handlers/osv.ts +184 -0
  51. package/src/core/tools/web-fetch-handlers/package-managers-2.test.ts +199 -0
  52. package/src/core/tools/web-fetch-handlers/package-managers.test.ts +171 -0
  53. package/src/core/tools/web-fetch-handlers/package-registries.test.ts +259 -0
  54. package/src/core/tools/web-fetch-handlers/packagist.ts +170 -0
  55. package/src/core/tools/web-fetch-handlers/pub-dev.ts +185 -0
  56. package/src/core/tools/web-fetch-handlers/pubmed.ts +174 -0
  57. package/src/core/tools/web-fetch-handlers/pypi.ts +125 -0
  58. package/src/core/tools/web-fetch-handlers/readthedocs.ts +122 -0
  59. package/src/core/tools/web-fetch-handlers/reddit.ts +100 -0
  60. package/src/core/tools/web-fetch-handlers/repology.ts +257 -0
  61. package/src/core/tools/web-fetch-handlers/research.test.ts +107 -0
  62. package/src/core/tools/web-fetch-handlers/rfc.ts +205 -0
  63. package/src/core/tools/web-fetch-handlers/rubygems.ts +112 -0
  64. package/src/core/tools/web-fetch-handlers/sec-edgar.ts +269 -0
  65. package/src/core/tools/web-fetch-handlers/security.test.ts +103 -0
  66. package/src/core/tools/web-fetch-handlers/semantic-scholar.ts +190 -0
  67. package/src/core/tools/web-fetch-handlers/social-extended.test.ts +192 -0
  68. package/src/core/tools/web-fetch-handlers/social.test.ts +259 -0
  69. package/src/core/tools/web-fetch-handlers/spotify.ts +218 -0
  70. package/src/core/tools/web-fetch-handlers/stackexchange.test.ts +120 -0
  71. package/src/core/tools/web-fetch-handlers/stackoverflow.ts +123 -0
  72. package/src/core/tools/web-fetch-handlers/standards.test.ts +122 -0
  73. package/src/core/tools/web-fetch-handlers/terraform.ts +296 -0
  74. package/src/core/tools/web-fetch-handlers/tldr.ts +47 -0
  75. package/src/core/tools/web-fetch-handlers/twitter.ts +84 -0
  76. package/src/core/tools/web-fetch-handlers/types.ts +163 -0
  77. package/src/core/tools/web-fetch-handlers/utils.ts +91 -0
  78. package/src/core/tools/web-fetch-handlers/vimeo.ts +152 -0
  79. package/src/core/tools/web-fetch-handlers/wikidata.ts +349 -0
  80. package/src/core/tools/web-fetch-handlers/wikipedia.test.ts +73 -0
  81. package/src/core/tools/web-fetch-handlers/wikipedia.ts +91 -0
  82. package/src/core/tools/web-fetch-handlers/youtube.test.ts +198 -0
  83. package/src/core/tools/web-fetch-handlers/youtube.ts +319 -0
  84. package/src/core/tools/web-fetch.ts +152 -1324
  85. package/src/utils/tools-manager.ts +110 -8
@@ -207,13 +207,39 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
207
207
  const sessionFile = subtaskSessionFile ?? options.sessionFile ?? null;
208
208
  const spawnsEnv = agent.spawns === undefined ? "" : agent.spawns === "*" ? "*" : agent.spawns.join(",");
209
209
 
210
- const worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module" });
210
+ let worker: Worker;
211
+ try {
212
+ worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module" });
213
+ } catch (err) {
214
+ return {
215
+ index,
216
+ taskId,
217
+ agent: agent.name,
218
+ agentSource: agent.source,
219
+ task,
220
+ description: options.description,
221
+ exitCode: 1,
222
+ output: "",
223
+ stderr: `Failed to create worker: ${err instanceof Error ? err.message : String(err)}`,
224
+ truncated: false,
225
+ durationMs: Date.now() - startTime,
226
+ tokens: 0,
227
+ modelOverride,
228
+ error: `Failed to create worker: ${err instanceof Error ? err.message : String(err)}`,
229
+ };
230
+ }
211
231
 
212
232
  let output = "";
213
233
  let stderr = "";
214
234
  let finalOutput = "";
215
235
  let resolved = false;
216
236
  let pendingTermination = false; // Set when shouldTerminate fires, wait for message_end
237
+ type AbortReason = "signal" | "terminate";
238
+ let abortSent = false;
239
+ let abortReason: AbortReason | undefined;
240
+ let abortTerminateTimer: ReturnType<typeof setTimeout> | undefined;
241
+ let pendingTerminationTimer: ReturnType<typeof setTimeout> | undefined;
242
+ let finalize: ((message: Extract<SubagentWorkerResponse, { type: "done" }>) => void) | null = null;
217
243
 
218
244
  // Accumulate usage incrementally from message_end events (no memory for streaming events)
219
245
  const accumulatedUsage = {
@@ -226,22 +252,55 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
226
252
  };
227
253
  let hasUsage = false;
228
254
 
229
- let abortSent = false;
230
- const requestAbort = () => {
231
- if (abortSent) return;
255
+ const clearTimers = (): void => {
256
+ if (abortTerminateTimer) clearTimeout(abortTerminateTimer);
257
+ abortTerminateTimer = undefined;
258
+ if (pendingTerminationTimer) clearTimeout(pendingTerminationTimer);
259
+ pendingTerminationTimer = undefined;
260
+ };
261
+
262
+ const requestAbort = (reason: AbortReason) => {
263
+ if (abortSent) {
264
+ if (reason === "signal" && abortReason !== "signal") {
265
+ abortReason = "signal";
266
+ }
267
+ return;
268
+ }
269
+ if (resolved) return;
232
270
  abortSent = true;
271
+ abortReason = reason;
272
+ if (pendingTerminationTimer) clearTimeout(pendingTerminationTimer);
273
+ pendingTerminationTimer = undefined;
233
274
  const abortMessage: SubagentWorkerRequest = { type: "abort" };
234
- worker.postMessage(abortMessage);
235
- setTimeout(() => {
275
+ try {
276
+ worker.postMessage(abortMessage);
277
+ } catch {
278
+ // Worker already terminated, nothing to do
279
+ }
280
+ if (abortTerminateTimer) clearTimeout(abortTerminateTimer);
281
+ abortTerminateTimer = setTimeout(() => {
236
282
  if (!resolved) {
237
- worker.terminate();
283
+ try {
284
+ worker.terminate();
285
+ } catch {
286
+ // Ignore termination errors
287
+ }
288
+ if (finalize && !resolved) {
289
+ finalize({
290
+ type: "done",
291
+ exitCode: 1,
292
+ durationMs: Date.now() - startTime,
293
+ error: reason === "signal" ? "Aborted" : "Worker terminated after tool completion",
294
+ aborted: reason === "signal",
295
+ });
296
+ }
238
297
  }
239
298
  }, 2000);
240
299
  };
241
300
 
242
301
  // Handle abort signal
243
302
  const onAbort = () => {
244
- if (!resolved) requestAbort();
303
+ if (!resolved) requestAbort("signal");
245
304
  };
246
305
  if (signal) {
247
306
  signal.addEventListener("abort", onAbort, { once: true });
@@ -349,9 +408,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
349
408
  // Don't terminate immediately - wait for message_end to get token counts
350
409
  pendingTermination = true;
351
410
  // Safety timeout in case message_end never arrives
352
- setTimeout(() => {
411
+ if (pendingTerminationTimer) clearTimeout(pendingTerminationTimer);
412
+ pendingTerminationTimer = setTimeout(() => {
353
413
  if (!resolved) {
354
- requestAbort();
414
+ requestAbort("terminate");
355
415
  }
356
416
  }, 2000);
357
417
  }
@@ -421,7 +481,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
421
481
  }
422
482
  // If pending termination, now we have tokens - terminate
423
483
  if (pendingTermination && !resolved) {
424
- requestAbort();
484
+ requestAbort("terminate");
425
485
  }
426
486
  break;
427
487
  }
@@ -469,38 +529,96 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
469
529
  }
470
530
 
471
531
  const done = await new Promise<Extract<SubagentWorkerResponse, { type: "done" }>>((resolve) => {
532
+ const cleanup = () => {
533
+ worker.removeEventListener("message", onMessage);
534
+ worker.removeEventListener("error", onError);
535
+ worker.removeEventListener("close", onClose);
536
+ worker.removeEventListener("messageerror", onMessageError);
537
+ clearTimers();
538
+ };
539
+ finalize = (message) => {
540
+ if (resolved) return;
541
+ resolved = true;
542
+ cleanup();
543
+ resolve(message);
544
+ };
472
545
  const onMessage = (event: WorkerMessageEvent<SubagentWorkerResponse>) => {
473
546
  const message = event.data;
474
547
  if (!message || resolved) return;
475
548
  if (message.type === "event") {
476
- processEvent(message.event);
549
+ try {
550
+ processEvent(message.event);
551
+ } catch (err) {
552
+ finalize?.({
553
+ type: "done",
554
+ exitCode: 1,
555
+ durationMs: Date.now() - startTime,
556
+ error: `Failed to process worker event: ${err instanceof Error ? err.message : String(err)}`,
557
+ });
558
+ }
477
559
  return;
478
560
  }
479
561
  if (message.type === "done") {
480
- resolved = true;
481
- resolve(message);
562
+ finalize?.(message);
482
563
  }
483
564
  };
484
565
  const onError = (event: WorkerErrorEvent) => {
485
- if (resolved) return;
486
- resolved = true;
487
- resolve({
566
+ finalize?.({
488
567
  type: "done",
489
568
  exitCode: 1,
490
569
  durationMs: Date.now() - startTime,
491
570
  error: event.message,
492
571
  });
493
572
  };
573
+ const onMessageError = () => {
574
+ finalize?.({
575
+ type: "done",
576
+ exitCode: 1,
577
+ durationMs: Date.now() - startTime,
578
+ error: "Worker message deserialization failed",
579
+ });
580
+ };
581
+ const onClose = () => {
582
+ // Worker terminated unexpectedly (crashed or was killed without sending done)
583
+ const abortMessage =
584
+ abortSent && abortReason === "signal"
585
+ ? "Worker terminated after abort"
586
+ : abortSent
587
+ ? "Worker terminated after tool completion"
588
+ : "Worker terminated unexpectedly";
589
+ finalize?.({
590
+ type: "done",
591
+ exitCode: 1,
592
+ durationMs: Date.now() - startTime,
593
+ error: abortMessage,
594
+ aborted: abortReason === "signal",
595
+ });
596
+ };
494
597
  worker.addEventListener("message", onMessage);
495
598
  worker.addEventListener("error", onError);
496
- worker.postMessage(startMessage);
599
+ worker.addEventListener("close", onClose);
600
+ worker.addEventListener("messageerror", onMessageError);
601
+ try {
602
+ worker.postMessage(startMessage);
603
+ } catch (err) {
604
+ finalize({
605
+ type: "done",
606
+ exitCode: 1,
607
+ durationMs: Date.now() - startTime,
608
+ error: `Failed to start worker: ${err instanceof Error ? err.message : String(err)}`,
609
+ });
610
+ }
497
611
  });
498
612
 
499
613
  // Cleanup
500
614
  if (signal) {
501
615
  signal.removeEventListener("abort", onAbort);
502
616
  }
503
- worker.terminate();
617
+ try {
618
+ worker.terminate();
619
+ } catch {
620
+ // Ignore termination errors
621
+ }
504
622
 
505
623
  let exitCode = done.exitCode;
506
624
  if (done.error) {
@@ -528,7 +646,15 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
528
646
  }
529
647
  } else {
530
648
  // Normal successful completion
531
- const completeData = lastComplete?.data ?? null;
649
+ let completeData = lastComplete?.data ?? null;
650
+ // Handle double-stringified JSON (subagent returned JSON string instead of object)
651
+ if (typeof completeData === "string" && (completeData.startsWith("{") || completeData.startsWith("["))) {
652
+ try {
653
+ completeData = JSON.parse(completeData);
654
+ } catch {
655
+ // Not valid JSON, keep as string
656
+ }
657
+ }
532
658
  try {
533
659
  rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
534
660
  } catch (err) {