pi-subagents 0.8.3 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.8.5] - 2026-02-16
6
+
7
+ ### Fixed
8
+ - Async subagent execution no longer fails with "jiti not found" on machines without a global `jiti` install. The jiti resolution now tries three strategies: vanilla `jiti`, the `@mariozechner/jiti` fork, and finally resolves `@mariozechner/jiti` from pi's own installation via `process.argv[1]`. Since pi always ships the fork as a dependency, async mode now works out of the box.
9
+ - Improved the "jiti not found" error message to explain what's needed and how to fix it.
10
+
11
+ ## [0.8.4] - 2026-02-13
12
+
13
+ ### Fixed
14
+ - JSONL artifact files no longer written by default — they duplicated pi's own session files and were the sole cause of `subagent-artifacts` directories growing to 10+ GB. Changed `includeJsonl` default from `true` to `false`. `_output.md` and `_meta.json` still capture the useful data.
15
+ - Artifact cleanup now covers session-based directories, not just the temp dir. Previously `cleanupOldArtifacts` only ran on `os.tmpdir()/pi-subagent-artifacts` at startup, while sync runs (the common path) wrote to `<session-dir>/subagent-artifacts/` which was never cleaned. Now scans all `~/.pi/agent/sessions/*/subagent-artifacts/` dirs on startup and cleans the current session's artifacts dir on session lifecycle events.
16
+ - JSONL writer now enforces a 50 MB size cap (`maxBytes` on `JsonlWriterDeps`) as defense-in-depth for users who opt into JSONL. Silently stops writing at the cap without pausing the source stream, so the progress tracker keeps working.
17
+
5
18
  ## [0.8.3] - 2026-02-11
6
19
 
7
20
  ### Added
package/artifacts.ts CHANGED
@@ -69,3 +69,24 @@ export function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {
69
69
 
70
70
  fs.writeFileSync(markerPath, String(now));
71
71
  }
