pi-subagents-lite 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents-lite",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Lightweight sub-agents for pi — spawn specialized agents with isolated sessions, tools, and models.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -115,6 +115,8 @@ export interface SpawnOptions {
115
115
  onAssistantUsage?: (usage: LifetimeUsage) => void;
116
116
  /** Called when the session successfully compacts. */
117
117
  onCompaction?: (info: CompactionInfo) => void;
118
+ /** Grace turns: extra turns allowed after hitting maxTurns. */
119
+ graceTurns?: number;
118
120
  }
119
121
 
120
122
  export class AgentManager {
@@ -304,6 +306,7 @@ export class AgentManager {
304
306
  model: options.model,
305
307
  maxTurns: options.maxTurns,
306
308
  thinkingLevel: options.thinkingLevel,
309
+ graceTurns: options.graceTurns,
307
310
  signal: record.abortController!.signal,
308
311
  ...this.createRecordCallbacks(record, options),
309
312
  onTurnEnd: (turnCount) => {
@@ -29,8 +29,8 @@ import { type CompactionInfo, type EnvInfo, SHORT_ID_LENGTH, type SubagentType,
29
29
  /** Names of tools registered by this extension that subagents must NOT inherit. */
30
30
  const EXCLUDED_TOOL_NAMES = ["Agent"];
31
31
 
32
- /** Additional turns allowed after the soft limit steer message. */
33
- const GRACE_TURNS = 5;
32
+ /** Default grace turns when not specified in config. */
33
+ const DEFAULT_GRACE_TURNS = 6;
34
34
 
35
35
  /** Timeout for quick git commands (branch detection, repo check). */
36
36
  const GIT_EXEC_TIMEOUT_MS = 5000;
@@ -76,6 +76,8 @@ interface RunOptions {
76
76
  * pre-compaction context size estimate. Aborted compactions don't fire.
77
77
  */
78
78
  onCompaction?: (info: CompactionInfo) => void;
79
+ /** Grace turns: extra turns allowed after hitting maxTurns. Defaults to 6. */
80
+ graceTurns?: number;
79
81
  }
80
82
 
81
83
  interface RunResult {
@@ -558,6 +560,7 @@ export async function runAgent(
558
560
  const maxTurns = normalizeMaxTurns(options.maxTurns ?? agentConfig?.maxTurns);
559
561
  let softLimitReached = false;
560
562
  let aborted = false;
563
+ const graceTurns = options.graceTurns ?? DEFAULT_GRACE_TURNS;
561
564
 
562
565
  const unsubEvents = subscribeToSessionEvents(session, options);
563
566
 
@@ -569,7 +572,7 @@ export async function runAgent(
569
572
  if (!softLimitReached && turnCount >= maxTurns) {
570
573
  softLimitReached = true;
571
574
  session.steer("You have reached your turn limit. Wrap up immediately — provide your final answer now.");
572
- } else if (softLimitReached && turnCount >= maxTurns + GRACE_TURNS) {
575
+ } else if (softLimitReached && turnCount >= maxTurns + graceTurns) {
573
576
  aborted = true;
574
577
  session.abort();
575
578
  }
package/src/config-io.ts CHANGED
@@ -14,7 +14,7 @@ const CONFIG_PATH = path.join(CONFIG_DIR, "subagents-lite.json");
14
14
 
15
15
  /** Default configuration — used when config file doesn't exist or is invalid. */
16
16
  export const DEFAULT_CONFIG: SubagentsConfig = {
17
- agent: { default: null, forceBackground: false },
17
+ agent: { default: null, forceBackground: false, graceTurns: 6 },
18
18
  concurrency: { default: 4 },
19
19
  };
20
20
 
package/src/menus.ts CHANGED
@@ -301,6 +301,26 @@ export async function showModelSettingsMenu(
301
301
  );
302
302
  });
303
303
 
304
+ // Grace turns setting
305
+ const graceTurns = __config.agent.graceTurns ?? 6;
306
+ items.push(`Grace turns · ${graceTurns}`);
307
+ actions.push(async () => {
308
+ const input = await ctx.ui.input("Grace turns (≥ 0)", String(graceTurns));
309
+ if (input === undefined) return;
310
+ const parsed = parseInt(input.trim(), 10);
311
+ if (isNaN(parsed)) {
312
+ ctx.ui.notify("Invalid value — must be a number", "error");
313
+ return;
314
+ }
315
+ if (parsed < 0) {
316
+ ctx.ui.notify("Invalid value — must be ≥ 0", "error");
317
+ return;
318
+ }
319
+ __config.agent.graceTurns = parsed;
320
+ saveConfigAtomic(__config);
321
+ ctx.ui.notify(`Grace turns set to ${parsed}`, "info");
322
+ });
323
+
304
324
  items.push("");
305
325
  actions.push(async () => {});
306
326
  items.push("─── per-type overrides ───");
@@ -375,13 +395,20 @@ export async function showModelSettingsMenu(
375
395
  items.push("Clear all overrides");
376
396
  actions.push(async () => {
377
397
  const hasOverrides = Object.entries(__config.agent).some(
378
- ([k, v]) => k !== "default" && k !== "forceBackground" && v != null,
398
+ ([k, v]) => k !== "default" && k !== "forceBackground" && k !== "graceTurns" && v != null,
379
399
  );
380
400
  if (!hasOverrides && __config.agent.default === null) {
381
401
  ctx.ui.notify("No overrides to clear", "info");
382
402
  return;
383
403
  }
384
- __config.agent = { default: __config.agent.default, forceBackground: __config.agent.forceBackground };
404
+ const preserved: Record<string, unknown> = {
405
+ default: __config.agent.default,
406
+ forceBackground: __config.agent.forceBackground,
407
+ };
408
+ if (__config.agent.graceTurns != null) {
409
+ preserved.graceTurns = __config.agent.graceTurns;
410
+ }
411
+ __config.agent = preserved as typeof __config.agent;
385
412
  saveConfigAtomic(__config);
386
413
  ctx.ui.notify("All model overrides cleared", "info");
387
414
  });
@@ -17,7 +17,8 @@ export interface SubagentsConfig {
17
17
  agent: {
18
18
  default: string | null;
19
19
  forceBackground: boolean;
20
- [agentType: string]: string | null | undefined | boolean;
20
+ graceTurns?: number;
21
+ [agentType: string]: string | null | undefined | boolean | number;
21
22
  };
22
23
  concurrency: {
23
24
  default: number;
@@ -60,10 +61,11 @@ export function resolveModel(options: ResolveModelOptions): string {
60
61
  const { subagentType, agentConfig, config, parentModelId, sessionOverrides } = options;
61
62
 
62
63
  // Precedence chain: session > config > frontmatter > parent
64
+ // Cast agent values: index signature includes number (graceTurns), but models are always strings
63
65
  const candidates: Array<string | boolean | null | undefined> = [
64
66
  sessionOverrides?.[subagentType],
65
67
  sessionOverrides?.["default"],
66
- config.agent[subagentType],
68
+ config.agent[subagentType] as string | null | undefined,
67
69
  config.agent["default"],
68
70
  agentConfig?.model,
69
71
  parentModelId, // final fallback (always a valid string)
@@ -208,6 +208,7 @@ export async function executeAgentTool(
208
208
  thinkingLevel,
209
209
  modelKey,
210
210
  invocation: modelName ? { modelName } : undefined,
211
+ graceTurns: __config.agent.graceTurns,
211
212
  };
212
213
 
213
214
  if (runInBackground || __config.agent.forceBackground) {
@@ -239,7 +240,7 @@ async function executeSpawnBackground(
239
240
 
240
241
  const record = manager.getRecord(agentId)!;
241
242
  const details: Record<string, unknown> = { type: resolvedType, description: spawnOptions.description };
242
- const suffix = `A notification will arrive when done - User asks you not to poll or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
243
+ const suffix = `A notification will arrive when done - User asks you not to poll, check status or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
243
244
  const label = record.status === "queued" ? "Agent queued" : "Agent running";
244
245
 
245
246
  return successResult(`[${label}] ${suffix}`, details);