pi-subagents-lite 1.0.0 → 1.0.2
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 +1 -1
- package/src/agent-manager.ts +3 -0
- package/src/agent-runner.ts +6 -3
- package/src/agent-types.ts +1 -1
- package/src/config-io.ts +1 -1
- package/src/default-agents.ts +1 -1
- package/src/index.ts +12 -9
- package/src/menus.ts +29 -2
- package/src/model-precedence.ts +4 -2
- package/src/tool-execution.ts +9 -13
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -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) => {
|
package/src/agent-runner.ts
CHANGED
|
@@ -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
|
-
/**
|
|
33
|
-
const
|
|
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 +
|
|
575
|
+
} else if (softLimitReached && turnCount >= maxTurns + graceTurns) {
|
|
573
576
|
aborted = true;
|
|
574
577
|
session.abort();
|
|
575
578
|
}
|
package/src/agent-types.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { AgentConfig } from "./types.js";
|
|
|
17
17
|
* `find` and `ls` were removed — they're thin wrappers over bash commands
|
|
18
18
|
* that add ~180 tokens/turn with no real benefit.
|
|
19
19
|
*/
|
|
20
|
-
export const BUILTIN_TOOL_NAMES: string[] = ["read", "bash", "edit", "write", "grep"];
|
|
20
|
+
export const BUILTIN_TOOL_NAMES: string[] = ["read", "bash", "edit", "write", "grep", "find"];
|
|
21
21
|
|
|
22
22
|
/** Unified runtime registry of all agents (defaults + user-defined). */
|
|
23
23
|
const agents = new Map<string, AgentConfig>();
|
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/default-agents.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -136,6 +136,13 @@ async function loadConfigAndRegisterAgents(ctx: ExtensionContext): Promise<void>
|
|
|
136
136
|
// UI helpers — stats card rendering (shared by renderResult and message renderer)
|
|
137
137
|
// ============================================================================
|
|
138
138
|
|
|
139
|
+
/** Format agent display name with optional model: "Agent (mimo-v2.5-pro)" or "Agent". */
|
|
140
|
+
function agentNameLabel(d: Record<string, unknown>, theme: Theme): string {
|
|
141
|
+
const typeName = getDisplayName((d.type as string) || "");
|
|
142
|
+
const modelName = d.modelName as string | undefined;
|
|
143
|
+
return modelName ? `${theme.bold(typeName)} (${modelName})` : theme.bold(typeName);
|
|
144
|
+
}
|
|
145
|
+
|
|
139
146
|
/** Build the stats line for an agent result card. Used by both renderers. */
|
|
140
147
|
function buildStatsLine(d: Record<string, unknown>, theme: Theme): string {
|
|
141
148
|
const parts = buildStatsParts({
|
|
@@ -205,8 +212,9 @@ function registerAgentTool(pi: ExtensionAPI): void {
|
|
|
205
212
|
const desc = (d?.description as string) || "";
|
|
206
213
|
|
|
207
214
|
if (d && d.turnCount != null) {
|
|
215
|
+
const namePart = agentNameLabel(d, theme);
|
|
208
216
|
const statsLine = buildStatsLine(d, theme);
|
|
209
|
-
let lines = `${icon} ${statsLine}\n ${theme.fg("text", desc)}`;
|
|
217
|
+
let lines = `${icon} ${namePart}·${statsLine}\n ${theme.fg("text", desc)}`;
|
|
210
218
|
if (expanded && text) {
|
|
211
219
|
lines += "\n" + text.split("\n").map(l => ` ${l}`).join("\n");
|
|
212
220
|
}
|
|
@@ -265,11 +273,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
265
273
|
if (d && d.turnCount != null) {
|
|
266
274
|
const isError = d.status === "error" || d.status === "aborted" || d.status === "stopped";
|
|
267
275
|
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
268
|
-
const typeName = getDisplayName((d.type as string) || "");
|
|
269
|
-
const modelName = d.modelName as string | undefined;
|
|
270
276
|
const desc = (d.description as string) || "";
|
|
271
277
|
|
|
272
|
-
const namePart =
|
|
278
|
+
const namePart = agentNameLabel(d, theme);
|
|
273
279
|
const statsLine = buildStatsLine(d, theme);
|
|
274
280
|
let headerLine = `${icon} ${namePart}·${statsLine}\n ${theme.fg("text", desc)}`;
|
|
275
281
|
if ((d.outputFile as string)) {
|
|
@@ -283,13 +289,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
283
289
|
inner.addChild(new Text(resultLines, 0, 0));
|
|
284
290
|
}
|
|
285
291
|
} else {
|
|
286
|
-
const typeName = getDisplayName((d?.type as string) || "");
|
|
287
|
-
const modelName = d?.modelName as string | undefined;
|
|
288
292
|
const desc = (d?.description as string) || "";
|
|
289
293
|
let line = `${theme.fg("success", "✓")}`;
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
line += ` ${namePart}`;
|
|
294
|
+
if (d?.type) {
|
|
295
|
+
line += ` ${agentNameLabel(d, theme)}`;
|
|
293
296
|
}
|
|
294
297
|
if (desc) line += `\n ${theme.fg("text", desc)}`;
|
|
295
298
|
if (d?.outputFile) {
|
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
|
-
|
|
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
|
});
|
package/src/model-precedence.ts
CHANGED
|
@@ -17,7 +17,8 @@ export interface SubagentsConfig {
|
|
|
17
17
|
agent: {
|
|
18
18
|
default: string | null;
|
|
19
19
|
forceBackground: boolean;
|
|
20
|
-
|
|
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)
|
package/src/tool-execution.ts
CHANGED
|
@@ -191,11 +191,8 @@ export async function executeAgentTool(
|
|
|
191
191
|
const model = findModelInRegistry(modelStr, ctx.modelRegistry, ctx.model);
|
|
192
192
|
const modelKey = model ? `${model.provider}/${model.id}` : undefined;
|
|
193
193
|
|
|
194
|
-
// Determine modelName for invocation (
|
|
195
|
-
const
|
|
196
|
-
const modelName = (modelKey && modelKey !== parentModelId)
|
|
197
|
-
? parseModelKey(modelKey)?.modelId
|
|
198
|
-
: undefined;
|
|
194
|
+
// Determine modelName for invocation (always capture for display)
|
|
195
|
+
const modelName = model?.id;
|
|
199
196
|
|
|
200
197
|
// Resolve thinking: explicit param > agent config (frontmatter) > undefined (inherit)
|
|
201
198
|
const thinkingLevel = parseThinkingLevel(params.thinking as string | undefined)
|
|
@@ -207,7 +204,8 @@ export async function executeAgentTool(
|
|
|
207
204
|
maxTurns,
|
|
208
205
|
thinkingLevel,
|
|
209
206
|
modelKey,
|
|
210
|
-
invocation:
|
|
207
|
+
invocation: { modelName },
|
|
208
|
+
graceTurns: __config.agent.graceTurns,
|
|
211
209
|
};
|
|
212
210
|
|
|
213
211
|
if (runInBackground || __config.agent.forceBackground) {
|
|
@@ -239,7 +237,7 @@ async function executeSpawnBackground(
|
|
|
239
237
|
|
|
240
238
|
const record = manager.getRecord(agentId)!;
|
|
241
239
|
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}`;
|
|
240
|
+
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
241
|
const label = record.status === "queued" ? "Agent queued" : "Agent running";
|
|
244
242
|
|
|
245
243
|
return successResult(`[${label}] ${suffix}`, details);
|
|
@@ -318,12 +316,10 @@ export async function toolCallListener(
|
|
|
318
316
|
|
|
319
317
|
if (effectiveModel) {
|
|
320
318
|
input.model = effectiveModel;
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
input._modelOverride = parsed.modelId;
|
|
326
|
-
}
|
|
319
|
+
// Always inject _modelOverride for renderCall
|
|
320
|
+
const parsed = parseModelKey(effectiveModel);
|
|
321
|
+
if (parsed) {
|
|
322
|
+
input._modelOverride = parsed.modelId;
|
|
327
323
|
}
|
|
328
324
|
}
|
|
329
325
|
|