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 +13 -0
- package/artifacts.ts +21 -0
- package/async-execution.ts +15 -4
- package/index.ts +15 -3
- package/jsonl-writer.ts +10 -1
- package/package.json +1 -1
- package/types.ts +1 -1
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
|
+
}
|
package/async-execution.ts
CHANGED
|
@@ -24,11 +24,22 @@ import {
|
|
|
24
24
|
|
|
25
25
|
const require = createRequire(import.meta.url);
|
|
26
26
|
const jitiCliPath: string | undefined = (() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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(
|
|
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
package/types.ts
CHANGED