pi-fast-subagent 0.9.3 → 0.9.4
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/index.ts +1 -3
- package/package.json +1 -1
- package/runner.ts +25 -35
package/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "./format.js";
|
|
28
28
|
import { defaultLoaderPool } from "./loader-pool.js";
|
|
29
29
|
import { renderSubagentCall, renderSubagentResult } from "./render.js";
|
|
30
|
-
import {
|
|
30
|
+
import { mapConcurrent, runAgent } from "./runner.js";
|
|
31
31
|
import { SubagentParams } from "./schemas.js";
|
|
32
32
|
import type { AgentRowStatus, OnUpdate, RunResult, SubagentDetails, ToolCallEntry } from "./types.js";
|
|
33
33
|
|
|
@@ -532,7 +532,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
532
532
|
|
|
533
533
|
emitParallel(true);
|
|
534
534
|
|
|
535
|
-
const parentDepth = getCurrentDepth();
|
|
536
535
|
const allResults = await mapConcurrent(expanded, concurrency, async (t, i) => {
|
|
537
536
|
parallelAgents[i]!.status = "running";
|
|
538
537
|
emitParallel(true);
|
|
@@ -556,7 +555,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
556
555
|
t.model,
|
|
557
556
|
signal,
|
|
558
557
|
agentOnUpdate,
|
|
559
|
-
parentDepth,
|
|
560
558
|
);
|
|
561
559
|
parallelAgents[i]!.status = result.exitCode === 0 ? "done" : "error";
|
|
562
560
|
parallelAgents[i]!.durMs = Date.now() - agentStart;
|
package/package.json
CHANGED
package/runner.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* via `onUpdate`, and enforces the per-agent `maxDepth` gate on nested calls.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
|
+
|
|
8
10
|
import {
|
|
9
11
|
AuthStorage,
|
|
10
12
|
createAgentSession,
|
|
@@ -32,8 +34,6 @@ export function getAuth() {
|
|
|
32
34
|
// ─── Depth gating ────────────────────────────────────────────────────────────
|
|
33
35
|
|
|
34
36
|
export const DEFAULT_MAX_DEPTH = 0;
|
|
35
|
-
export const DEPTH_ENV = "PI_FAST_SUBAGENT_DEPTH";
|
|
36
|
-
export const MAX_DEPTH_ENV = "PI_FAST_SUBAGENT_MAX_DEPTH";
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Pure helper: given the current nesting depth and the allowed max depth,
|
|
@@ -53,13 +53,22 @@ export function checkDepthGate(depth: number, maxDepth: number): { allowed: bool
|
|
|
53
53
|
return { allowed: true };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
export interface DepthState {
|
|
57
|
+
depth: number;
|
|
58
|
+
maxDepth: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const TOP_LEVEL_DEPTH: DepthState = { depth: 0, maxDepth: DEFAULT_MAX_DEPTH };
|
|
62
|
+
const _depthContext = new AsyncLocalStorage<DepthState>();
|
|
63
|
+
|
|
64
|
+
/** Read the depth/maxDepth in the current async context, or top-level defaults if none. */
|
|
65
|
+
export function getDepthState(): DepthState {
|
|
66
|
+
return _depthContext.getStore() ?? TOP_LEVEL_DEPTH;
|
|
67
|
+
}
|
|
59
68
|
|
|
60
|
-
/**
|
|
61
|
-
export function
|
|
62
|
-
return
|
|
69
|
+
/** Run `fn` with `state` set as the current async-scoped depth context. */
|
|
70
|
+
export function runWithDepth<T>(state: DepthState, fn: () => Promise<T>): Promise<T> {
|
|
71
|
+
return _depthContext.run(state, fn);
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
// ─── runAgent ────────────────────────────────────────────────────────────────
|
|
@@ -75,12 +84,11 @@ export async function runAgent(
|
|
|
75
84
|
modelOverride: string | undefined,
|
|
76
85
|
signal: AbortSignal | undefined,
|
|
77
86
|
onUpdate: OnUpdate | undefined,
|
|
78
|
-
parentDepth?: number,
|
|
79
87
|
deps: RunAgentDeps = {},
|
|
80
88
|
): Promise<RunResult> {
|
|
81
89
|
const pool = deps.loaderPool ?? defaultLoaderPool;
|
|
82
|
-
const depth
|
|
83
|
-
const gate = checkDepthGate(depth,
|
|
90
|
+
const { depth, maxDepth } = getDepthState();
|
|
91
|
+
const gate = checkDepthGate(depth, maxDepth);
|
|
84
92
|
if (!gate.allowed) {
|
|
85
93
|
return {
|
|
86
94
|
output: "",
|
|
@@ -114,8 +122,6 @@ export async function runAgent(
|
|
|
114
122
|
});
|
|
115
123
|
await allowUiPaint(coldLoader);
|
|
116
124
|
|
|
117
|
-
const createPrevEnvDepth = process.env[DEPTH_ENV];
|
|
118
|
-
process.env[DEPTH_ENV] = String(depth + 1);
|
|
119
125
|
const loaderLease = await pool.acquire(
|
|
120
126
|
cwd,
|
|
121
127
|
agentDir,
|
|
@@ -136,8 +142,6 @@ export async function runAgent(
|
|
|
136
142
|
session = created.session;
|
|
137
143
|
} catch (e) {
|
|
138
144
|
loaderLease.release();
|
|
139
|
-
if (createPrevEnvDepth === undefined) delete process.env[DEPTH_ENV];
|
|
140
|
-
else process.env[DEPTH_ENV] = createPrevEnvDepth;
|
|
141
145
|
return {
|
|
142
146
|
output: "",
|
|
143
147
|
exitCode: 1,
|
|
@@ -146,8 +150,6 @@ export async function runAgent(
|
|
|
146
150
|
usage: { input: 0, output: 0, cost: 0, turns: 0 },
|
|
147
151
|
};
|
|
148
152
|
}
|
|
149
|
-
if (createPrevEnvDepth === undefined) delete process.env[DEPTH_ENV];
|
|
150
|
-
else process.env[DEPTH_ENV] = createPrevEnvDepth;
|
|
151
153
|
|
|
152
154
|
// Resolve and apply model
|
|
153
155
|
const modelStr = modelOverride ?? agent.model;
|
|
@@ -308,17 +310,11 @@ export async function runAgent(
|
|
|
308
310
|
});
|
|
309
311
|
});
|
|
310
312
|
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const prevMaxDepth = _currentMaxDepth;
|
|
317
|
-
const maxDepth = Math.max(DEFAULT_MAX_DEPTH, agent.maxDepth ?? DEFAULT_MAX_DEPTH);
|
|
318
|
-
_currentDepth = depth + 1;
|
|
319
|
-
_currentMaxDepth = depth + maxDepth;
|
|
320
|
-
process.env[DEPTH_ENV] = String(_currentDepth);
|
|
321
|
-
process.env[MAX_DEPTH_ENV] = String(_currentMaxDepth);
|
|
313
|
+
// Per-agent maxDepth; defaults to 0 so subagents can't spawn subagents unless
|
|
314
|
+
// their frontmatter opts in. AsyncLocalStorage scopes this to the nested
|
|
315
|
+
// call, so overlapping parallel agents don't trample each other's state.
|
|
316
|
+
const agentMaxDepth = Math.max(DEFAULT_MAX_DEPTH, agent.maxDepth ?? DEFAULT_MAX_DEPTH);
|
|
317
|
+
const childState: DepthState = { depth: depth + 1, maxDepth: depth + agentMaxDepth };
|
|
322
318
|
|
|
323
319
|
let exitCode = 0;
|
|
324
320
|
let error: string | undefined;
|
|
@@ -329,7 +325,7 @@ export async function runAgent(
|
|
|
329
325
|
const onAbort = () => void session.abort();
|
|
330
326
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
331
327
|
try {
|
|
332
|
-
await session.prompt(task);
|
|
328
|
+
await runWithDepth(childState, () => session.prompt(task));
|
|
333
329
|
} finally {
|
|
334
330
|
signal?.removeEventListener("abort", onAbort);
|
|
335
331
|
}
|
|
@@ -342,12 +338,6 @@ export async function runAgent(
|
|
|
342
338
|
unsubscribe();
|
|
343
339
|
session.dispose();
|
|
344
340
|
loaderLease.release();
|
|
345
|
-
if (prevEnvDepth === undefined) delete process.env[DEPTH_ENV];
|
|
346
|
-
else process.env[DEPTH_ENV] = prevEnvDepth;
|
|
347
|
-
if (prevEnvMaxDepth === undefined) delete process.env[MAX_DEPTH_ENV];
|
|
348
|
-
else process.env[MAX_DEPTH_ENV] = prevEnvMaxDepth;
|
|
349
|
-
_currentDepth = prevDepth;
|
|
350
|
-
_currentMaxDepth = prevMaxDepth;
|
|
351
341
|
}
|
|
352
342
|
|
|
353
343
|
return { output: lastOutput, exitCode, error, model: detectedModel, toolCalls, executionEvents, usage };
|