@saifymatteo/pi-extensions 0.1.3 → 0.1.5

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 (65) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +2 -1
  3. package/packages/pi-bg-bash/AGENTS.md +29 -0
  4. package/packages/pi-bg-bash/CHANGELOG.md +25 -0
  5. package/packages/pi-bg-bash/package.json +2 -1
  6. package/packages/pi-bg-bash/src/index.ts +189 -99
  7. package/packages/pi-bg-bash/src/lib/output.ts +31 -0
  8. package/packages/pi-bg-bash/src/lib/panel.ts +155 -0
  9. package/packages/pi-bg-bash/src/lib/process-control.ts +221 -0
  10. package/packages/pi-bg-bash/src/lib/windows-console.ts +169 -0
  11. package/packages/pi-bg-bash/test/job-controls.test.mjs +347 -0
  12. package/packages/pi-bg-bash/test/native-process-control.test.mjs +126 -0
  13. package/packages/pi-bg-bash/test/output.test.mjs +28 -0
  14. package/packages/pi-bg-bash/test/panel.test.mjs +146 -0
  15. package/packages/pi-bg-bash/test/process-control.test.mjs +300 -0
  16. package/packages/pi-bg-bash/test/windows-console.test.mjs +221 -0
  17. package/packages/pi-claude-usage/CHANGELOG.md +18 -0
  18. package/packages/pi-claude-usage/package.json +2 -1
  19. package/packages/pi-clipboard-image/CHANGELOG.md +18 -0
  20. package/packages/pi-clipboard-image/package.json +2 -1
  21. package/packages/pi-codeblock-borders/CHANGELOG.md +18 -0
  22. package/packages/pi-codeblock-borders/package.json +2 -1
  23. package/packages/pi-codegraph/CHANGELOG.md +25 -0
  24. package/packages/pi-codegraph/package.json +2 -1
  25. package/packages/pi-codegraph/src/index.ts +67 -27
  26. package/packages/pi-compact-tools/CHANGELOG.md +18 -0
  27. package/packages/pi-compact-tools/package.json +2 -1
  28. package/packages/pi-decisions/CHANGELOG.md +18 -0
  29. package/packages/pi-decisions/package.json +2 -1
  30. package/packages/pi-load-pi-dotenv/CHANGELOG.md +18 -0
  31. package/packages/pi-load-pi-dotenv/package.json +2 -1
  32. package/packages/pi-model-go/CHANGELOG.md +18 -0
  33. package/packages/pi-model-go/package.json +2 -1
  34. package/packages/pi-prestart-working/CHANGELOG.md +18 -0
  35. package/packages/pi-prestart-working/package.json +2 -1
  36. package/packages/pi-root-guard/CHANGELOG.md +18 -0
  37. package/packages/pi-root-guard/package.json +2 -1
  38. package/packages/pi-session-finder/CHANGELOG.md +18 -0
  39. package/packages/pi-session-finder/package.json +2 -1
  40. package/packages/pi-session-id/CHANGELOG.md +18 -0
  41. package/packages/pi-session-id/package.json +2 -1
  42. package/packages/pi-session-nav/CHANGELOG.md +18 -0
  43. package/packages/pi-session-nav/package.json +2 -1
  44. package/packages/pi-session-rename/CHANGELOG.md +18 -0
  45. package/packages/pi-session-rename/package.json +2 -1
  46. package/packages/pi-session-retention/AGENTS.md +15 -0
  47. package/packages/pi-session-retention/CHANGELOG.md +25 -0
  48. package/packages/pi-session-retention/package.json +2 -1
  49. package/packages/pi-session-retention/src/index.ts +43 -4
  50. package/packages/pi-session-retention/test/startup.test.mjs +229 -0
  51. package/packages/pi-skill-usage-log/CHANGELOG.md +18 -0
  52. package/packages/pi-skill-usage-log/package.json +2 -1
  53. package/packages/pi-tool-usage-log/CHANGELOG.md +18 -0
  54. package/packages/pi-tool-usage-log/package.json +2 -1
  55. package/packages/pi-tts/CHANGELOG.md +18 -0
  56. package/packages/pi-tts/package.json +2 -1
  57. package/packages/pi-user-image-previews/CHANGELOG.md +18 -0
  58. package/packages/pi-user-image-previews/package.json +2 -1
  59. package/packages/pi-vision/CHANGELOG.md +18 -0
  60. package/packages/pi-vision/package.json +2 -1
  61. package/packages/pi-wait/CHANGELOG.md +18 -0
  62. package/packages/pi-wait/package.json +2 -1
  63. package/packages/pi-zvec/CHANGELOG.md +25 -0
  64. package/packages/pi-zvec/package.json +2 -1
  65. package/packages/pi-zvec/src/index.ts +81 -39
