pi-crew 0.5.2 → 0.5.6

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 (137) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +17 -1
  3. package/docs/architecture.md +2 -0
  4. package/docs/bugs/cross-session-notification-leakage.md +82 -0
  5. package/docs/coding-agent-optimization.md +268 -0
  6. package/docs/deep-review-report.md +384 -0
  7. package/docs/distillation/cybersecurity-patterns.md +294 -0
  8. package/docs/migration-v0.4-v0.5.md +208 -0
  9. package/docs/optimization-plan.md +642 -0
  10. package/docs/pi-crew-v0.5.5-audit-fix-plan.md +133 -0
  11. package/docs/pi-mono-opportunities.md +969 -0
  12. package/docs/pi-mono-review.md +291 -0
  13. package/docs/skills/REFERENCE.md +144 -0
  14. package/package.json +12 -9
  15. package/skills/artifact-analysis-loop/SKILL.md +302 -0
  16. package/skills/async-worker-recovery/SKILL.md +19 -1
  17. package/skills/child-pi-spawning/SKILL.md +19 -6
  18. package/skills/context-artifact-hygiene/SKILL.md +19 -2
  19. package/skills/delegation-patterns/SKILL.md +68 -3
  20. package/skills/detection-pipeline-design/SKILL.md +285 -0
  21. package/skills/event-log-tracing/SKILL.md +20 -6
  22. package/skills/git-master/SKILL.md +20 -6
  23. package/skills/hunting-investigation-loop/SKILL.md +401 -0
  24. package/skills/incident-playbook-construction/SKILL.md +383 -0
  25. package/skills/live-agent-lifecycle/SKILL.md +20 -6
  26. package/skills/mailbox-interactive/SKILL.md +19 -6
  27. package/skills/model-routing-context/SKILL.md +19 -1
  28. package/skills/multi-perspective-review/SKILL.md +19 -4
  29. package/skills/observability-reliability/SKILL.md +19 -2
  30. package/skills/orchestration/SKILL.md +20 -2
  31. package/skills/ownership-session-security/SKILL.md +20 -2
  32. package/skills/pi-extension-lifecycle/SKILL.md +20 -2
  33. package/skills/post-mortem/SKILL.md +7 -2
  34. package/skills/read-only-explorer/SKILL.md +20 -6
  35. package/skills/requirements-to-task-packet/SKILL.md +23 -3
  36. package/skills/resource-discovery-config/SKILL.md +20 -2
  37. package/skills/runtime-state-reader/SKILL.md +20 -2
  38. package/skills/safe-bash/SKILL.md +21 -6
  39. package/skills/scrutinize/SKILL.md +20 -2
  40. package/skills/secure-agent-orchestration-review/SKILL.md +29 -2
  41. package/skills/security-review/SKILL.md +560 -0
  42. package/skills/state-mutation-locking/SKILL.md +22 -2
  43. package/skills/systematic-debugging/SKILL.md +8 -6
  44. package/skills/threat-hypothesis-framework/SKILL.md +175 -0
  45. package/skills/ui-render-performance/SKILL.md +20 -2
  46. package/skills/verification-before-done/SKILL.md +17 -2
  47. package/skills/widget-rendering/SKILL.md +21 -6
  48. package/skills/workspace-isolation/SKILL.md +20 -6
  49. package/skills/worktree-isolation/SKILL.md +20 -6
  50. package/src/agents/agent-config.ts +40 -1
  51. package/src/benchmark/benchmark-runner.ts +45 -0
  52. package/src/benchmark/feedback-loop.ts +5 -0
  53. package/src/config/config.ts +32 -5
  54. package/src/config/role-tools.ts +82 -0
  55. package/src/config/suggestions.ts +8 -0
  56. package/src/config/types.ts +4 -0
  57. package/src/extension/async-notifier.ts +10 -1
  58. package/src/extension/crew-cleanup.ts +114 -0
  59. package/src/extension/cross-extension-rpc.ts +1 -1
  60. package/src/extension/notification-router.ts +18 -0
  61. package/src/extension/register.ts +27 -19
  62. package/src/extension/registration/subagent-tools.ts +1 -1
  63. package/src/extension/team-tool/anchor.ts +201 -0
  64. package/src/extension/team-tool/api.ts +2 -1
  65. package/src/extension/team-tool/auto-summarize.ts +154 -0
  66. package/src/extension/team-tool/run.ts +42 -7
  67. package/src/extension/team-tool.ts +44 -2
  68. package/src/hooks/registry.ts +1 -3
  69. package/src/observability/event-bus.ts +69 -0
  70. package/src/observability/event-to-metric.ts +0 -2
  71. package/src/runtime/anchor-manager.ts +473 -0
  72. package/src/runtime/async-runner.ts +8 -4
  73. package/src/runtime/auto-summarize.ts +350 -0
  74. package/src/runtime/background-runner.ts +10 -3
  75. package/src/runtime/budget-tracker.ts +354 -0
  76. package/src/runtime/chain-runner.ts +507 -0
  77. package/src/runtime/child-pi.ts +123 -35
  78. package/src/runtime/crash-recovery.ts +5 -4
  79. package/src/runtime/crew-agent-runtime.ts +1 -0
  80. package/src/runtime/custom-tools/irc-tool.ts +13 -0
  81. package/src/runtime/custom-tools/submit-result-tool.ts +3 -2
  82. package/src/runtime/delivery-coordinator.ts +10 -3
  83. package/src/runtime/dynamic-script-runner.ts +482 -0
  84. package/src/runtime/foreground-control.ts +87 -17
  85. package/src/runtime/handoff-manager.ts +589 -0
  86. package/src/runtime/hidden-handoff.ts +424 -0
  87. package/src/runtime/live-agent-manager.ts +20 -4
  88. package/src/runtime/live-session-runtime.ts +39 -4
  89. package/src/runtime/manifest-cache.ts +2 -1
  90. package/src/runtime/model-resolver.ts +16 -4
  91. package/src/runtime/phase-tracker.ts +373 -0
  92. package/src/runtime/pi-args.ts +11 -1
  93. package/src/runtime/pi-json-output.ts +31 -0
  94. package/src/runtime/pipeline-runner.ts +514 -0
  95. package/src/runtime/progress-tracker.ts +124 -0
  96. package/src/runtime/retry-runner.ts +354 -0
  97. package/src/runtime/sandbox.ts +252 -0
  98. package/src/runtime/scheduler.ts +7 -2
  99. package/src/runtime/skill-effectiveness.ts +473 -0
  100. package/src/runtime/skill-instructions.ts +37 -3
  101. package/src/runtime/subagent-manager.ts +1 -1
  102. package/src/runtime/task-graph.ts +11 -1
  103. package/src/runtime/task-runner.ts +92 -18
  104. package/src/runtime/team-runner.ts +13 -12
  105. package/src/runtime/tool-progress.ts +10 -3
  106. package/src/runtime/verification-gates.ts +367 -0
  107. package/src/schema/team-tool-schema.ts +37 -0
  108. package/src/skills/discover-skills.ts +5 -0
  109. package/src/state/active-run-registry.ts +9 -2
  110. package/src/state/contracts.ts +9 -0
  111. package/src/state/crew-init.ts +3 -3
  112. package/src/state/decision-ledger.ts +98 -55
  113. package/src/state/event-log-rotation.ts +2 -2
  114. package/src/state/event-log.ts +144 -10
  115. package/src/state/hook-instinct-bridge.ts +5 -5
  116. package/src/state/mailbox.ts +10 -0
  117. package/src/state/run-cache.ts +18 -8
  118. package/src/state/state-store.ts +3 -1
  119. package/src/state/types.ts +4 -0
  120. package/src/tools/safe-bash-extension.ts +1 -0
  121. package/src/tools/safe-bash.ts +152 -20
  122. package/src/types/new-api-types.ts +34 -0
  123. package/src/ui/agent-management-overlay.ts +5 -1
  124. package/src/ui/crew-widget.ts +29 -15
  125. package/src/ui/overlays/mailbox-detail-overlay.ts +13 -2
  126. package/src/ui/powerbar-publisher.ts +101 -7
  127. package/src/ui/tool-render.ts +15 -15
  128. package/src/ui/transcript-cache.ts +13 -0
  129. package/src/utils/bm25-search.ts +16 -8
  130. package/src/utils/env-filter.ts +8 -5
  131. package/src/utils/redaction.ts +169 -15
  132. package/src/utils/session-utils.ts +52 -0
  133. package/src/utils/sse-parser.ts +10 -1
  134. package/src/worktree/cleanup.ts +6 -1
  135. package/src/worktree/worktree-manager.ts +32 -13
  136. package/workflows/chain.workflow.md +252 -0
  137. package/workflows/pipeline.workflow.md +27 -0
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Budget Tracker — token budget tracking for team/subagent execution.
3
+ *
4
+ * Tracks token usage with configurable warning (default 80%) and abort
5
+ * (default 95%) thresholds. Provides spent(), remaining(), warning(),
6
+ * exhausted(), and createAbortSignal() for integration with team-runner.
7
+ *
8
+ * @file src/runtime/budget-tracker.ts
9
+ */
10
+
11
+ import { EventEmitter } from "node:events";
12
+
13
+ /** Budget configuration passed to TeamBudgetTracker constructor. */
14
+ export interface BudgetConfig {
15
+ /** Total token budget for the run. */
16
+ total: number;
17
+ /** Warning threshold as fraction of total (default: 0.8 = 80%). */
18
+ warningThreshold?: number;
19
+ /** Abort threshold as fraction of total (default: 0.95 = 95%). */
20
+ abortThreshold?: number;
21
+ }
22
+
23
+ /** Internal phase-level accounting for trackUsage breakdown. */
24
+ interface PhaseUsage {
25
+ phaseName: string;
26
+ tokens: number;
27
+ startTime: number;
28
+ }
29
+
30
+ /** Public usage record returned by trackUsage. */
31
+ export interface BudgetUsageRecord {
32
+ /** Total tokens spent after this update. */
33
+ totalSpent: number;
34
+ /** Tokens added in this update. */
35
+ delta: number;
36
+ /** Warning state after this update. */
37
+ isWarning: boolean;
38
+ /** Exhausted state after this update. */
39
+ isExhausted: boolean;
40
+ }
41
+
42
+ /** Event emitted when budget crosses thresholds. */
43
+ export interface BudgetEvent {
44
+ type: "budget:warning" | "budget:exhausted";
45
+ budget: BudgetSnapshot;
46
+ }
47
+
48
+ /** Snapshot of budget state for event payloads. */
49
+ export interface BudgetSnapshot {
50
+ total: number;
51
+ spent: number;
52
+ remaining: number;
53
+ percentUsed: number;
54
+ }
55
+
56
+ /**
57
+ * TeamBudgetTracker tracks token usage against a configurable budget.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const tracker = new TeamBudgetTracker({ total: 100000 });
62
+ * tracker.trackUsage(5000);
63
+ * console.log(tracker.spent()); // 5000
64
+ * console.log(tracker.warning()); // false (50% of 100k)
65
+ * ```
66
+ */
67
+ export class TeamBudgetTracker extends EventEmitter {
68
+ private used = 0;
69
+ private readonly total: number;
70
+ private readonly warningThreshold: number;
71
+ private readonly abortThreshold: number;
72
+ private phaseUsage: PhaseUsage[] = [];
73
+ private warningEmitted = false;
74
+ private exhaustedEmitted = false;
75
+ private abortController: AbortController | null = null;
76
+ private abortInterval: NodeJS.Timeout | null = null;
77
+
78
+ /**
79
+ * Create a new budget tracker.
80
+ * @param config - Budget configuration with total and optional thresholds.
81
+ */
82
+ constructor(config: BudgetConfig) {
83
+ super();
84
+ this.total = config.total;
85
+ this.warningThreshold = config.warningThreshold ?? 0.8;
86
+ this.abortThreshold = config.abortThreshold ?? 0.95;
87
+ }
88
+
89
+ /**
90
+ * Total budget tokens.
91
+ */
92
+ get totalBudget(): number {
93
+ return this.total;
94
+ }
95
+
96
+ /**
97
+ * Get total tokens spent.
98
+ */
99
+ spent(): number {
100
+ return this.used;
101
+ }
102
+
103
+ /**
104
+ * Get remaining tokens.
105
+ */
106
+ remaining(): number {
107
+ return this.total - this.used;
108
+ }
109
+
110
+ /**
111
+ * Percentage used as decimal (0-1).
112
+ */
113
+ percentUsed(): number {
114
+ return this.total > 0 ? this.used / this.total : 0;
115
+ }
116
+
117
+ /**
118
+ * Check if usage has crossed the warning threshold.
119
+ */
120
+ warning(): boolean {
121
+ return this.percentUsed() >= this.warningThreshold;
122
+ }
123
+
124
+ /**
125
+ * Check if usage has crossed the abort threshold.
126
+ */
127
+ exhausted(): boolean {
128
+ return this.percentUsed() >= this.abortThreshold;
129
+ }
130
+
131
+ /**
132
+ * Check if both warning and exhausted events have been emitted for current usage.
133
+ */
134
+ isWarningEmitted(): boolean {
135
+ return this.warningEmitted;
136
+ }
137
+
138
+ /**
139
+ * Check if exhausted event has been emitted.
140
+ */
141
+ isExhaustedEmitted(): boolean {
142
+ return this.exhaustedEmitted;
143
+ }
144
+
145
+ /**
146
+ * Track token usage and emit threshold-crossed events.
147
+ *
148
+ * @param tokens - Number of tokens to add to usage.
149
+ * @param phaseName - Optional phase name for breakdown tracking.
150
+ * @returns BudgetUsageRecord with updated totals and thresholds.
151
+ */
152
+ trackUsage(tokens: number, phaseName?: string): BudgetUsageRecord {
153
+ if (tokens < 0) {
154
+ throw new Error("trackUsage: tokens must be non-negative");
155
+ }
156
+
157
+ const prevSpent = this.used;
158
+ this.used += tokens;
159
+
160
+ // Phase-level tracking for breakdown reporting
161
+ if (phaseName) {
162
+ const existing = this.phaseUsage.find((p) => p.phaseName === phaseName);
163
+ if (existing) {
164
+ existing.tokens += tokens;
165
+ } else {
166
+ this.phaseUsage.push({ phaseName, tokens, startTime: Date.now() });
167
+ }
168
+ }
169
+
170
+ const snapshot: BudgetSnapshot = {
171
+ total: this.total,
172
+ spent: this.used,
173
+ remaining: this.remaining(),
174
+ percentUsed: this.percentUsed(),
175
+ };
176
+
177
+ // Emit warning event on threshold crossing
178
+ if (this.warning() && !this.warningEmitted) {
179
+ this.warningEmitted = true;
180
+ this.emit("warning", { type: "budget:warning", budget: snapshot } as BudgetEvent);
181
+ }
182
+
183
+ // Emit exhausted event on threshold crossing
184
+ if (this.exhausted() && !this.exhaustedEmitted) {
185
+ this.exhaustedEmitted = true;
186
+ this.emit("exhausted", { type: "budget:exhausted", budget: snapshot } as BudgetEvent);
187
+ }
188
+
189
+ return {
190
+ totalSpent: this.used,
191
+ delta: tokens,
192
+ isWarning: this.warning(),
193
+ isExhausted: this.exhausted(),
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Create an AbortSignal that fires when the budget is exhausted.
199
+ *
200
+ * The signal will be aborted automatically once the abort threshold
201
+ * is crossed. If already exhausted when called, the signal is
202
+ * immediately aborted.
203
+ *
204
+ * @returns AbortSignal that can be passed to subagent execution.
205
+ */
206
+ createAbortSignal(): AbortSignal {
207
+ // If already exhausted, return immediately aborted signal
208
+ if (this.exhausted()) {
209
+ const controller = new AbortController();
210
+ controller.abort(new Error("Budget exhausted before signal creation"));
211
+ return controller.signal;
212
+ }
213
+
214
+ // Clear any existing interval before creating new one
215
+ if (this.abortInterval) {
216
+ clearInterval(this.abortInterval);
217
+ this.abortInterval = null;
218
+ }
219
+
220
+ // Create controller and set up threshold check
221
+ this.abortController = new AbortController();
222
+
223
+ // Store reference for potential external abort
224
+ const tracker = this;
225
+
226
+ // Return a signal that checks threshold on each access
227
+ // The actual abort happens once exhausted() first returns true
228
+ const signal = this.abortController.signal;
229
+
230
+ // Set up interval check and store the ID for cleanup
231
+ this.abortInterval = setInterval(() => {
232
+ if (tracker.exhausted() && !signal.aborted) {
233
+ tracker.abortController!.abort(
234
+ new Error(`Budget exhausted: ${tracker.spent()}/${tracker.total}`),
235
+ );
236
+ if (tracker.abortInterval) {
237
+ clearInterval(tracker.abortInterval);
238
+ tracker.abortInterval = null;
239
+ }
240
+ }
241
+ }, 1000);
242
+
243
+ // Clean up interval when signal is aborted
244
+ const cleanup = (): void => {
245
+ if (tracker.abortInterval) {
246
+ clearInterval(tracker.abortInterval);
247
+ tracker.abortInterval = null;
248
+ }
249
+ };
250
+ signal.addEventListener("abort", cleanup, { once: true });
251
+
252
+ return signal;
253
+ }
254
+
255
+ /**
256
+ * Get phase-level usage breakdown.
257
+ * @returns Array of phase usage records.
258
+ */
259
+ getPhaseBreakdown(): { phaseName: string; tokens: number }[] {
260
+ return this.phaseUsage.map((p) => ({
261
+ phaseName: p.phaseName,
262
+ tokens: p.tokens,
263
+ }));
264
+ }
265
+
266
+ /**
267
+ * Reset usage for re-use (e.g., in testing or recovery scenarios).
268
+ * Does not reset emitted flags — use resetAll() for full reset.
269
+ */
270
+ resetUsage(): void {
271
+ this.used = 0;
272
+ this.phaseUsage = [];
273
+ }
274
+
275
+ /**
276
+ * Full reset including emitted flags.
277
+ */
278
+ resetAll(): void {
279
+ this.used = 0;
280
+ this.phaseUsage = [];
281
+ this.warningEmitted = false;
282
+ this.exhaustedEmitted = false;
283
+ this.abortController = null;
284
+ if (this.abortInterval) {
285
+ clearInterval(this.abortInterval);
286
+ this.abortInterval = null;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get current snapshot of budget state.
292
+ */
293
+ snapshot(): BudgetSnapshot {
294
+ return {
295
+ total: this.total,
296
+ spent: this.used,
297
+ remaining: this.remaining(),
298
+ percentUsed: this.percentUsed(),
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Dispose of resources (EventEmitter listeners, timers).
304
+ * Call this when the tracker is no longer needed.
305
+ */
306
+ dispose(): void {
307
+ this.removeAllListeners();
308
+ if (this.abortInterval) {
309
+ clearInterval(this.abortInterval);
310
+ this.abortInterval = null;
311
+ }
312
+ this.abortController = null;
313
+ this.used = 0;
314
+ this.phaseUsage = [];
315
+ this.warningEmitted = false;
316
+ this.exhaustedEmitted = false;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Create a BudgetConfig with reasonable defaults.
322
+ * @param total - Total token budget.
323
+ * @param warningThreshold - Warning threshold (default 0.8).
324
+ * @param abortThreshold - Abort threshold (default 0.95).
325
+ */
326
+ export function createBudgetConfig(
327
+ total: number,
328
+ warningThreshold = 0.8,
329
+ abortThreshold = 0.95,
330
+ ): BudgetConfig {
331
+ return { total, warningThreshold, abortThreshold };
332
+ }
333
+
334
+ /**
335
+ * Check if a budget config is valid.
336
+ * @param config - Budget configuration to validate.
337
+ */
338
+ export function validateBudgetConfig(config: BudgetConfig): { valid: boolean; error?: string } {
339
+ if (typeof config.total !== "number" || config.total <= 0) {
340
+ return { valid: false, error: "total must be a positive number" };
341
+ }
342
+ const warning = config.warningThreshold ?? 0.8;
343
+ const abort = config.abortThreshold ?? 0.95;
344
+ if (warning < 0 || warning > 1) {
345
+ return { valid: false, error: "warningThreshold must be between 0 and 1" };
346
+ }
347
+ if (abort < 0 || abort > 1) {
348
+ return { valid: false, error: "abortThreshold must be between 0 and 1" };
349
+ }
350
+ if (warning >= abort) {
351
+ return { valid: false, error: "warningThreshold must be less than abortThreshold" };
352
+ }
353
+ return { valid: true };
354
+ }