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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +257 -0
  3. package/package.json +49 -0
  4. package/presets/code-reviewer.md +37 -0
  5. package/presets/data-analyst.md +33 -0
  6. package/presets/debugger.md +36 -0
  7. package/presets/dev-agent.md +30 -0
  8. package/presets/rapid-prototyper.md +25 -0
  9. package/presets/refactorer.md +34 -0
  10. package/presets/security-auditor.md +39 -0
  11. package/presets/tech-writer.md +33 -0
  12. package/presets/test-engineer.md +34 -0
  13. package/src/concurrency.ts +186 -0
  14. package/src/diff.ts +102 -0
  15. package/src/event-bus.ts +86 -0
  16. package/src/git.ts +187 -0
  17. package/src/history.ts +177 -0
  18. package/src/index.ts +3824 -0
  19. package/src/logging.ts +135 -0
  20. package/src/messaging.ts +164 -0
  21. package/src/metrics.ts +221 -0
  22. package/src/model-availability.ts +73 -0
  23. package/src/notify-completion.ts +207 -0
  24. package/src/params.ts +225 -0
  25. package/src/preflight.ts +150 -0
  26. package/src/presets.ts +425 -0
  27. package/src/prompt.ts +169 -0
  28. package/src/reports.ts +325 -0
  29. package/src/router.ts +133 -0
  30. package/src/runner.ts +877 -0
  31. package/src/sanitize.ts +365 -0
  32. package/src/scheduler.ts +187 -0
  33. package/src/session-manager.ts +1065 -0
  34. package/src/state.ts +590 -0
  35. package/src/templates.ts +469 -0
  36. package/src/transcript-tail.ts +327 -0
  37. package/src/transcript.ts +119 -0
  38. package/src/tui-format.ts +51 -0
  39. package/src/tui.ts +3103 -0
  40. package/src/types.ts +803 -0
  41. package/src/unit-run.ts +269 -0
  42. package/src/update.ts +109 -0
  43. package/src/validate.ts +302 -0
  44. package/templates/analyze-data.md +9 -0
  45. package/templates/code-review.md +10 -0
  46. package/templates/debug-issue.md +9 -0
  47. package/templates/implement-feature.md +9 -0
  48. package/templates/prototype.md +9 -0
  49. package/templates/refactor.md +9 -0
  50. package/templates/security-audit.md +10 -0
  51. package/templates/write-docs.md +9 -0
  52. 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
+ }