@bacnh85/pi-subagent 0.15.2 → 0.15.3
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 +6 -0
- package/README.md +2 -2
- package/extensions/index.ts +10 -4
- package/extensions/runner.ts +6 -2
- package/extensions/security.ts +79 -3
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.3 (2026-08-20)
|
|
4
|
+
### Improvements
|
|
5
|
+
- Timeouts have been extracted as Environment Variables enabling overriding.
|
|
6
|
+
- `PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS` default : 3 Mins
|
|
7
|
+
- `PI_SUBAGENT_HARD_TIMEOUT_MINS` default: 20 Mins
|
|
8
|
+
|
|
3
9
|
## 0.15.2 (2026-08-18)
|
|
4
10
|
|
|
5
11
|
### Improvements
|
package/README.md
CHANGED
|
@@ -140,8 +140,8 @@ each writes into its own checkout.
|
|
|
140
140
|
|
|
141
141
|
Every child execution receives a timeout:
|
|
142
142
|
|
|
143
|
-
- **Default inactivity window:** 3 minutes (`DEFAULT_TIMEOUT_MS`); real SDK lifecycle activity resets it.
|
|
144
|
-
- **Absolute cap:** 20 minutes for every child, even when active.
|
|
143
|
+
- **Default inactivity window:** 3 minutes (`DEFAULT_TIMEOUT_MS`); real SDK lifecycle activity resets it. Overridable with Environment Variable `PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS`
|
|
144
|
+
- **Absolute cap:** Default 20 minutes for every child, even when active. Overridable with Environment variable `PI_SUBAGENT_HARD_TIMEOUT_MINS`
|
|
145
145
|
- **Maximum requested inactivity window:** 60 minutes (`MAX_TIMEOUT_MS`); values must be positive integers.
|
|
146
146
|
- Timeout diagnostics distinguish `Idle timeout` from `Hard timeout` and parent cancellation.
|
|
147
147
|
- 30-second progress heartbeats only keep the parent transport alive; they never reset inactivity.
|
package/extensions/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
startHeartbeat,
|
|
40
40
|
} from "./runner.ts";
|
|
41
41
|
import {
|
|
42
|
+
flushWarnings,
|
|
42
43
|
isRateLimitError,
|
|
43
44
|
normalizeTimeout,
|
|
44
45
|
resolveSafeCwd,
|
|
@@ -107,14 +108,14 @@ const TaskItem = Type.Object({
|
|
|
107
108
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
108
109
|
task: Type.String({ description: "Task to delegate to the agent" }),
|
|
109
110
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent" })),
|
|
110
|
-
timeout: Type.Optional(Type.Number({ description: "Inactivity timeout in
|
|
111
|
+
timeout: Type.Optional(Type.Number({ description: "Inactivity timeout in ms; aborts on no activity within timeout. Default: 3 min (PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS). The agent always has a lifetime cap: default 20 min or (PI_SUBAGENT_HARD_TIMEOUT_MINS)." })),
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
const ChainItem = Type.Object({
|
|
114
115
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
115
116
|
task: Type.String({ description: "Task with optional {previous} placeholder for prior output" }),
|
|
116
117
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent" })),
|
|
117
|
-
timeout: Type.Optional(Type.Number({ description: "Inactivity timeout in
|
|
118
|
+
timeout: Type.Optional(Type.Number({ description: "Inactivity timeout in ms; aborts on no activity within timeout. Default: 3 min (PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS). The agent always has a lifetime cap: default 20 min or (PI_SUBAGENT_HARD_TIMEOUT_MINS)." })),
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
|
|
@@ -153,7 +154,7 @@ const SubagentParams = Type.Object({
|
|
|
153
154
|
// Project-agent confirmation is enforced via trusted configuration.
|
|
154
155
|
// See Security model section in README.
|
|
155
156
|
cwd: Type.Optional(Type.String({ description: "Working directory (single mode, must be inside workspace)" })),
|
|
156
|
-
timeout: Type.Optional(Type.Number({ description: "
|
|
157
|
+
timeout: Type.Optional(Type.Number({ description: "Inactivity timeout for the whole run, in ms; resets on activity, aborts on silence. Default 3 min (PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS). Lifetime cap: 20 min or (PI_SUBAGENT_HARD_TIMEOUT_MINS)." })),
|
|
157
158
|
instructions: Type.Optional(Type.String({ description: "Bounded repository/task instructions passed to each child (max 16 KB)" })),
|
|
158
159
|
abortOnFailure: Type.Optional(Type.Boolean({ description: "In parallel mode, cancel remaining tasks when one fails. Default: false.", default: false })),
|
|
159
160
|
});
|
|
@@ -477,6 +478,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
477
478
|
"Use /subagent to list all available agents or /subagent <name> for agent details.",
|
|
478
479
|
],
|
|
479
480
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
481
|
+
// Surface env-var timeout warnings collected at module load. The
|
|
482
|
+
// interactive TUI swallows module-load stderr, so notify on launch.
|
|
483
|
+
for (const msg of flushWarnings()) {
|
|
484
|
+
ctx.ui?.notify?.(msg, "warning");
|
|
485
|
+
}
|
|
480
486
|
const agentScope: AgentScope = params.agentScope ?? "user";
|
|
481
487
|
const discovery = discoverAgents(ctx.cwd, agentScope, bundledAgentsDir);
|
|
482
488
|
const agents = discovery.agents;
|
|
@@ -1711,4 +1717,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
1711
1717
|
}
|
|
1712
1718
|
}, { overlay: true, overlayOptions: { maxHeight: "70%" } }); // Overlay: editor stays visible below
|
|
1713
1719
|
}
|
|
1714
|
-
}
|
|
1720
|
+
}
|
package/extensions/runner.ts
CHANGED
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
classifyStopReason,
|
|
31
31
|
createCombinedAbortSignal,
|
|
32
32
|
type SubagentStatus,
|
|
33
|
+
DEFAULT_TIMEOUT_MS,
|
|
34
|
+
HARD_TIMEOUT_MS,
|
|
33
35
|
} from "./security.ts";
|
|
34
36
|
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
@@ -46,8 +48,10 @@ export interface UsageStats {
|
|
|
46
48
|
turns: number;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
export
|
|
50
|
-
export const
|
|
51
|
+
/** Re-export from security.ts — single source of truth for both timeouts. */
|
|
52
|
+
export const DEFAULT_INACTIVITY_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
|
|
53
|
+
export { HARD_TIMEOUT_MS };
|
|
54
|
+
|
|
51
55
|
|
|
52
56
|
// ---------------------------------------------------------------------------
|
|
53
57
|
// Extension resource loader
|
package/extensions/security.ts
CHANGED
|
@@ -65,10 +65,57 @@ export const MUTATION_TOOLS: readonly string[] = ["edit", "write"];
|
|
|
65
65
|
export const EXECUTION_TOOLS: readonly string[] = ["bash"];
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* Generic warning sink for configuration problems. Stderr so it is visible
|
|
69
|
+
* with or without a TUI attached. Warnings are also buffered so the extension
|
|
70
|
+
* host can surface them as TUI notifications — the interactive TUI swallows
|
|
71
|
+
* module-load stderr, so module-load warnings alone are invisible in-TUI.
|
|
72
|
+
*/
|
|
73
|
+
const warnings: string[] = [];
|
|
74
|
+
function warn(message: string): void {
|
|
75
|
+
process.stderr.write(`[pi-subagent] ${message}\n`);
|
|
76
|
+
warnings.push(message);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Flush env-var config warnings collected at module load. Called once from
|
|
81
|
+
* the /subagent tool handler so the warnings surface as TUI notifications
|
|
82
|
+
* (module-load stderr is not visible inside the interactive TUI).
|
|
70
83
|
*/
|
|
71
|
-
export
|
|
84
|
+
export function flushWarnings(): string[] {
|
|
85
|
+
return warnings.splice(0, warnings.length);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Read a numeric value from an env var, with validation:
|
|
90
|
+
* - undefined/empty -> defaultValue (no warning)
|
|
91
|
+
* - not a finite number (NaN) -> defaultValue + warning
|
|
92
|
+
* - below `min` -> `min` + warning
|
|
93
|
+
* - above `max` -> `max` + warning
|
|
94
|
+
* - otherwise -> the parsed value (no warning)
|
|
95
|
+
*/
|
|
96
|
+
export function getNumericEnvVar(
|
|
97
|
+
envVar: string,
|
|
98
|
+
defaultValue: number,
|
|
99
|
+
min: number,
|
|
100
|
+
max: number,
|
|
101
|
+
): number {
|
|
102
|
+
const raw = process.env[envVar];
|
|
103
|
+
if (raw === undefined || raw === "") return defaultValue;
|
|
104
|
+
const value = Number(raw);
|
|
105
|
+
if (!Number.isFinite(value)) {
|
|
106
|
+
warn(`ENV VAR: ${envVar}="${raw}" is not a number; using default ${defaultValue}.`);
|
|
107
|
+
return defaultValue;
|
|
108
|
+
}
|
|
109
|
+
if (value < min) {
|
|
110
|
+
warn(`ENV VAR: ${envVar}=${value} is below the minimum of ${min}; using ${min}.`);
|
|
111
|
+
return min;
|
|
112
|
+
}
|
|
113
|
+
if (value > max) {
|
|
114
|
+
warn(`ENV VAR: ${envVar}=${value} is above the maximum of ${max}; using ${max}.`);
|
|
115
|
+
return max;
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
72
119
|
|
|
73
120
|
/**
|
|
74
121
|
* Absolute maximum timeout. Any requested value above this cap is rejected
|
|
@@ -76,6 +123,35 @@ export const DEFAULT_TIMEOUT_MS = 3 * 60 * 1_000; // 3 minutes
|
|
|
76
123
|
*/
|
|
77
124
|
export const MAX_TIMEOUT_MS = 60 * 60 * 1_000; // 60 minutes
|
|
78
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Timeout caps, in minutes, shared by getNumericEnvVar for both env vars.
|
|
128
|
+
* No timeout may be shorter than 1 minute; the inactivity window may not
|
|
129
|
+
* exceed the 60-minute absolute maximum.
|
|
130
|
+
*/
|
|
131
|
+
const MIN_TIMEOUT_MINS = 1;
|
|
132
|
+
const MAX_TIMEOUT_MINS = MAX_TIMEOUT_MS / 60_000;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Default inactivity timeout. Real SDK lifecycle activity resets this window;
|
|
136
|
+
* the runner enforces a fixed absolute cap (HARD_TIMEOUT_MS).
|
|
137
|
+
*
|
|
138
|
+
* Reads PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS (default 3 min, range 1–60) and
|
|
139
|
+
* PI_SUBAGENT_HARD_TIMEOUT_MINS (default 20 min, range 1–60) from env via
|
|
140
|
+
* getNumericEnvVar, which warns on invalid/out-of-range values. The hard cap
|
|
141
|
+
* is additionally clamped up to the inactivity window: a lifetime cap smaller
|
|
142
|
+
* than the idle window is nonsensical.
|
|
143
|
+
*/
|
|
144
|
+
const INACTIVITY_TIMEOUT_MINS = getNumericEnvVar("PI_SUBAGENT_INACTIVITY_TIMEOUT_MINS", 3, MIN_TIMEOUT_MINS, MAX_TIMEOUT_MINS);
|
|
145
|
+
let hardTimeoutMins = getNumericEnvVar("PI_SUBAGENT_HARD_TIMEOUT_MINS", 20, MIN_TIMEOUT_MINS, MAX_TIMEOUT_MINS);
|
|
146
|
+
if (hardTimeoutMins < INACTIVITY_TIMEOUT_MINS) {
|
|
147
|
+
warn(
|
|
148
|
+
`PI_SUBAGENT_HARD_TIMEOUT_MINS (${hardTimeoutMins}) is below the inactivity window (${INACTIVITY_TIMEOUT_MINS}); raising hard cap to ${INACTIVITY_TIMEOUT_MINS}.`,
|
|
149
|
+
);
|
|
150
|
+
hardTimeoutMins = INACTIVITY_TIMEOUT_MINS;
|
|
151
|
+
}
|
|
152
|
+
export const DEFAULT_TIMEOUT_MS = INACTIVITY_TIMEOUT_MINS * 60 * 1_000;
|
|
153
|
+
export const HARD_TIMEOUT_MS = hardTimeoutMins * 60 * 1_000;
|
|
154
|
+
|
|
79
155
|
// ---------------------------------------------------------------------------
|
|
80
156
|
// Canonical result status
|
|
81
157
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-subagent",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.3",
|
|
4
4
|
"description": "In-process subagents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,9 +73,15 @@
|
|
|
73
73
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
74
74
|
"@types/mocha": "^10.0.10",
|
|
75
75
|
"@types/node": "^20.19.43",
|
|
76
|
-
"mocha": "^
|
|
76
|
+
"mocha": "^11.8.0",
|
|
77
77
|
"tsx": "^4.22.4",
|
|
78
78
|
"typescript": "^5.9.3",
|
|
79
79
|
"typebox": "^1.3.1"
|
|
80
|
+
},
|
|
81
|
+
"overrides": {
|
|
82
|
+
"serialize-javascript@>=5.0.0 <7.0.5": "^7.0.5",
|
|
83
|
+
"js-yaml@>=4.0.0 <4.3.1": "^4.3.1",
|
|
84
|
+
"brace-expansion@>=2.0.0 <2.1.4": "^2.1.4",
|
|
85
|
+
"diff": "^8.0.3"
|
|
80
86
|
}
|
|
81
87
|
}
|