brl-subagent 2.3.9 → 2.4.0
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/AGENT.md +5 -0
- package/README.md +20 -8
- package/package.json +11 -9
- package/src/concurrency.ts +1 -0
- package/src/diff.ts +1 -0
- package/src/event-bus.ts +1 -0
- package/src/git.ts +1 -0
- package/src/history.ts +1 -0
- package/src/index.ts +60 -374
- package/src/logging.ts +21 -1
- package/src/messaging.ts +1 -0
- package/src/metrics.ts +1 -0
- package/src/model-availability.ts +1 -0
- package/src/notify-completion.ts +1 -0
- package/src/params.ts +1 -0
- package/src/paths.ts +1 -0
- package/src/preflight.ts +1 -0
- package/src/prelude.ts +1 -0
- package/src/presets.ts +1 -0
- package/src/prompt.ts +1 -0
- package/src/reports.ts +64 -8
- package/src/router.ts +1 -0
- package/src/runner.ts +1 -0
- package/src/sanitize.ts +1 -0
- package/src/scheduler.ts +1 -0
- package/src/schema.ts +254 -0
- package/src/session-manager.ts +44 -96
- package/src/state.ts +1 -0
- package/src/templates.ts +1 -0
- package/src/transcript-tail.ts +1 -0
- package/src/transcript.ts +1 -0
- package/src/tui-format.ts +1 -0
- package/src/tui.ts +3 -208
- package/src/types.ts +23 -2
- package/src/unit-run.ts +1 -0
- package/src/validate.ts +16 -2
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Purpose: Entry point: tool/command registration, the delegate_task handlers, and the execution-mode runners.
|
|
1
2
|
/**
|
|
2
3
|
* brl-subagent — multi-agent orchestration for pi.
|
|
3
4
|
* Entry point: tool registrations, command surface, and the multi-mode runners.
|
|
@@ -29,6 +30,7 @@ import type {
|
|
|
29
30
|
GraphDetails,
|
|
30
31
|
GraphWave,
|
|
31
32
|
DelegateTaskDetails,
|
|
33
|
+
DelegateTaskParams,
|
|
32
34
|
Priority,
|
|
33
35
|
TaskTemplate,
|
|
34
36
|
ToolResult,
|
|
@@ -68,8 +70,9 @@ import { preflightCheck } from "./preflight";
|
|
|
68
70
|
import { gateSessionCost, rejectApprovalAlwaysInBackground, runPreTaskValidation, validateDelegationTargets, formatValidationWarnings } from "./prelude";
|
|
69
71
|
import { loadBuiltinPresets, loadCustomPresets, getAllPresets, writePresetFile, formatPresetRestriction, formatToolRestriction } from "./presets";
|
|
70
72
|
import { modelIsAvailable } from "./model-availability";
|
|
71
|
-
import { validatePreTask, diagnoseFailure } from "./validate";
|
|
73
|
+
import { validatePreTask, diagnoseFailure, DEFAULT_BACKGROUND_DEADLINE_MS } from "./validate";
|
|
72
74
|
import { findUnknownParams, KNOWN_DELEGATE_KEYS, resolveSubagentParams, snapshotOriginalParams } from "./params";
|
|
75
|
+
import { delegateTaskParamsSchema } from "./schema";
|
|
73
76
|
import { createSessionState } from "./state";
|
|
74
77
|
import { makeLiveOnUpdate, createUnitRun, finalizeUnitRun, finalizeUnitRunCrash, pruneHistoryIfNeeded, registerLiveRun } from "./unit-run";
|
|
75
78
|
import { buildSubagentPrompt, describePromptMode } from "./prompt";
|
|
@@ -100,6 +103,7 @@ import {
|
|
|
100
103
|
showRunHistory,
|
|
101
104
|
showMonitor,
|
|
102
105
|
showDashboard,
|
|
106
|
+
showComplianceMenu,
|
|
103
107
|
showRetryMenu,
|
|
104
108
|
renderDelegateCall,
|
|
105
109
|
renderDelegateResult,
|
|
@@ -119,35 +123,6 @@ import {
|
|
|
119
123
|
// Helpers
|
|
120
124
|
// ---------------------------------------------------------------------------
|
|
121
125
|
|
|
122
|
-
/**
|
|
123
|
-
* Sanitize a text preview for markdown-rendered notifications: strip code
|
|
124
|
-
* fences (``` and ~~~) and backticks, keep whole non-empty lines with
|
|
125
|
-
* mid-line truncation for over-long lines, and never split surrogate pairs
|
|
126
|
-
* at the boundary.
|
|
127
|
-
*/
|
|
128
|
-
function sanitizePreview(text: string, maxLen = 500): string {
|
|
129
|
-
// Strip markdown fences and backticks
|
|
130
|
-
const cleaned = text.replace(/```/g, '').replace(/~~~+/g, '').replace(/`/g, '');
|
|
131
|
-
const lines = cleaned.split('\n').filter(l => l.trim());
|
|
132
|
-
let out = '';
|
|
133
|
-
for (const line of lines) {
|
|
134
|
-
if (out.length + line.length + 1 > maxLen) {
|
|
135
|
-
// Mid-line truncation: keep as much of this line as fits
|
|
136
|
-
const remaining = maxLen - out.length - 1;
|
|
137
|
-
if (remaining > 10) {
|
|
138
|
-
out += line.slice(0, remaining - 3) + '...\n';
|
|
139
|
-
}
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
out += line + '\n';
|
|
143
|
-
}
|
|
144
|
-
// Avoid splitting surrogate pairs at the boundary (reachable now via slice above)
|
|
145
|
-
const safe = out.slice(0, maxLen);
|
|
146
|
-
const lastChar = safe[safe.length - 1];
|
|
147
|
-
const stripped = lastChar && /[\uD800-\uDBFF]/.test(lastChar) ? safe.slice(0, -1) : safe;
|
|
148
|
-
return stripped.trimEnd();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
126
|
/**
|
|
152
127
|
* DRY helper (issue #154 review): shape an inventory of items into a single
|
|
153
128
|
* comma-delimited summary line for the LLM-facing delegation guidance. Both
|
|
@@ -192,6 +167,17 @@ export function buildTemplateGuideline(templateSummary: string): string {
|
|
|
192
167
|
// Extension entry point
|
|
193
168
|
// ---------------------------------------------------------------------------
|
|
194
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Compile-time coverage (issue #229 item 4): fails to compile if
|
|
172
|
+
* DelegateTaskParams omits any KNOWN_DELEGATE_KEYS entry. Runtime drift is
|
|
173
|
+
* invisible (the schema passes the key through, the handler ignores it), so
|
|
174
|
+
* only the compiler can catch it. Together with per-step-model.test.ts's
|
|
175
|
+
* runtime ratchet this closes the chain: registered schema <-> KNOWN_DELEGATE_KEYS <-> DelegateTaskParams.
|
|
176
|
+
*/
|
|
177
|
+
type _KnownDelegateKey = (typeof KNOWN_DELEGATE_KEYS extends Set<infer K> ? K : never) & string;
|
|
178
|
+
type _ExpectNever<T extends never> = T;
|
|
179
|
+
type _DelegateParamCoverage = _ExpectNever<Exclude<_KnownDelegateKey, keyof DelegateTaskParams>>;
|
|
180
|
+
|
|
195
181
|
export default function (pi: ExtensionAPI) {
|
|
196
182
|
const log = createLogger("brl-subagent");
|
|
197
183
|
|
|
@@ -413,12 +399,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
413
399
|
// -------------------------------------------------------------------
|
|
414
400
|
|
|
415
401
|
async function runChainMode(
|
|
416
|
-
params:
|
|
402
|
+
params: DelegateTaskParams,
|
|
417
403
|
signal: AbortSignal | undefined,
|
|
418
404
|
onUpdate: AgentToolUpdateCallback<DelegateTaskDetails> | undefined,
|
|
419
405
|
ctx: ExtensionContext,
|
|
420
406
|
): Promise<ToolResult<SubagentResult | ChainDetails | undefined>> {
|
|
421
|
-
const chainSteps = params.chain
|
|
407
|
+
const chainSteps: SubTaskParams[] = params.chain!;
|
|
422
408
|
|
|
423
409
|
// Issue #133: dispatch-start timestamp — the aggregate run entry's
|
|
424
410
|
// startedAt (captured once, at mode entry, not at completion).
|
|
@@ -937,12 +923,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
937
923
|
// -------------------------------------------------------------------
|
|
938
924
|
|
|
939
925
|
async function runParallelMode(
|
|
940
|
-
params:
|
|
926
|
+
params: DelegateTaskParams,
|
|
941
927
|
signal: AbortSignal | undefined,
|
|
942
928
|
onUpdate: AgentToolUpdateCallback<DelegateTaskDetails> | undefined,
|
|
943
929
|
ctx: ExtensionContext,
|
|
944
930
|
): Promise<ToolResult<SubagentResult | ParallelDetails | undefined>> {
|
|
945
|
-
const taskList = params.tasks
|
|
931
|
+
const taskList: SubTaskParams[] = params.tasks!;
|
|
946
932
|
|
|
947
933
|
// R5: Check session cost limit before spawning
|
|
948
934
|
const costError = gateSessionCost({
|
|
@@ -1407,12 +1393,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1407
1393
|
// -------------------------------------------------------------------
|
|
1408
1394
|
|
|
1409
1395
|
async function runGraphMode(
|
|
1410
|
-
params:
|
|
1396
|
+
params: DelegateTaskParams,
|
|
1411
1397
|
signal: AbortSignal | undefined,
|
|
1412
1398
|
onUpdate: AgentToolUpdateCallback<DelegateTaskDetails> | undefined,
|
|
1413
1399
|
ctx: ExtensionContext,
|
|
1414
1400
|
): Promise<ToolResult<SubagentResult | GraphDetails | undefined>> {
|
|
1415
|
-
const graphTasks = params.graph
|
|
1401
|
+
const graphTasks: GraphTask[] = params.graph!;
|
|
1416
1402
|
|
|
1417
1403
|
// Issue #133: dispatch-start timestamp — the aggregate run entry's
|
|
1418
1404
|
// startedAt (captured once, at mode entry, not at completion).
|
|
@@ -2012,10 +1998,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2012
1998
|
async function runBackgroundFanOut(
|
|
2013
1999
|
pi: ExtensionAPI,
|
|
2014
2000
|
ctx: ExtensionContext,
|
|
2015
|
-
params:
|
|
2001
|
+
params: DelegateTaskParams,
|
|
2016
2002
|
signal: AbortSignal | undefined,
|
|
2017
2003
|
): Promise<ToolResult<SubagentResult | undefined>> {
|
|
2018
|
-
const taskList = params.tasks
|
|
2004
|
+
const taskList: SubTaskParams[] = params.tasks!;
|
|
2019
2005
|
|
|
2020
2006
|
// Session cost limit gates the WHOLE batch before any spawn — same
|
|
2021
2007
|
// estimate/limit logic as runParallelMode, scaled by task count.
|
|
@@ -2253,10 +2239,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2253
2239
|
// startBackgroundAgent — the shared background-spawn tail.
|
|
2254
2240
|
// Used by BOTH spawnBackgroundRun (single task) and runBackgroundFanOut
|
|
2255
2241
|
// (tasks fan-out): the session-manager import, the spawn call, live-monitor
|
|
2256
|
-
// registration, footer counters, the 2s progress poller and the
|
|
2257
|
-
// timer live here exactly once. Callers keep their own prelude,
|
|
2258
|
-
// validations and result formatting. THROWS on spawn failure —
|
|
2259
|
-
// translate the error into their own result shape.
|
|
2242
|
+
// registration, footer counters, the 2s progress poller and the default
|
|
2243
|
+
// deadline timer live here exactly once. Callers keep their own prelude,
|
|
2244
|
+
// checks, validations and result formatting. THROWS on spawn failure —
|
|
2245
|
+
// callers translate the error into their own result shape.
|
|
2260
2246
|
// -------------------------------------------------------------------
|
|
2261
2247
|
|
|
2262
2248
|
async function startBackgroundAgent(
|
|
@@ -2412,7 +2398,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2412
2398
|
}, { deliverAs: "followUp" });
|
|
2413
2399
|
} else if (agent.status === 'stopped') {
|
|
2414
2400
|
// User-initiated stop (stop_subagent) or deadline abort
|
|
2415
|
-
// (timeout/
|
|
2401
|
+
// (timeout/deadline) — not a failure.
|
|
2416
2402
|
updateProgressStatus(state, ctx);
|
|
2417
2403
|
} else {
|
|
2418
2404
|
state.completedSubagents++;
|
|
@@ -2447,20 +2433,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
2447
2433
|
}
|
|
2448
2434
|
}, 2000);
|
|
2449
2435
|
|
|
2450
|
-
//
|
|
2451
|
-
// W2 (issue #28): previously this only stopped the poller —
|
|
2452
|
-
// session kept running forever (orphaned). Now it pre-sets
|
|
2453
|
-
// 'stopped' (so the .then keeps it, per W1) and aborts the
|
|
2454
|
-
//
|
|
2455
|
-
|
|
2456
|
-
|
|
2436
|
+
// Default deadline: stop polling AND abort the session after the
|
|
2437
|
+
// deadline. W2 (issue #28): previously this only stopped the poller —
|
|
2438
|
+
// the pi session kept running forever (orphaned). Now it pre-sets
|
|
2439
|
+
// 'stopped' (so the .then keeps it, per W1) and aborts the session.
|
|
2440
|
+
// Issue #240: an explicit per-agent timeout is HONORED verbatim; this
|
|
2441
|
+
// deadline DEFAULTS to 30m only when no timeout is given. It is a
|
|
2442
|
+
// default, not a ceiling. Issue #244: exactly ONE deadline timer per
|
|
2443
|
+
// run — an explicit timeout is owned by the session-manager W3 timer,
|
|
2444
|
+
// so this index timer arms ONLY on the no-timeout path, mirroring
|
|
2445
|
+
// spawnBackgroundSession's `params.timeout && params.timeout > 0` guard
|
|
2446
|
+
// (with no timeout, it owns the 30m default, #28).
|
|
2447
|
+
const hasExplicitDeadline = spawn.timeout !== undefined && spawn.timeout > 0;
|
|
2448
|
+
const hardCapMs = spawn.timeout ?? DEFAULT_BACKGROUND_DEADLINE_MS;
|
|
2449
|
+
const hardCapHandle: ReturnType<typeof setTimeout> | undefined =
|
|
2450
|
+
hasExplicitDeadline ? undefined : setTimeout(() => {
|
|
2457
2451
|
if (!completed && !agent.completedAt) {
|
|
2458
2452
|
try {
|
|
2459
2453
|
completed = true;
|
|
2460
2454
|
clearInterval(pollInterval);
|
|
2461
2455
|
// Pre-set stopped BEFORE aborting so the .then in
|
|
2462
2456
|
// spawnBackgroundSession keeps the stopped state.
|
|
2463
|
-
updateAgentStatus(agent.id, 'stopped', `Timed out (${hardCapMs}ms
|
|
2457
|
+
updateAgentStatus(agent.id, 'stopped', `Timed out (${hardCapMs}ms deadline)`);
|
|
2464
2458
|
agent._sessionRef?.abort().catch(() => {
|
|
2465
2459
|
// Abort may reject if the session is mid-dispose;
|
|
2466
2460
|
// the status flip above is already recorded.
|
|
@@ -2480,7 +2474,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2480
2474
|
// W3/poller stopped path (no completedSubagents increment).
|
|
2481
2475
|
updateProgressStatus(state, ctx);
|
|
2482
2476
|
} catch (err) {
|
|
2483
|
-
// Defensive: never let the
|
|
2477
|
+
// Defensive: never let the deadline timer throw uncaught — that would
|
|
2484
2478
|
// skip finalizeLiveSubagent, the counter decrement, and the notification.
|
|
2485
2479
|
// Do all fallible work first (output capture), then mutate counters,
|
|
2486
2480
|
// then notify — a throw mid-path can't double-fire mutations.
|
|
@@ -2491,7 +2485,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2491
2485
|
// get_subagent_result doesn't report 'running' and the W3 timer
|
|
2492
2486
|
// guard (!agent.completedAt) can't re-fire later.
|
|
2493
2487
|
try {
|
|
2494
|
-
updateAgentStatus(agent.id, 'stopped', `Timed out (${hardCapMs}ms
|
|
2488
|
+
updateAgentStatus(agent.id, 'stopped', `Timed out (${hardCapMs}ms deadline)`);
|
|
2495
2489
|
} catch { /* ignore */ }
|
|
2496
2490
|
try {
|
|
2497
2491
|
setAgentFinalOutput(agent.id, extractAgentFinalOutput(agent));
|
|
@@ -2696,6 +2690,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
2696
2690
|
retry: () => showRetryMenu(ctx, state),
|
|
2697
2691
|
sla: () => showSLAConfig(ctx, state, applyConfig),
|
|
2698
2692
|
"sla-stats": () => showSLAStats(ctx, state),
|
|
2693
|
+
// E5: the compliance report menu (file access / secrets exposure /
|
|
2694
|
+
// full summary) shipped in tui.ts but had no handler key, so
|
|
2695
|
+
// `/brl-subagent compliance` fell through to the config menu and the
|
|
2696
|
+
// feature was unreachable (broken wiring, not dead code).
|
|
2697
|
+
compliance: () => showComplianceMenu(ctx, state),
|
|
2699
2698
|
};
|
|
2700
2699
|
|
|
2701
2700
|
if (trimmed && trimmed in handlers) {
|
|
@@ -2755,13 +2754,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
2755
2754
|
"You can customize per-call via inheritSystemPrompt and systemPrompt: set inheritSystemPrompt: false to save context, provide a systemPrompt for custom instructions, or use both to add instructions on top of inheritance.",
|
|
2756
2755
|
"Set thinkingLevel per call to match task complexity. The level is capped at the user's configured maximum. Map tasks to levels using this heuristic: off = file listing, grep, simple read. minimal = file diff, syntax check, find-and-replace. low = refactoring, test generation, documentation. medium = default — code review, debugging, moderate analysis. high = security audit, architecture review, complex debugging. xhigh = multi-step causal reasoning, research, novel problem solving. Default to 'off' or 'minimal' for trivial tasks — do not waste the user's budget.",
|
|
2757
2756
|
"Use outputFile to have the subagent write full findings to disk and return only a structured summary — saves context tokens for large investigations.",
|
|
2758
|
-
"Set timeout (in ms) to limit how long a subagent can run. Useful for tasks that might hang or get stuck. Background runs
|
|
2757
|
+
"Set timeout (in ms) to limit how long a subagent can run. Useful for tasks that might hang or get stuck. Background runs default to a 30-minute deadline when no timeout is given; an explicit timeout is honored as given (30 minutes is a default, not a ceiling).",
|
|
2759
2758
|
"Set cwd to override the subagent's working directory. Defaults to the current project directory.",
|
|
2760
2759
|
"Set label to give the subagent a human-readable name (e.g., 'security-audit' or 'docs-review'). Labels appear in the status bar and tool call display.",
|
|
2761
2760
|
`Use preset to apply a delegation configuration (built-in or custom via /brl-subagent preset). Preset values are defaults — explicit parameters override them. IMPORTANT: some presets restrict tools — e.g. outputFile requires the subagent's write tool, which security-auditor and code-reviewer exclude. Built-in presets: ${presetRestrictionSummary}. Custom presets are NOT listed here — inspect them via /brl-subagent preset before combining with outputFile or tool-dependent work. When combining a preset with outputFile or tool-dependent work, verify the preset allows the required tools.`,
|
|
2762
2761
|
buildTemplateGuideline(templateSummary),
|
|
2763
2762
|
"To retry a failed subagent, pass its run ID as retryRunId. The retry restores the recorded params - including background, gitMode, approvalMode, force - with explicit values on this call winning. Multi-step runs degrade to a single task. See the retryRunId description for the full field list.",
|
|
2764
|
-
"Set retryOnTimeout: true to automatically retry a subagent that times out. Only retries once — the second timeout is treated as a final failure.",
|
|
2763
|
+
"Set retryOnTimeout: true to automatically retry a FOREGROUND subagent that times out. Only retries once — the second timeout is treated as a final failure. Background runs are not auto-retried; re-dispatch with retryRunId.",
|
|
2765
2764
|
"Set background: true to run the subagent in the background without blocking. The tool returns immediately with an agent ID. With tasks, background fans out: every task starts as its own background agent, the call returns one ID per task in task order, and the conductor is woken once per agent as each finishes; chain and graph cannot be combined with background (rejected). Background runs wake the conductor with a structured completion message when they finish — do not poll: polling is only correct when completion notifications are disabled (completionNotify \"off\"); one status check as a stall check is legitimate.",
|
|
2766
2765
|
"",
|
|
2767
2766
|
"## Conductor Guardrails",
|
|
@@ -2771,7 +2770,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2771
2770
|
"2. **Thinking level**: Match thinking level to task complexity: off/minimal for trivial tasks (file listing, grep), low for refactoring/docs, medium for code review/debugging, high for security audits/complex debugging, xhigh for multi-step reasoning/novel problems.",
|
|
2772
2771
|
"3. **Git mode**: Use gitMode='branch' for tasks that create commits or PRs. Use gitMode='none' for read-only tasks.",
|
|
2773
2772
|
"4. **Tools**: Verify the subagent has the tools it needs. If the task writes files, ensure write and edit are not excluded. If the task runs commands, ensure bash is not excluded.",
|
|
2774
|
-
"5. **Timeout**: Set timeout based on task complexity. Simple: 30s. Medium: 60s. Complex: 120s+. xhigh thinking: at least 120s. Background runs
|
|
2773
|
+
"5. **Timeout**: Set timeout based on task complexity. Simple: 30s. Medium: 60s. Complex: 120s+. xhigh thinking: at least 120s. Background runs default to a 30-minute deadline when no timeout is set; an explicit timeout is honored.",
|
|
2775
2774
|
"",
|
|
2776
2775
|
"These guardrails prevent common misconfigurations. The extension also validates configuration before spawning (H1): tool warnings are surfaced in the returned result, but two mismatches are HARD errors that reject the delegation — outputFile with the write tool excluded, and a capability-critical task/toolset mismatch (a run/execute/test/compile/benchmark task without bash, or an exploration task with none of find/ls/grep/bash; pass force: true to override the capability class). Getting it right the first time is faster and more efficient.",
|
|
2777
2776
|
"",
|
|
@@ -2779,324 +2778,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
2779
2778
|
"The autoRoutePreset() function can automatically select the best preset based on task keywords. Consider using it for preset selection.",
|
|
2780
2779
|
"Prefer a preset when the delegation matches a standard shape — review/audit/test/docs; override per-call deliberately.",
|
|
2781
2780
|
],
|
|
2782
|
-
parameters:
|
|
2783
|
-
task: Type.Optional(Type.String({
|
|
2784
|
-
description: "Detailed description of the task for the subagent to complete (required for single mode, optional for chain/tasks/graph)",
|
|
2785
|
-
})),
|
|
2786
|
-
systemPrompt: Type.Optional(
|
|
2787
|
-
Type.String({
|
|
2788
|
-
description:
|
|
2789
|
-
"Custom system prompt or additional instructions for the subagent. " +
|
|
2790
|
-
"When inheritSystemPrompt is true (default), this is appended after the inherited prompt. " +
|
|
2791
|
-
"When inheritSystemPrompt is false, this replaces the inherited prompt entirely.",
|
|
2792
|
-
}),
|
|
2793
|
-
),
|
|
2794
|
-
inheritSystemPrompt: Type.Optional(
|
|
2795
|
-
Type.Boolean({
|
|
2796
|
-
description:
|
|
2797
|
-
"Whether to inherit the main agent's system prompt. " +
|
|
2798
|
-
"Default: true. Set to false to use only your custom systemPrompt, " +
|
|
2799
|
-
"or to avoid passing a large inherited prompt to the subagent.",
|
|
2800
|
-
}),
|
|
2801
|
-
),
|
|
2802
|
-
thinkingLevel: Type.Optional(
|
|
2803
|
-
Type.String({
|
|
2804
|
-
description:
|
|
2805
|
-
"Requested thinking level for this subagent call. " +
|
|
2806
|
-
"One of: off, minimal, low, medium, high, xhigh. " +
|
|
2807
|
-
"Capped at the user's configured maximum. If omitted, the user's configured level is used.",
|
|
2808
|
-
}),
|
|
2809
|
-
),
|
|
2810
|
-
outputFile: Type.Optional(
|
|
2811
|
-
Type.String({
|
|
2812
|
-
description:
|
|
2813
|
-
"Project-relative path where the subagent should write its full findings. " +
|
|
2814
|
-
"When provided, the subagent is instructed to write complete output to this file " +
|
|
2815
|
-
"and return only a structured summary.",
|
|
2816
|
-
}),
|
|
2817
|
-
),
|
|
2818
|
-
label: Type.Optional(
|
|
2819
|
-
Type.String({
|
|
2820
|
-
description:
|
|
2821
|
-
"Human-readable label for this subagent (e.g., 'security-audit', 'docs-review'). " +
|
|
2822
|
-
"Appears in the status bar and tool call display. " +
|
|
2823
|
-
"Omit to use the default anonymous counter.",
|
|
2824
|
-
}),
|
|
2825
|
-
),
|
|
2826
|
-
model: Type.Optional(Type.String({ description: "Model override (provider/model-id). Defaults to the global subagent model." })),
|
|
2827
|
-
timeout: Type.Optional(
|
|
2828
|
-
Type.Number({
|
|
2829
|
-
description:
|
|
2830
|
-
"Maximum time in milliseconds the subagent is allowed to run. " +
|
|
2831
|
-
"If exceeded, the subagent is killed and an error is returned. " +
|
|
2832
|
-
"Background runs are additionally hard-capped at 30 minutes, so a larger value is silently shortened there.",
|
|
2833
|
-
}),
|
|
2834
|
-
),
|
|
2835
|
-
cwd: Type.Optional(
|
|
2836
|
-
Type.String({
|
|
2837
|
-
description:
|
|
2838
|
-
"Working directory for the subagent. Must be an existing directory. " +
|
|
2839
|
-
"Defaults to the conductor's current working directory.",
|
|
2840
|
-
}),
|
|
2841
|
-
),
|
|
2842
|
-
tools: Type.Optional(
|
|
2843
|
-
Type.Array(Type.String(), {
|
|
2844
|
-
description:
|
|
2845
|
-
"Explicit allowlist of tool names for the subagent. Maps to pi's --tools flag.",
|
|
2846
|
-
}),
|
|
2847
|
-
),
|
|
2848
|
-
excludeTools: Type.Optional(
|
|
2849
|
-
Type.Array(Type.String(), {
|
|
2850
|
-
description:
|
|
2851
|
-
"Tool names to disable for the subagent. Maps to pi's --exclude-tools flag.",
|
|
2852
|
-
}),
|
|
2853
|
-
),
|
|
2854
|
-
noBuiltinTools: Type.Optional(
|
|
2855
|
-
Type.Boolean({
|
|
2856
|
-
description:
|
|
2857
|
-
"Disable all built-in tools for the subagent. Maps to pi's --no-builtin-tools flag.",
|
|
2858
|
-
}),
|
|
2859
|
-
),
|
|
2860
|
-
preset: Type.Optional(
|
|
2861
|
-
Type.String({
|
|
2862
|
-
description:
|
|
2863
|
-
"Name of a saved delegation preset (created via /brl-subagent preset). " +
|
|
2864
|
-
"Preset values are used as defaults; explicit parameters on this call override them.",
|
|
2865
|
-
}),
|
|
2866
|
-
),
|
|
2867
|
-
template: Type.Optional(
|
|
2868
|
-
Type.String({
|
|
2869
|
-
description:
|
|
2870
|
-
"Name of a saved task template. Use with params to fill template slots. " +
|
|
2871
|
-
"Templates are file-backed: 9 builtin templates ship with the extension " +
|
|
2872
|
-
"(browse via /brl-subagent templates); override or add via .md files in " +
|
|
2873
|
-
"~/.pi/agent/brl-subagent/templates/ or .pi/brl-subagent/templates/ " +
|
|
2874
|
-
"(project-local wins over user-global over builtin).",
|
|
2875
|
-
}),
|
|
2876
|
-
),
|
|
2877
|
-
params: Type.Optional(
|
|
2878
|
-
Type.Record(Type.String(), Type.String(), {
|
|
2879
|
-
description:
|
|
2880
|
-
"Parameter values for template ${param} slots. " +
|
|
2881
|
-
"Keys are param names, values are the substitution text.",
|
|
2882
|
-
}),
|
|
2883
|
-
),
|
|
2884
|
-
retryRunId: Type.Optional(
|
|
2885
|
-
Type.String({
|
|
2886
|
-
description:
|
|
2887
|
-
"ID of a previously failed subagent run to retry. " +
|
|
2888
|
-
"The retry rebuilds the parameter object from a fixed field set, falling back to the " +
|
|
2889
|
-
"original run's recorded values, with explicit values on this call winning: task, label, " +
|
|
2890
|
-
"model, preset, systemPrompt, inheritSystemPrompt, thinkingLevel, priority, outputFile, " +
|
|
2891
|
-
"timeout, cwd, tools, excludeTools, noBuiltinTools, background, gitMode, approvalMode, force. " +
|
|
2892
|
-
"The execution-shape fields ARE restored: a retried background run stays background (with its " +
|
|
2893
|
-
"completion wake), and a retried branch-mode run keeps its work branch, its approvalMode " +
|
|
2894
|
-
"gating and its force override. Qualify that by the effective gitMode - when neither the record " +
|
|
2895
|
-
"nor this call carries one, the configured default applies, which may still be 'branch'. " +
|
|
2896
|
-
"Not restored: chain/tasks/graph and params, so a retried multi-step run silently degrades to a " +
|
|
2897
|
-
"single task - re-issue it fresh. retryOnTimeout is explicit-only: never restored, honoured when passed. " +
|
|
2898
|
-
"template is asymmetric: the original's template is NOT restored, but a template passed on the " +
|
|
2899
|
-
"retry call DOES take effect (it is resolved before the retry merge). " +
|
|
2900
|
-
"Fan-out units (parallel/chain/graph origin) record no background, so retrying one is a single " +
|
|
2901
|
-
"foreground run - pass background: true for a background retry. " +
|
|
2902
|
-
"Only works with runs that ended in failure (exitCode != 0, timeout, error, or abort).",
|
|
2903
|
-
}),
|
|
2904
|
-
),
|
|
2905
|
-
gitMode: Type.Optional(
|
|
2906
|
-
Type.String({
|
|
2907
|
-
description:
|
|
2908
|
-
"Git integration mode for this subagent call. " +
|
|
2909
|
-
"'branch' creates a work branch, captures the diff, and switches back. " +
|
|
2910
|
-
"'none' (default) does nothing. Falls back to the configured default.",
|
|
2911
|
-
}),
|
|
2912
|
-
),
|
|
2913
|
-
retryOnTimeout: Type.Optional(
|
|
2914
|
-
Type.Boolean({
|
|
2915
|
-
description:
|
|
2916
|
-
"If true and the subagent times out, automatically retry with the same parameters. " +
|
|
2917
|
-
"Only retries once — the second timeout is treated as a final failure.",
|
|
2918
|
-
}),
|
|
2919
|
-
),
|
|
2920
|
-
approvalMode: Type.Optional(
|
|
2921
|
-
Type.String({
|
|
2922
|
-
description:
|
|
2923
|
-
"Change approval mode: auto (never ask), writes (ask when files changed), " +
|
|
2924
|
-
"always (ask every time). Default is user config (/brl-subagent approval).",
|
|
2925
|
-
}),
|
|
2926
|
-
),
|
|
2927
|
-
force: Type.Optional(
|
|
2928
|
-
Type.Boolean({
|
|
2929
|
-
description:
|
|
2930
|
-
"Override for capability pre-flight errors (default false). A dispatch is blocked when the task clearly needs a capability the resolved toolset lacks — " +
|
|
2931
|
-
"a run/execute/test/compile/benchmark task with no bash, or an exploration task (search/grep/find/list/locate/glob) with none of find/ls/grep/bash. " +
|
|
2932
|
-
"Set force: true to dispatch anyway: the mismatch is then delivered as a warning in the result instead of rejecting the call. " +
|
|
2933
|
-
"Warnings are surfaced either way. force never suppresses outputFile-without-write conflicts.",
|
|
2934
|
-
}),
|
|
2935
|
-
),
|
|
2936
|
-
background: Type.Optional(
|
|
2937
|
-
Type.Boolean({
|
|
2938
|
-
description:
|
|
2939
|
-
"Run the subagent in the background without blocking the conductor. " +
|
|
2940
|
-
"When true, the tool returns immediately with an agent ID. " +
|
|
2941
|
-
"The conductor is woken with a completion message; use get_subagent_result for post-wake retrieval and stall checks. " +
|
|
2942
|
-
"Supports a single task or the tasks array (a fan-out that starts one background agent per task; with tasks the call returns one ID per task and the conductor is woken once per agent as each finishes); combining background with chain or graph is rejected. " +
|
|
2943
|
-
"Default: false (blocking mode).",
|
|
2944
|
-
}),
|
|
2945
|
-
),
|
|
2946
|
-
priority: Type.Optional(
|
|
2947
|
-
Type.Union([
|
|
2948
|
-
Type.Literal("critical"),
|
|
2949
|
-
Type.Literal("high"),
|
|
2950
|
-
Type.Literal("normal"),
|
|
2951
|
-
Type.Literal("low"),
|
|
2952
|
-
], {
|
|
2953
|
-
description: "Concurrency priority for this delegation: critical, high, normal, or low. Higher-priority delegations queue ahead. Defaults to normal.",
|
|
2954
|
-
})
|
|
2955
|
-
),
|
|
2956
|
-
// Issue #114: NO per-step `priority` on chain[] — chain steps never
|
|
2957
|
-
// compete for concurrency slots (the chain holds ONE slot for its whole
|
|
2958
|
-
// duration); array order IS the priority.
|
|
2959
|
-
chain: Type.Optional(Type.Array(Type.Object({
|
|
2960
|
-
task: Type.String({ description: "Task description. Use {previous} to reference the previous step output." }),
|
|
2961
|
-
label: Type.Optional(Type.String({})),
|
|
2962
|
-
model: Type.Optional(Type.String({ description: "Model override for this step (provider/model-id). Defaults to the global subagent model." })),
|
|
2963
|
-
thinkingLevel: Type.Optional(Type.String({})),
|
|
2964
|
-
cwd: Type.Optional(Type.String({})),
|
|
2965
|
-
timeout: Type.Optional(Type.Number({})),
|
|
2966
|
-
outputFile: Type.Optional(Type.String({})),
|
|
2967
|
-
tools: Type.Optional(Type.Array(Type.String({}))),
|
|
2968
|
-
excludeTools: Type.Optional(Type.Array(Type.String({}))),
|
|
2969
|
-
noBuiltinTools: Type.Optional(Type.Boolean({})),
|
|
2970
|
-
systemPrompt: Type.Optional(Type.String({})),
|
|
2971
|
-
inheritSystemPrompt: Type.Optional(Type.Boolean({})),
|
|
2972
|
-
}), {
|
|
2973
|
-
description: "Sequential chain of tasks. Each step receives the previous step output via {previous} placeholder in the task string. Chain stops at the first failure. Max " + MAX_CHAIN_STEPS + " steps."
|
|
2974
|
-
})),
|
|
2975
|
-
tasks: Type.Optional(Type.Array(Type.Object({
|
|
2976
|
-
task: Type.String({ description: "Task description for this parallel subtask" }),
|
|
2977
|
-
label: Type.Optional(Type.String({})),
|
|
2978
|
-
model: Type.Optional(Type.String({ description: "Model override for this step (provider/model-id). Defaults to the global subagent model." })),
|
|
2979
|
-
thinkingLevel: Type.Optional(Type.String({})),
|
|
2980
|
-
priority: Type.Optional(
|
|
2981
|
-
Type.Union([
|
|
2982
|
-
Type.Literal("critical"),
|
|
2983
|
-
Type.Literal("high"),
|
|
2984
|
-
Type.Literal("normal"),
|
|
2985
|
-
Type.Literal("low"),
|
|
2986
|
-
], { description: "Concurrency priority for this subtask (overrides the call-level default)." })
|
|
2987
|
-
),
|
|
2988
|
-
cwd: Type.Optional(Type.String({})),
|
|
2989
|
-
timeout: Type.Optional(Type.Number({})),
|
|
2990
|
-
outputFile: Type.Optional(Type.String({})),
|
|
2991
|
-
tools: Type.Optional(Type.Array(Type.String({}))),
|
|
2992
|
-
excludeTools: Type.Optional(Type.Array(Type.String({}))),
|
|
2993
|
-
noBuiltinTools: Type.Optional(Type.Boolean({})),
|
|
2994
|
-
systemPrompt: Type.Optional(Type.String({})),
|
|
2995
|
-
inheritSystemPrompt: Type.Optional(Type.Boolean({})),
|
|
2996
|
-
}), {
|
|
2997
|
-
description: "Parallel tasks to execute concurrently. All tasks run regardless of individual failures. Max " + MAX_PARALLEL_TASKS + " tasks."
|
|
2998
|
-
})),
|
|
2999
|
-
graph: Type.Optional(Type.Array(Type.Object({
|
|
3000
|
-
id: Type.String({ description: "Unique identifier for this task node" }),
|
|
3001
|
-
task: Type.String({ description: "Task description. Use {<nodeId>} to reference output from another task (the referenced node's own id, which must be a word-character id - letters, digits, underscore; ids like 'step-1' or 'node.a' are not matched and the braces are left as literal text)." }),
|
|
3002
|
-
label: Type.Optional(Type.String({})),
|
|
3003
|
-
model: Type.Optional(Type.String({ description: "Model override for this step (provider/model-id). Defaults to the global subagent model." })),
|
|
3004
|
-
dependsOn: Type.Optional(Type.Array(Type.String({}), { description: "IDs of tasks that must complete before this one starts" })),
|
|
3005
|
-
thinkingLevel: Type.Optional(Type.String({})),
|
|
3006
|
-
priority: Type.Optional(
|
|
3007
|
-
Type.Union([
|
|
3008
|
-
Type.Literal("critical"),
|
|
3009
|
-
Type.Literal("high"),
|
|
3010
|
-
Type.Literal("normal"),
|
|
3011
|
-
Type.Literal("low"),
|
|
3012
|
-
], { description: "Concurrency priority for this subtask (overrides the call-level default)." })
|
|
3013
|
-
),
|
|
3014
|
-
cwd: Type.Optional(Type.String({})),
|
|
3015
|
-
timeout: Type.Optional(Type.Number({})),
|
|
3016
|
-
outputFile: Type.Optional(Type.String({})),
|
|
3017
|
-
tools: Type.Optional(Type.Array(Type.String({}))),
|
|
3018
|
-
excludeTools: Type.Optional(Type.Array(Type.String({}))),
|
|
3019
|
-
noBuiltinTools: Type.Optional(Type.Boolean({})),
|
|
3020
|
-
systemPrompt: Type.Optional(Type.String({})),
|
|
3021
|
-
inheritSystemPrompt: Type.Optional(Type.Boolean({})),
|
|
3022
|
-
}), {
|
|
3023
|
-
description: "Declare tasks with dependencies. The scheduler parallelizes independent tasks and sequences dependent ones. Max " + MAX_GRAPH_TASKS + " tasks."
|
|
3024
|
-
})),
|
|
3025
|
-
}),
|
|
2781
|
+
parameters: delegateTaskParamsSchema,
|
|
3026
2782
|
|
|
3027
2783
|
async execute(
|
|
3028
2784
|
_toolCallId: string,
|
|
3029
|
-
params:
|
|
3030
|
-
// Schema registers task as Type.Optional — required for single mode only,
|
|
3031
|
-
// omitted by chain/tasks/graph calls. Aligned with Static<TParams>.
|
|
3032
|
-
task?: string;
|
|
3033
|
-
label?: string;
|
|
3034
|
-
model?: string;
|
|
3035
|
-
preset?: string;
|
|
3036
|
-
systemPrompt?: string;
|
|
3037
|
-
inheritSystemPrompt?: boolean;
|
|
3038
|
-
thinkingLevel?: string;
|
|
3039
|
-
outputFile?: string;
|
|
3040
|
-
timeout?: number;
|
|
3041
|
-
cwd?: string;
|
|
3042
|
-
tools?: string[];
|
|
3043
|
-
excludeTools?: string[];
|
|
3044
|
-
noBuiltinTools?: boolean;
|
|
3045
|
-
template?: string;
|
|
3046
|
-
params?: Record<string, string>;
|
|
3047
|
-
retryRunId?: string;
|
|
3048
|
-
retryOnTimeout?: boolean;
|
|
3049
|
-
background?: boolean;
|
|
3050
|
-
force?: boolean;
|
|
3051
|
-
gitMode?: string;
|
|
3052
|
-
priority?: string;
|
|
3053
|
-
chain?: Array<{
|
|
3054
|
-
task: string;
|
|
3055
|
-
label?: string;
|
|
3056
|
-
model?: string;
|
|
3057
|
-
thinkingLevel?: string;
|
|
3058
|
-
cwd?: string;
|
|
3059
|
-
timeout?: number;
|
|
3060
|
-
outputFile?: string;
|
|
3061
|
-
tools?: string[];
|
|
3062
|
-
excludeTools?: string[];
|
|
3063
|
-
noBuiltinTools?: boolean;
|
|
3064
|
-
systemPrompt?: string;
|
|
3065
|
-
inheritSystemPrompt?: boolean;
|
|
3066
|
-
}>;
|
|
3067
|
-
tasks?: Array<{
|
|
3068
|
-
task: string;
|
|
3069
|
-
label?: string;
|
|
3070
|
-
model?: string;
|
|
3071
|
-
thinkingLevel?: string;
|
|
3072
|
-
priority?: string;
|
|
3073
|
-
cwd?: string;
|
|
3074
|
-
timeout?: number;
|
|
3075
|
-
outputFile?: string;
|
|
3076
|
-
tools?: string[];
|
|
3077
|
-
excludeTools?: string[];
|
|
3078
|
-
noBuiltinTools?: boolean;
|
|
3079
|
-
systemPrompt?: string;
|
|
3080
|
-
inheritSystemPrompt?: boolean;
|
|
3081
|
-
}>;
|
|
3082
|
-
graph?: Array<{
|
|
3083
|
-
id: string;
|
|
3084
|
-
task: string;
|
|
3085
|
-
label?: string;
|
|
3086
|
-
model?: string;
|
|
3087
|
-
dependsOn?: string[];
|
|
3088
|
-
thinkingLevel?: string;
|
|
3089
|
-
priority?: string;
|
|
3090
|
-
cwd?: string;
|
|
3091
|
-
timeout?: number;
|
|
3092
|
-
outputFile?: string;
|
|
3093
|
-
tools?: string[];
|
|
3094
|
-
excludeTools?: string[];
|
|
3095
|
-
noBuiltinTools?: boolean;
|
|
3096
|
-
systemPrompt?: string;
|
|
3097
|
-
inheritSystemPrompt?: boolean;
|
|
3098
|
-
}>;
|
|
3099
|
-
},
|
|
2785
|
+
params: DelegateTaskParams,
|
|
3100
2786
|
signal: AbortSignal | undefined,
|
|
3101
2787
|
onUpdate: AgentToolUpdateCallback<DelegateTaskDetails> | undefined,
|
|
3102
2788
|
ctx: ExtensionContext,
|
|
@@ -3204,7 +2890,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
3204
2890
|
if (params.retryRunId) {
|
|
3205
2891
|
const runEntry = state.findSpawnRunById(ctx, params.retryRunId);
|
|
3206
2892
|
if (runEntry) {
|
|
3207
|
-
params = resolveRetryParams(params, runEntry);
|
|
2893
|
+
params = resolveRetryParams(params, runEntry) as DelegateTaskParams;
|
|
3208
2894
|
} else {
|
|
3209
2895
|
// Issue #98: a silent no-op here made retries of background runs
|
|
3210
2896
|
// (which previously never wrote run entries) start as FRESH runs
|
|
@@ -4038,7 +3724,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4038
3724
|
label: "Steer Subagent",
|
|
4039
3725
|
description: [
|
|
4040
3726
|
"Send a steering message to a running background agent.",
|
|
4041
|
-
"The message
|
|
3727
|
+
"The message is delivered after the agent's current tool call finishes, before its next model call.",
|
|
4042
3728
|
"Use this to redirect an agent's work without restarting it.",
|
|
4043
3729
|
].join(" "),
|
|
4044
3730
|
parameters: Type.Object({
|
|
@@ -4053,7 +3739,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
4053
3739
|
const { steerAgent } = await import('./session-manager');
|
|
4054
3740
|
|
|
4055
3741
|
try {
|
|
4056
|
-
const agent = steerAgent(params.agent_id, params.message);
|
|
3742
|
+
const agent = await steerAgent(params.agent_id, params.message);
|
|
4057
3743
|
|
|
4058
3744
|
if (!agent) {
|
|
4059
3745
|
return {
|
package/src/logging.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Purpose: Leveled structured logging with file output and rotation under `.pi/subagent-logs/`.
|
|
1
2
|
/**
|
|
2
3
|
* brl-subagent — Structured Logging (F10)
|
|
3
4
|
*
|
|
@@ -41,8 +42,27 @@ const LOG_LEVELS: Record<LogLevel, number> = {
|
|
|
41
42
|
error: 3,
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
/** The level names BRL_LOG_LEVEL accepts (the keys of LOG_LEVELS). */
|
|
46
|
+
const LOG_LEVEL_NAMES: readonly LogLevel[] = ["debug", "info", "warn", "error"];
|
|
47
|
+
|
|
44
48
|
/** Current minimum log level — can be changed at runtime */
|
|
45
|
-
let minLevel: LogLevel =
|
|
49
|
+
let minLevel: LogLevel = resolveInitialLogLevel();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Issue #235: the log verbosity knob was never wired, so the three `log.debug()`
|
|
53
|
+
* call sites were permanently suppressed with no way to turn them on.
|
|
54
|
+
* `BRL_LOG_LEVEL` is read ONCE at module load (logger call sites are spread
|
|
55
|
+
* across the codebase, and re-reading env per call would be wasteful and
|
|
56
|
+
* non-deterministic mid-run). `setLogLevel()` remains the programmatic override.
|
|
57
|
+
* Anything outside debug|info|warn|error falls back to "info".
|
|
58
|
+
*/
|
|
59
|
+
function resolveInitialLogLevel(): LogLevel {
|
|
60
|
+
const raw = process.env.BRL_LOG_LEVEL?.trim().toLowerCase();
|
|
61
|
+
if (raw && (LOG_LEVEL_NAMES as readonly string[]).includes(raw)) {
|
|
62
|
+
return raw as LogLevel;
|
|
63
|
+
}
|
|
64
|
+
return "info";
|
|
65
|
+
}
|
|
46
66
|
|
|
47
67
|
// ---------------------------------------------------------------------------
|
|
48
68
|
// Logger
|
package/src/messaging.ts
CHANGED
package/src/metrics.ts
CHANGED