@kushalkhemka/opensec-pi-subagents 0.19.10 → 0.19.11
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 +1 -1
- package/dist/agent-runner.d.ts +6 -0
- package/dist/agent-runner.js +17 -6
- package/dist/index.js +11 -1
- package/package.json +1 -1
- package/src/agent-runner.ts +15 -6
- package/src/index.ts +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.19.11] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **The main agent's commands had no time limit either.** Pi's `bash` has no default timeout, so an unbounded `grep -r … /` or a server started in the foreground held the main agent's turn until someone noticed. A main-agent `bash` call that sets no timeout now gets `toolTimeoutSeconds` (default 1200 s, `0` = off) and is killed at it, with the timeout reported to the model. A timeout the main agent supplies, even a longer one, is kept; subagents stay capped at the limit.
|
|
15
|
+
|
|
10
16
|
## [0.19.10] - 2026-10-07
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ model and thinking level unless their definition or call overrides them.
|
|
|
82
82
|
| Setting | Default | Meaning |
|
|
83
83
|
|---|---|---|
|
|
84
84
|
| Background concurrency (`/agents → Settings`) | `10` | How many background agents run at once. |
|
|
85
|
-
| Tool timeout (`/agents → Settings`, `toolTimeoutSeconds`) | `1200` | Seconds one subagent tool call may run (`0` = no limit). A `bash` call is killed at the limit and the agent sees the timeout; any other tool still running a minute later is stopped and the agent is told to continue another way (up to 3 times per run). |
|
|
85
|
+
| Tool timeout (`/agents → Settings`, `toolTimeoutSeconds`) | `1200` | Seconds one subagent tool call may run (`0` = no limit). A `bash` call is killed at the limit and the agent sees the timeout; any other tool still running a minute later is stopped and the agent is told to continue another way (up to 3 times per run). The main agent's `bash` calls get the same timeout when they set none; a timeout the main agent asks for is kept. |
|
|
86
86
|
|
|
87
87
|
## Notes
|
|
88
88
|
|
package/dist/agent-runner.d.ts
CHANGED
|
@@ -170,6 +170,12 @@ export interface HungTool {
|
|
|
170
170
|
}
|
|
171
171
|
/** The prompt that resumes an agent whose tool call the watchdog stopped. */
|
|
172
172
|
export declare function hungToolPrompt(hung: HungTool): string;
|
|
173
|
+
/**
|
|
174
|
+
* Give a `bash` call's arguments the limit as their timeout when they set none —
|
|
175
|
+
* and, with `cap`, when they set a longer or invalid one. Pi mutates the same
|
|
176
|
+
* object it then executes with, so this changes the running call.
|
|
177
|
+
*/
|
|
178
|
+
export declare function boundBashTimeout(args: unknown, limitSeconds: number, cap: boolean): void;
|
|
173
179
|
/**
|
|
174
180
|
* Bound a subagent session's tool calls (see `toolTimeoutSeconds`).
|
|
175
181
|
*
|
package/dist/agent-runner.js
CHANGED
|
@@ -383,6 +383,21 @@ export function hungToolPrompt(hung) {
|
|
|
383
383
|
+ "output is lost. Continue the task without repeating that call: split the work into smaller, bounded steps "
|
|
384
384
|
+ "(search specific folders rather than the whole filesystem, limit depth and output, add timeouts).";
|
|
385
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Give a `bash` call's arguments the limit as their timeout when they set none —
|
|
388
|
+
* and, with `cap`, when they set a longer or invalid one. Pi mutates the same
|
|
389
|
+
* object it then executes with, so this changes the running call.
|
|
390
|
+
*/
|
|
391
|
+
export function boundBashTimeout(args, limitSeconds, cap) {
|
|
392
|
+
if (limitSeconds <= 0 || !args || typeof args !== "object")
|
|
393
|
+
return;
|
|
394
|
+
const call = args;
|
|
395
|
+
if (call.timeout == null)
|
|
396
|
+
call.timeout = limitSeconds;
|
|
397
|
+
else if (cap && (typeof call.timeout !== "number" || !(call.timeout > 0) || call.timeout > limitSeconds)) {
|
|
398
|
+
call.timeout = limitSeconds;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
386
401
|
/**
|
|
387
402
|
* Bound a subagent session's tool calls (see `toolTimeoutSeconds`).
|
|
388
403
|
*
|
|
@@ -396,12 +411,8 @@ export function installToolTimeouts(session, limitSeconds = toolTimeoutSeconds)
|
|
|
396
411
|
return { takeHung: () => undefined, dispose: () => { } };
|
|
397
412
|
const priorBeforeToolCall = session.agent.beforeToolCall;
|
|
398
413
|
session.agent.beforeToolCall = async (context, signal) => {
|
|
399
|
-
if (context.toolCall.name === "bash"
|
|
400
|
-
|
|
401
|
-
if (typeof args.timeout !== "number" || !(args.timeout > 0) || args.timeout > limitSeconds) {
|
|
402
|
-
args.timeout = limitSeconds;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
414
|
+
if (context.toolCall.name === "bash")
|
|
415
|
+
boundBashTimeout(context.args, limitSeconds, true);
|
|
405
416
|
return priorBeforeToolCall?.(context, signal);
|
|
406
417
|
};
|
|
407
418
|
const running = new Map();
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { abortable } from "./abortable.js";
|
|
|
18
18
|
import { hasAgentBadge, renderAgentName } from "./agent-color.js";
|
|
19
19
|
import { buildNewAgentFile, disableInContent, enableInContent, isEmptyStub, locateAgentFile, personalAgentsDir, projectAgentsDir, serializeAgentFile } from "./agent-file-toggle.js";
|
|
20
20
|
import { AgentManager, isTopLevelAgent } from "./agent-manager.js";
|
|
21
|
-
import { getAgentConversation, getDefaultMaxTurns, getGraceTurns, getRememberAgents, getToolTimeoutSeconds, normalizeMaxTurns, resolveEffectiveMaxTurns, SUBAGENT_TOOL_NAMES, setDefaultMaxTurns, setGraceTurns, setRememberAgents, setToolTimeoutSeconds, steerAgent } from "./agent-runner.js";
|
|
21
|
+
import { boundBashTimeout, getAgentConversation, getDefaultMaxTurns, getGraceTurns, getRememberAgents, getToolTimeoutSeconds, normalizeMaxTurns, resolveEffectiveMaxTurns, SUBAGENT_TOOL_NAMES, setDefaultMaxTurns, setGraceTurns, setRememberAgents, setToolTimeoutSeconds, steerAgent } from "./agent-runner.js";
|
|
22
22
|
import { BUILTIN_TOOL_NAMES, getAgentConfig, getAllTypes, getAvailableTypes, getConfig, getFallbackSubagent, isDefaultsDisabled, NO_FALLBACK, registerAgents, resolveSpawnType, resolveType, setDefaultsDisabled, setFallbackSubagent } from "./agent-types.js";
|
|
23
23
|
import { inChildSessionContext } from "./child-context.js";
|
|
24
24
|
import { registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
@@ -1310,6 +1310,16 @@ export default function (pi) {
|
|
|
1310
1310
|
});
|
|
1311
1311
|
return record;
|
|
1312
1312
|
}
|
|
1313
|
+
// The main agent's `bash` calls get the tool timeout too, but only when they
|
|
1314
|
+
// set none: the user is there to ask for a longer one. Pi's bash has no default,
|
|
1315
|
+
// so an unbounded `grep -r … /` or a server started in the foreground would
|
|
1316
|
+
// otherwise hold the turn until someone notices. (Subagents are also capped —
|
|
1317
|
+
// see installToolTimeouts in agent-runner.ts.)
|
|
1318
|
+
pi.on("tool_call", (event) => {
|
|
1319
|
+
if (event.toolName === "bash")
|
|
1320
|
+
boundBashTimeout(event.input, getToolTimeoutSeconds(), false);
|
|
1321
|
+
return undefined;
|
|
1322
|
+
});
|
|
1313
1323
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
1314
1324
|
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
1315
1325
|
widget.setUICtx(ctx.ui);
|
package/package.json
CHANGED
package/src/agent-runner.ts
CHANGED
|
@@ -449,6 +449,20 @@ export function hungToolPrompt(hung: HungTool): string {
|
|
|
449
449
|
+ "(search specific folders rather than the whole filesystem, limit depth and output, add timeouts).";
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Give a `bash` call's arguments the limit as their timeout when they set none —
|
|
454
|
+
* and, with `cap`, when they set a longer or invalid one. Pi mutates the same
|
|
455
|
+
* object it then executes with, so this changes the running call.
|
|
456
|
+
*/
|
|
457
|
+
export function boundBashTimeout(args: unknown, limitSeconds: number, cap: boolean): void {
|
|
458
|
+
if (limitSeconds <= 0 || !args || typeof args !== "object") return;
|
|
459
|
+
const call = args as { timeout?: unknown };
|
|
460
|
+
if (call.timeout == null) call.timeout = limitSeconds;
|
|
461
|
+
else if (cap && (typeof call.timeout !== "number" || !(call.timeout > 0) || call.timeout > limitSeconds)) {
|
|
462
|
+
call.timeout = limitSeconds;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
452
466
|
/**
|
|
453
467
|
* Bound a subagent session's tool calls (see `toolTimeoutSeconds`).
|
|
454
468
|
*
|
|
@@ -465,12 +479,7 @@ export function installToolTimeouts(session: AgentSession, limitSeconds = toolTi
|
|
|
465
479
|
|
|
466
480
|
const priorBeforeToolCall = session.agent.beforeToolCall;
|
|
467
481
|
session.agent.beforeToolCall = async (context, signal) => {
|
|
468
|
-
if (context.toolCall.name === "bash"
|
|
469
|
-
const args = context.args as { timeout?: unknown };
|
|
470
|
-
if (typeof args.timeout !== "number" || !(args.timeout > 0) || args.timeout > limitSeconds) {
|
|
471
|
-
args.timeout = limitSeconds;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
482
|
+
if (context.toolCall.name === "bash") boundBashTimeout(context.args, limitSeconds, true);
|
|
474
483
|
return priorBeforeToolCall?.(context, signal);
|
|
475
484
|
};
|
|
476
485
|
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { abortable } from "./abortable.js";
|
|
|
19
19
|
import { hasAgentBadge, renderAgentName } from "./agent-color.js";
|
|
20
20
|
import { buildNewAgentFile, disableInContent, enableInContent, isEmptyStub, locateAgentFile, personalAgentsDir, projectAgentsDir, serializeAgentFile } from "./agent-file-toggle.js";
|
|
21
21
|
import { AgentManager, isTopLevelAgent } from "./agent-manager.js";
|
|
22
|
-
import { getAgentConversation, getDefaultMaxTurns, getGraceTurns, getRememberAgents, getToolTimeoutSeconds, normalizeMaxTurns, resolveEffectiveMaxTurns, SUBAGENT_TOOL_NAMES, setDefaultMaxTurns, setGraceTurns, setRememberAgents, setToolTimeoutSeconds, steerAgent } from "./agent-runner.js";
|
|
22
|
+
import { boundBashTimeout, getAgentConversation, getDefaultMaxTurns, getGraceTurns, getRememberAgents, getToolTimeoutSeconds, normalizeMaxTurns, resolveEffectiveMaxTurns, SUBAGENT_TOOL_NAMES, setDefaultMaxTurns, setGraceTurns, setRememberAgents, setToolTimeoutSeconds, steerAgent } from "./agent-runner.js";
|
|
23
23
|
import { BUILTIN_TOOL_NAMES, getAgentConfig, getAllTypes, getAvailableTypes, getConfig, getFallbackSubagent, isDefaultsDisabled, NO_FALLBACK, registerAgents, resolveSpawnType, resolveType, setDefaultsDisabled, setFallbackSubagent } from "./agent-types.js";
|
|
24
24
|
import { inChildSessionContext } from "./child-context.js";
|
|
25
25
|
import { type RpcHandle, registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
@@ -1413,6 +1413,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1413
1413
|
return record;
|
|
1414
1414
|
}
|
|
1415
1415
|
|
|
1416
|
+
// The main agent's `bash` calls get the tool timeout too, but only when they
|
|
1417
|
+
// set none: the user is there to ask for a longer one. Pi's bash has no default,
|
|
1418
|
+
// so an unbounded `grep -r … /` or a server started in the foreground would
|
|
1419
|
+
// otherwise hold the turn until someone notices. (Subagents are also capped —
|
|
1420
|
+
// see installToolTimeouts in agent-runner.ts.)
|
|
1421
|
+
pi.on("tool_call", (event) => {
|
|
1422
|
+
if (event.toolName === "bash") boundBashTimeout(event.input, getToolTimeoutSeconds(), false);
|
|
1423
|
+
return undefined;
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1416
1426
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
1417
1427
|
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
1418
1428
|
widget.setUICtx(ctx.ui as UICtx);
|