72
+
73
+ export function cleanupAllArtifactDirs(maxAgeDays: number): void {
74
+ cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);
75
+
76
+ const sessionsBase = path.join(os.homedir(), ".pi", "agent", "sessions");
77
+ if (!fs.existsSync(sessionsBase)) return;
78
+
79
+ let dirs: string[];
80
+ try {
81
+ dirs = fs.readdirSync(sessionsBase);
82
+ } catch {
83
+ return;
84
+ }
85
+
86
+ for (const dir of dirs) {
87
+ const artifactsDir = path.join(sessionsBase, dir, "subagent-artifacts");
88
+ try {
89
+ cleanupOldArtifacts(artifactsDir, maxAgeDays);
90
+ } catch {}
91
+ }
92
+ }
@@ -24,11 +24,22 @@ import {
24
24
 
25
25
  const require = createRequire(import.meta.url);
26
26
  const jitiCliPath: string | undefined = (() => {
27
- try {
28
- return path.join(path.dirname(require.resolve("jiti/package.json")), "lib/jiti-cli.mjs");
29
- } catch {
30
- return undefined;
27
+ const candidates: Array<() => string> = [
28
+ () => path.join(path.dirname(require.resolve("jiti/package.json")), "lib/jiti-cli.mjs"),
29
+ () => path.join(path.dirname(require.resolve("@mariozechner/jiti/package.json")), "lib/jiti-cli.mjs"),
30
+ () => {
31
+ const piEntry = fs.realpathSync(process.argv[1]);
32
+ const piRequire = createRequire(piEntry);
33
+ return path.join(path.dirname(piRequire.resolve("@mariozechner/jiti/package.json")), "lib/jiti-cli.mjs");
34
+ },
35
+ ];
36
+ for (const candidate of candidates) {
37
+ try {
38
+ const p = candidate();
39
+ if (fs.existsSync(p)) return p;
40
+ } catch {}
31
41
  }
42
+ return undefined;
32
43
  })();
33
44
 
34
45
  export interface AsyncExecutionContext {
package/index.ts CHANGED
@@ -22,7 +22,7 @@ import { type AgentConfig, type AgentScope, discoverAgents, discoverAgentsAll }
22
22
  import { resolveExecutionAgentScope } from "./agent-scope.js";
23
23
  import { cleanupOldChainDirs, getStepAgents, isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep } from "./settings.js";
24
24
  import { ChainClarifyComponent, type ChainClarifyResult, type ModelInfo } from "./chain-clarify.js";
25
- import { cleanupOldArtifacts, getArtifactsDir } from "./artifacts.js";
25
+ import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "./artifacts.js";
26
26
  import {
27
27
  type AgentProgress,
28
28
  type ArtifactConfig,
@@ -78,7 +78,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
78
78
  const asyncByDefault = config.asyncByDefault === true;
79
79
 
80
80
  const tempArtifactsDir = getArtifactsDir(null);
81
- cleanupOldArtifacts(tempArtifactsDir, DEFAULT_ARTIFACT_CONFIG.cleanupDays);
81
+ cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
82
82
  let baseCwd = process.cwd();
83
83
  let currentSessionId: string | null = null;
84
84
  const asyncJobs = new Map<string, AsyncJobState>();
@@ -335,7 +335,7 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
335
335
  if (effectiveAsync) {
336
336
  if (!isAsyncAvailable()) {
337
337
  return {
338
- content: [{ type: "text", text: "jiti not found" }],
338
+ content: [{ type: "text", text: "Async mode requires jiti for TypeScript execution but it could not be found. Install globally: npm install -g jiti" }],
339
339
  isError: true,
340
340
  details: { mode: "single" as const, results: [] },
341
341
  };
@@ -1143,9 +1143,19 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
1143
1143
  }
1144
1144
  });
1145
1145
 
1146
+ const cleanupSessionArtifacts = (ctx: ExtensionContext) => {
1147
+ try {
1148
+ const sessionFile = ctx.sessionManager.getSessionFile();
1149
+ if (sessionFile) {
1150
+ cleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);
1151
+ }
1152
+ } catch {}
1153
+ };
1154
+
1146
1155
  pi.on("session_start", (_event, ctx) => {
1147
1156
  baseCwd = ctx.cwd;
1148
1157
  currentSessionId = ctx.sessionManager.getSessionFile() ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1158
+ cleanupSessionArtifacts(ctx);
1149
1159
  for (const timer of cleanupTimers.values()) clearTimeout(timer);
1150
1160
  cleanupTimers.clear();
1151
1161
  asyncJobs.clear();
@@ -1158,6 +1168,7 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
1158
1168
  pi.on("session_switch", (_event, ctx) => {
1159
1169
  baseCwd = ctx.cwd;
1160
1170
  currentSessionId = ctx.sessionManager.getSessionFile() ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1171
+ cleanupSessionArtifacts(ctx);
1161
1172
  for (const timer of cleanupTimers.values()) clearTimeout(timer);
1162
1173
  cleanupTimers.clear();
1163
1174
  asyncJobs.clear();
@@ -1170,6 +1181,7 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
1170
1181
  pi.on("session_branch", (_event, ctx) => {
1171
1182
  baseCwd = ctx.cwd;
1172
1183
  currentSessionId = ctx.sessionManager.getSessionFile() ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1184
+ cleanupSessionArtifacts(ctx);
1173
1185
  for (const timer of cleanupTimers.values()) clearTimeout(timer);
1174
1186
  cleanupTimers.clear();
1175
1187
  asyncJobs.clear();
package/jsonl-writer.ts CHANGED
@@ -11,8 +11,11 @@ export interface JsonlWriteStream {
11
11
  end(callback?: () => void): void;
12
12
  }
13
13
 
14
+ const DEFAULT_MAX_JSONL_BYTES = 50 * 1024 * 1024;
15
+
14
16
  export interface JsonlWriterDeps {
15
17
  createWriteStream?: (filePath: string) => JsonlWriteStream;
18
+ maxBytes?: number;
16
19
  }
17
20
 
18
21
  export interface JsonlWriter {
@@ -45,12 +48,18 @@ export function createJsonlWriter(
45
48
 
46
49
  let backpressured = false;
47
50
  let closed = false;
51
+ let bytesWritten = 0;
52
+ const maxBytes = deps.maxBytes ?? DEFAULT_MAX_JSONL_BYTES;
48
53
 
49
54
  return {
50
55
  writeLine(line: string) {
51
56
  if (!stream || closed || !line.trim()) return;
57
+ const chunk = `${line}\n`;
58
+ const chunkBytes = Buffer.byteLength(chunk, "utf-8");
59
+ if (bytesWritten + chunkBytes > maxBytes) return;
52
60
  try {
53
- const ok = stream.write(`${line}\n`);
61
+ const ok = stream.write(chunk);
62
+ bytesWritten += chunkBytes;
54
63
  if (!ok && !backpressured) {
55
64
  backpressured = true;
56
65
  source.pause();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
package/types.ts CHANGED
@@ -232,7 +232,7 @@ export const DEFAULT_ARTIFACT_CONFIG: ArtifactConfig = {
232
232
  enabled: true,
233
233
  includeInput: true,
234
234
  includeOutput: true,
235
- includeJsonl: true,
235
+ includeJsonl: false,
236
236
  includeMetadata: true,
237
237
  cleanupDays: 7,
238
238
  };