brl-subagent 2.3.4
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/LICENSE +21 -0
- package/README.md +257 -0
- package/package.json +49 -0
- package/presets/code-reviewer.md +37 -0
- package/presets/data-analyst.md +33 -0
- package/presets/debugger.md +36 -0
- package/presets/dev-agent.md +30 -0
- package/presets/rapid-prototyper.md +25 -0
- package/presets/refactorer.md +34 -0
- package/presets/security-auditor.md +39 -0
- package/presets/tech-writer.md +33 -0
- package/presets/test-engineer.md +34 -0
- package/src/concurrency.ts +186 -0
- package/src/diff.ts +102 -0
- package/src/event-bus.ts +86 -0
- package/src/git.ts +187 -0
- package/src/history.ts +177 -0
- package/src/index.ts +3824 -0
- package/src/logging.ts +135 -0
- package/src/messaging.ts +164 -0
- package/src/metrics.ts +221 -0
- package/src/model-availability.ts +73 -0
- package/src/notify-completion.ts +207 -0
- package/src/params.ts +225 -0
- package/src/preflight.ts +150 -0
- package/src/presets.ts +425 -0
- package/src/prompt.ts +169 -0
- package/src/reports.ts +325 -0
- package/src/router.ts +133 -0
- package/src/runner.ts +877 -0
- package/src/sanitize.ts +365 -0
- package/src/scheduler.ts +187 -0
- package/src/session-manager.ts +1065 -0
- package/src/state.ts +590 -0
- package/src/templates.ts +469 -0
- package/src/transcript-tail.ts +327 -0
- package/src/transcript.ts +119 -0
- package/src/tui-format.ts +51 -0
- package/src/tui.ts +3103 -0
- package/src/types.ts +803 -0
- package/src/unit-run.ts +269 -0
- package/src/update.ts +109 -0
- package/src/validate.ts +302 -0
- package/templates/analyze-data.md +9 -0
- package/templates/code-review.md +10 -0
- package/templates/debug-issue.md +9 -0
- package/templates/implement-feature.md +9 -0
- package/templates/prototype.md +9 -0
- package/templates/refactor.md +9 -0
- package/templates/security-audit.md +10 -0
- package/templates/write-docs.md +9 -0
- package/templates/write-tests.md +10 -0
package/src/history.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brl-subagent — Run History
|
|
3
|
+
*
|
|
4
|
+
* Manages subagent run records: creation, finalization, retry resolution,
|
|
5
|
+
* and duration formatting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SubagentRun, SubagentResult } from "./types";
|
|
9
|
+
import { isSubagentError, getFinalOutput, isSubagentRunShape, CUSTOM_ENTRY_TYPES, MAX_RUN_HISTORY_ENTRIES } from "./types";
|
|
10
|
+
import type { ExtensionContext, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Run pruning — R2: Disk usage policy
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prune a SubagentRun array to keep only the newest entries up to maxEntries.
|
|
18
|
+
* Uses startedAt timestamp for ordering (newest first). If maxEntries is 0,
|
|
19
|
+
* returns all entries (no limit). Returns a new array; does not mutate input.
|
|
20
|
+
*/
|
|
21
|
+
export function cleanupRuns(
|
|
22
|
+
runs: SubagentRun[],
|
|
23
|
+
maxEntries: number = MAX_RUN_HISTORY_ENTRIES,
|
|
24
|
+
): SubagentRun[] {
|
|
25
|
+
if (runs.length === 0) return [];
|
|
26
|
+
if (maxEntries === 0) return runs;
|
|
27
|
+
|
|
28
|
+
// Always sort newest-first for consistency, even when not truncating
|
|
29
|
+
const sorted = [...runs].sort(
|
|
30
|
+
(a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (sorted.length <= maxEntries) return sorted;
|
|
34
|
+
return sorted.slice(0, maxEntries);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Prune run entries in the current session to at most maxEntries.
|
|
39
|
+
* Gets all entries from the session, identifies run entries, prunes them via
|
|
40
|
+
* cleanupRuns, and writes a prune marker entry back to the session.
|
|
41
|
+
* Since session entries are append-only, actual old entries remain in the
|
|
42
|
+
* session file but are filtered out at read time by getRunEntries (state.ts).
|
|
43
|
+
* Returns the number of entries that were pruned.
|
|
44
|
+
*/
|
|
45
|
+
export function pruneSessionRuns(
|
|
46
|
+
ctx: ExtensionContext,
|
|
47
|
+
maxEntries: number = MAX_RUN_HISTORY_ENTRIES,
|
|
48
|
+
): number {
|
|
49
|
+
const entries = ctx.sessionManager.getEntries();
|
|
50
|
+
const runEntries: SubagentRun[] = [];
|
|
51
|
+
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (
|
|
54
|
+
entry.type === "custom" &&
|
|
55
|
+
entry.customType === CUSTOM_ENTRY_TYPES.run &&
|
|
56
|
+
entry.data &&
|
|
57
|
+
isSubagentRunShape(entry.data)
|
|
58
|
+
) {
|
|
59
|
+
runEntries.push(entry.data);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pruned = cleanupRuns(runEntries, maxEntries);
|
|
64
|
+
const prunedCount = runEntries.length - pruned.length;
|
|
65
|
+
if (prunedCount <= 0) return 0;
|
|
66
|
+
|
|
67
|
+
// Append a prune marker entry for reference (actual filtering is in getRunEntries)
|
|
68
|
+
// ctx.sessionManager is typed ReadonlySessionManager, but the runtime object
|
|
69
|
+
// IS the full SessionManager (runner exposes it unwrapped) — appendCustomEntry
|
|
70
|
+
// is the only way to persist a marker here (no `pi` handle in scope).
|
|
71
|
+
(ctx.sessionManager as SessionManager).appendCustomEntry(CUSTOM_ENTRY_TYPES.run + ":prune", {
|
|
72
|
+
prunedCount,
|
|
73
|
+
keptCount: pruned.length,
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return prunedCount;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Run record management
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export function createEmptyResult(): SubagentResult {
|
|
85
|
+
return {
|
|
86
|
+
messages: [],
|
|
87
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
88
|
+
exitCode: 0,
|
|
89
|
+
stderr: "",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Finalize a run record with results after the subagent completes.
|
|
95
|
+
*/
|
|
96
|
+
export function finalizeRunRecord(
|
|
97
|
+
run: SubagentRun,
|
|
98
|
+
result: SubagentResult,
|
|
99
|
+
finalOutput: string,
|
|
100
|
+
startTimestamp: number,
|
|
101
|
+
): void {
|
|
102
|
+
const error = isSubagentError(result);
|
|
103
|
+
run.status = error ? "failed" : "done";
|
|
104
|
+
run.finishedAt = new Date().toISOString();
|
|
105
|
+
run.durationMs = Date.now() - startTimestamp;
|
|
106
|
+
run.cost = result.usage.cost;
|
|
107
|
+
run.tokensIn = result.usage.input;
|
|
108
|
+
run.tokensOut = result.usage.output;
|
|
109
|
+
run.errorMessage = result.errorMessage;
|
|
110
|
+
run.outputSummary = finalOutput.slice(0, 200);
|
|
111
|
+
run.fullOutput = finalOutput || undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Retry parameter resolution
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Merge retry parameters: explicit params override original,
|
|
120
|
+
* but task falls back to the original task if not provided.
|
|
121
|
+
*/
|
|
122
|
+
export function resolveRetryParams(
|
|
123
|
+
params: {
|
|
124
|
+
// task is optional here by design: the retry path is reachable in
|
|
125
|
+
// chain/parallel/graph modes where the top-level task is legitimately
|
|
126
|
+
// absent — it falls back to the original run's task below.
|
|
127
|
+
task?: string;
|
|
128
|
+
label?: string;
|
|
129
|
+
model?: string;
|
|
130
|
+
preset?: string;
|
|
131
|
+
systemPrompt?: string;
|
|
132
|
+
inheritSystemPrompt?: boolean;
|
|
133
|
+
thinkingLevel?: string;
|
|
134
|
+
priority?: string;
|
|
135
|
+
outputFile?: string;
|
|
136
|
+
timeout?: number;
|
|
137
|
+
cwd?: string;
|
|
138
|
+
tools?: string[];
|
|
139
|
+
excludeTools?: string[];
|
|
140
|
+
noBuiltinTools?: boolean;
|
|
141
|
+
retryRunId?: string;
|
|
142
|
+
retryOnTimeout?: boolean;
|
|
143
|
+
},
|
|
144
|
+
run: SubagentRun,
|
|
145
|
+
): typeof params {
|
|
146
|
+
const orig = run.originalParams;
|
|
147
|
+
return {
|
|
148
|
+
task: params.task || run.task,
|
|
149
|
+
label: params.label ?? run.label,
|
|
150
|
+
model: params.model ?? orig?.model,
|
|
151
|
+
preset: params.preset ?? orig?.preset,
|
|
152
|
+
systemPrompt: params.systemPrompt ?? orig?.systemPrompt,
|
|
153
|
+
inheritSystemPrompt: params.inheritSystemPrompt ?? orig?.inheritSystemPrompt,
|
|
154
|
+
thinkingLevel: params.thinkingLevel ?? orig?.thinkingLevel,
|
|
155
|
+
priority: params.priority ?? orig?.priority,
|
|
156
|
+
outputFile: params.outputFile ?? orig?.outputFile,
|
|
157
|
+
timeout: params.timeout ?? orig?.timeout,
|
|
158
|
+
cwd: params.cwd ?? orig?.cwd,
|
|
159
|
+
tools: params.tools ?? orig?.tools,
|
|
160
|
+
excludeTools: params.excludeTools ?? orig?.excludeTools,
|
|
161
|
+
noBuiltinTools: params.noBuiltinTools ?? orig?.noBuiltinTools,
|
|
162
|
+
retryOnTimeout: params.retryOnTimeout,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Duration formatting
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
export function formatRunDuration(ms: number): string {
|
|
171
|
+
if (ms < 1000) return `${ms}ms`;
|
|
172
|
+
const sec = (ms / 1000).toFixed(1);
|
|
173
|
+
if (ms < 60_000) return `${sec}s`;
|
|
174
|
+
const min = Math.floor(ms / 60_000);
|
|
175
|
+
const secs = Math.round((ms % 60_000) / 1000);
|
|
176
|
+
return `${min}m ${secs}s`;
|
|
177
|
+
}
|