@@ -15,7 +15,7 @@
15
15
  // vector → semantic-only
16
16
  // hybrid/auto → positional hybrid query
17
17
  // - /zvec-init /zvec-sync /zvec-status
18
- // - session_start : CLI check (report once if missing), config setup,
18
+ // - session_start : non-blocking CLI check (report once if missing), config setup,
19
19
  // index discovery by walking up from cwd for .zvec-grep/,
20
20
  // startup sync when indexed, auto-index when not (bounded
21
21
  // by home dir / filesystem root)
@@ -55,7 +55,6 @@
55
55
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
56
56
  import { Type } from "typebox";
57
57
  import { appendFileSync, existsSync, realpathSync } from "node:fs";
58
- import { execSync } from "node:child_process";
59
58
  import { mkdir, readFile, writeFile } from "node:fs/promises";
60
59
  import { homedir } from "node:os";
61
60
  import * as path from "node:path";
@@ -248,19 +247,21 @@ function textResult(text: string) {
248
247
  // package's JS entry once and drive it with `node` directly: no shell involved,
249
248
  // every argument preserved verbatim.
250
249
  let zgJsEntry: string | null | undefined;
251
- function resolveZgJsEntry(): string | null {
250
+ async function resolveZgJsEntry(pi: ExtensionAPI, signal?: AbortSignal): Promise<string | null> {
251
+ signal?.throwIfAborted();
252
252
  if (zgJsEntry !== undefined) return zgJsEntry;
253
- zgJsEntry = null;
254
253
  try {
255
- const root = execSync("npm root -g", {
256
- encoding: "utf8",
254
+ const result = await pi.exec("cmd.exe", ["/d", "/s", "/c", "npm", "root", "-g"], {
257
255
  timeout: CLI_CHECK_TIMEOUT_MS,
258
- stdio: ["ignore", "pipe", "ignore"],
259
- }).trim();
260
- const entry = path.join(root, "@zvec", "zvec-grep", "dist", "cli", "index.js");
261
- if (existsSync(entry)) zgJsEntry = entry;
256
+ signal,
257
+ });
258
+ signal?.throwIfAborted();
259
+ const entry = path.join(result.stdout.trim(), "@zvec", "zvec-grep", "dist", "cli", "index.js");
260
+ zgJsEntry = result.code === 0 && !result.killed && existsSync(entry) ? entry : null;
262
261
  } catch {
263
- /* no npm / no global install: fall back to the shell launcher */
262
+ // Cancellation is not a missing installation and must not poison the cache.
263
+ signal?.throwIfAborted();
264
+ zgJsEntry = null;
264
265
  }
265
266
  return zgJsEntry;
266
267
  }
@@ -271,11 +272,12 @@ async function execZg(
271
272
  options: { signal?: AbortSignal; timeout?: number; cwd?: string } = {},
272
273
  ) {
273
274
  if (process.platform === "win32") {
274
- const entry = resolveZgJsEntry();
275
+ const entry = await resolveZgJsEntry(pi, options.signal);
275
276
  if (entry) {
276
277
  try {
277
278
  return await pi.exec("node", [entry, ...args], options);
278
279
  } catch {
280
+ options.signal?.throwIfAborted();
279
281
  /* node missing from PATH: fall back to the shim below */
280
282
  }
281
283
  }
@@ -369,16 +371,26 @@ function sessionIdOf(ctx: ExtensionContext): string {
369
371
  }
370
372
 
371
373
  export default function zvec(pi: ExtensionAPI) {
372
- // ── shared CLI availability check ─────────────────────────────────────
374
+ let initialization: Promise<void> | null = null;
375
+ let cliCheck: Promise<boolean> | null = null;
376
+
377
+ // Share a cancellable probe across startup and concurrent first calls.
373
378
  async function ensureCli(pi2: ExtensionAPI): Promise<boolean> {
374
379
  if (cliAvailable !== null) return cliAvailable;
375
- try {
376
- const r = await execZg(pi2, ["version"], { timeout: CLI_CHECK_TIMEOUT_MS });
377
- cliAvailable = r.code === 0;
378
- } catch {
379
- cliAvailable = false;
380
- }
381
- return cliAvailable;
380
+ const gen = sessionGen;
381
+ const signal = runtimeAbort.signal;
382
+ cliCheck ??= (async () => {
383
+ try {
384
+ const r = await execZg(pi2, ["version"], { timeout: CLI_CHECK_TIMEOUT_MS, signal });
385
+ if (gen !== sessionGen || signal.aborted) return false;
386
+ cliAvailable = r.code === 0 && !r.killed;
387
+ } catch {
388
+ if (gen !== sessionGen || signal.aborted) return false;
389
+ cliAvailable = false;
390
+ }
391
+ return cliAvailable === true;
392
+ })();
393
+ return cliCheck;
382
394
  }
383
395
 
384
396
  // ── UI helpers: drop writes from a replaced session, never crash pi ───
@@ -517,7 +529,14 @@ export default function zvec(pi: ExtensionAPI) {
517
529
  limit: Type.Optional(Type.Integer({ description: "Maximum results per group" })),
518
530
  }),
519
531
  execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
520
- if (!(await ensureCli(pi))) return textResult(INSTALL_HINT);
532
+ const lifecycle = runtimeAbort.signal;
533
+ await initialization;
534
+ lifecycle.throwIfAborted();
535
+ signal?.throwIfAborted();
536
+ const available = await ensureCli(pi);
537
+ lifecycle.throwIfAborted();
538
+ signal?.throwIfAborted();
539
+ if (!available) return textResult(INSTALL_HINT);
521
540
  const root = params.path ? path.resolve(params.path) : (findIndexRoot(ctx.cwd) ?? ctx.cwd);
522
541
  const mode: SearchMode = params.mode ?? "auto";
523
542
  const args = buildSearchArgs({
@@ -582,6 +601,8 @@ export default function zvec(pi: ExtensionAPI) {
582
601
  const token = indexes ? ++opSeq : 0;
583
602
  if (token) currentOpToken = token;
584
603
  try {
604
+ await initialization;
605
+ if (gen !== sessionGen || runtimeAbort.signal.aborted) return;
585
606
  await command.handler(args, ctx);
586
607
  } catch (err) {
587
608
  reportError(ctx, gen, `Zvec command /${name} failed`, err instanceof Error ? (err.stack ?? err.message) : String(err));
@@ -686,29 +707,23 @@ export default function zvec(pi: ExtensionAPI) {
686
707
 
687
708
  // ── session_start: CLI check → config → auto-index / startup sync ─────
688
709
  // ── session_shutdown: kill in-flight zg children, invalidate UI writes ──
689
- pi.on("session_shutdown", () => {
710
+ pi.on("session_shutdown", async () => {
690
711
  cancelRetry();
691
712
  runtimeAbort.abort();
692
713
  sessionGen++;
693
714
  currentOpToken = 0;
694
715
  pendingSync = false;
695
716
  latestCtx = null;
717
+ const pending = initialization;
718
+ initialization = null;
719
+ cliCheck = null;
720
+ await pending?.catch(() => {});
696
721
  });
697
722
 
698
- pi.on("session_start", async (_event, ctx) => {
699
- runtimeAbort.abort();
700
- runtimeAbort = new AbortController();
701
- const gen = ++sessionGen;
702
- cancelRetry();
703
- retriesExhausted = false;
704
- state = null;
705
- latestCtx = ctx;
706
- currentOpToken = 0; // invalidate any op from a replaced session
707
- pendingSync = false;
708
- status(ctx, gen, undefined);
709
-
723
+ async function initialize(ctx: ExtensionContext, gen: number, signal: AbortSignal) {
724
+ if (gen !== sessionGen || signal.aborted) return;
710
725
  if (!(await ensureCli(pi))) {
711
- if (gen !== sessionGen) return;
726
+ if (gen !== sessionGen || signal.aborted) return;
712
727
  if (ctx.hasUI && !notifiedMissing) {
713
728
  notifiedMissing = true;
714
729
  reportError(ctx, gen, INSTALL_HINT);
@@ -716,26 +731,53 @@ export default function zvec(pi: ExtensionAPI) {
716
731
  return;
717
732
  }
718
733
 
734
+ if (gen !== sessionGen || signal.aborted) return;
719
735
  await ensureGlobalConfig();
736
+ if (gen !== sessionGen || signal.aborted) return;
720
737
  const indexedRoot = findIndexRoot(ctx.cwd);
721
738
  const projectRoot = indexedRoot ?? ctx.cwd;
722
- const globalCfg = await readConfigFile(globalConfigPath());
723
- const projectCfg = await readConfigFile(path.join(projectRoot, ".pi", "zvec.json"));
739
+ const [globalCfg, projectCfg] = await Promise.all([
740
+ readConfigFile(globalConfigPath()),
741
+ readConfigFile(path.join(projectRoot, ".pi", "zvec.json")),
742
+ ]);
724
743
  const config: ZvecConfig = {
725
744
  autoInit: boolOr(projectCfg, globalCfg, "autoInit"),
726
745
  syncOnSettle: boolOr(projectCfg, globalCfg, "syncOnSettle"),
727
746
  embedding: embeddingOr(projectCfg, globalCfg),
728
747
  };
729
- if (gen !== sessionGen) return;
748
+ if (gen !== sessionGen || signal.aborted) return;
730
749
  state = { config, projectRoot, indexed: indexedRoot !== null };
731
750
 
751
+ // A manual init/sync requested during setup already owns the writer slot.
752
+ if (currentOpToken !== 0) return;
732
753
  if (indexedRoot !== null) {
733
754
  await ensureIndexGitignore(indexedRoot);
734
755
  // Startup sync on the index owner (not cwd — cwd may be a subdir).
735
- if (gen === sessionGen && ctx.hasUI) spawnOp("sync", ctx, gen, projectRoot);
756
+ if (gen === sessionGen && !signal.aborted && currentOpToken === 0 && ctx.hasUI) spawnOp("sync", ctx, gen, projectRoot);
736
757
  } else if (config.autoInit && ctx.hasUI && !isHomeOrRoot(ctx.cwd)) {
737
758
  spawnOp("init", ctx, gen, ctx.cwd);
738
759
  }
760
+ }
761
+
762
+ pi.on("session_start", (_event, ctx) => {
763
+ runtimeAbort.abort();
764
+ runtimeAbort = new AbortController();
765
+ const signal = runtimeAbort.signal;
766
+ const gen = ++sessionGen;
767
+ cancelRetry();
768
+ retriesExhausted = false;
769
+ state = null;
770
+ latestCtx = ctx;
771
+ currentOpToken = 0;
772
+ pendingSync = false;
773
+ cliCheck = null;
774
+ status(ctx, gen, undefined);
775
+
776
+ // Startup never waits; early tool/command calls share this setup promise.
777
+ initialization = Promise.resolve().then(() => initialize(ctx, gen, signal));
778
+ void initialization.catch((error) => {
779
+ if (!signal.aborted) reportError(ctx, gen, "Zvec startup failed", error instanceof Error ? error.message : String(error));
780
+ });
739
781
  });
740
782
 
741
783
  // ── agent_settled: single-flight incremental index after each turn ────