@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 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
 
@@ -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
  *
@@ -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" && context.args && typeof context.args === "object") {
400
- const args = context.args;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kushalkhemka/opensec-pi-subagents",
3
- "version": "0.19.10",
3
+ "version": "0.19.11",
4
4
  "description": "Fork of @tintinweb/pi-subagents with parent model/thinking inheritance and cached agent discovery.",
5
5
  "author": "tintinweb",
6
6
  "contributors": [
@@ -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" && context.args && typeof context.args === "object") {
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);