squads-cli 0.4.10 → 0.4.11

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/dist/index.d.ts CHANGED
@@ -1,15 +1,579 @@
1
1
  declare const version: string;
2
2
 
3
+ type EffortLevel = 'high' | 'medium' | 'low';
4
+ interface SquadContext {
5
+ mcp?: string[];
6
+ skills?: string[];
7
+ memory?: {
8
+ load?: string[];
9
+ };
10
+ model?: {
11
+ default?: string;
12
+ expensive?: string;
13
+ cheap?: string;
14
+ };
15
+ budget?: {
16
+ daily?: number;
17
+ weekly?: number;
18
+ perExecution?: number;
19
+ };
20
+ }
21
+ interface SquadFrontmatter {
22
+ name?: string;
23
+ mission?: string;
24
+ repo?: string;
25
+ stack?: string;
26
+ context?: SquadContext;
27
+ effort?: EffortLevel;
28
+ }
3
29
  interface Agent {
4
30
  name: string;
5
- model: string;
6
- tools: string[];
7
- trigger: 'manual' | 'scheduled' | 'event';
31
+ role: string;
32
+ trigger: string;
33
+ status?: string;
34
+ filePath?: string;
35
+ squad?: string;
36
+ effort?: EffortLevel;
37
+ }
38
+ interface Pipeline {
39
+ name: string;
40
+ agents: string[];
41
+ }
42
+ interface Goal {
43
+ description: string;
44
+ completed: boolean;
45
+ progress?: string;
46
+ metrics?: string[];
8
47
  }
9
48
  interface Squad {
10
49
  name: string;
50
+ mission: string;
11
51
  agents: Agent[];
12
- mission?: string;
52
+ pipelines: Pipeline[];
53
+ triggers: {
54
+ scheduled: string[];
55
+ event: string[];
56
+ manual: string[];
57
+ };
58
+ dependencies: string[];
59
+ outputPath: string;
60
+ goals: Goal[];
61
+ effort?: EffortLevel;
62
+ context?: SquadContext;
63
+ repo?: string;
64
+ stack?: string;
65
+ }
66
+ declare function findSquadsDir(): string | null;
67
+ declare function findProjectRoot(): string | null;
68
+ declare function listSquads(squadsDir: string): string[];
69
+ declare function listAgents(squadsDir: string, squadName?: string): Agent[];
70
+ declare function parseSquadFile(filePath: string): Squad;
71
+ declare function loadSquad(squadName: string): Squad | null;
72
+ declare function loadAgentDefinition(agentPath: string): string;
73
+ declare function addGoalToSquad(squadName: string, goal: string): boolean;
74
+ declare function updateGoalInSquad(squadName: string, goalIndex: number, updates: {
75
+ completed?: boolean;
76
+ progress?: string;
77
+ }): boolean;
78
+
79
+ /**
80
+ * Token estimation and tracking for context compression.
81
+ *
82
+ * Uses character-based heuristics for speed (no API calls needed).
83
+ * ~4 characters per token is a reasonable approximation for English text.
84
+ */
85
+ declare const RATIOS: {
86
+ readonly english: 4;
87
+ readonly code: 3.5;
88
+ readonly json: 3;
89
+ readonly mixed: 3.75;
90
+ };
91
+ type ContentType = keyof typeof RATIOS;
92
+ /**
93
+ * Estimate token count from text content.
94
+ *
95
+ * @param text - The text to estimate tokens for
96
+ * @param type - Content type hint for better accuracy
97
+ * @returns Estimated token count
98
+ */
99
+ declare function estimateTokens(text: string, type?: ContentType): number;
100
+ /**
101
+ * Estimate tokens for a message object (handles different formats).
102
+ */
103
+ declare function estimateMessageTokens(message: {
104
+ role?: string;
105
+ content?: string | Array<{
106
+ type: string;
107
+ text?: string;
108
+ }>;
109
+ }): number;
110
+ /**
111
+ * Token usage tracker for a session.
112
+ */
113
+ interface TokenTracker {
114
+ /** Total tokens used so far */
115
+ used: number;
116
+ /** Model's context limit */
117
+ limit: number;
118
+ /** Usage as percentage (0-1) */
119
+ percentage: number;
120
+ /** Breakdown by category */
121
+ breakdown: {
122
+ system: number;
123
+ user: number;
124
+ assistant: number;
125
+ tools: number;
126
+ };
127
+ }
128
+ /**
129
+ * Create a new token tracker for a session.
130
+ *
131
+ * @param model - Model name to determine context limit
132
+ * @returns Fresh token tracker
133
+ */
134
+ declare function createTracker(model?: string): TokenTracker;
135
+ /**
136
+ * Update tracker with new content.
137
+ *
138
+ * @param tracker - Tracker to update (mutated in place)
139
+ * @param content - Content to add
140
+ * @param category - Category for breakdown tracking
141
+ */
142
+ declare function updateTracker(tracker: TokenTracker, content: string, category?: keyof TokenTracker['breakdown']): void;
143
+ /**
144
+ * Check if compression is needed based on thresholds.
145
+ */
146
+ type CompressionLevel = 'none' | 'light' | 'medium' | 'heavy';
147
+ interface ThresholdConfig {
148
+ light: number;
149
+ medium: number;
150
+ heavy: number;
151
+ }
152
+ /**
153
+ * Determine what level of compression is needed.
154
+ *
155
+ * @param tracker - Current token tracker state
156
+ * @param thresholds - Custom thresholds (optional)
157
+ * @returns Compression level needed
158
+ */
159
+ declare function getCompressionLevel(tracker: TokenTracker, thresholds?: ThresholdConfig): CompressionLevel;
160
+ /**
161
+ * Format tracker status for display.
162
+ */
163
+ declare function formatTrackerStatus(tracker: TokenTracker): string;
164
+
165
+ /**
166
+ * File deduplication for context compression.
167
+ *
168
+ * Tracks file reads across a conversation and replaces duplicate
169
+ * reads with concise references to save tokens.
170
+ *
171
+ * Based on Cline's approach: keeping only the latest version of each
172
+ * file prevents LLM confusion during edit operations.
173
+ */
174
+ /**
175
+ * Record of a file read in the conversation.
176
+ */
177
+ interface FileReadRecord {
178
+ /** File path that was read */
179
+ path: string;
180
+ /** Turn index where the read occurred */
181
+ turnIndex: number;
182
+ /** Estimated token count of the content */
183
+ tokenCount: number;
184
+ /** Hash of content for change detection */
185
+ contentHash: string;
186
+ }
187
+ /**
188
+ * Tracks file reads across a conversation for deduplication.
189
+ */
190
+ declare class FileDeduplicator {
191
+ /** Map of file path to all reads of that file */
192
+ private reads;
193
+ /** Current turn index */
194
+ private currentTurn;
195
+ /**
196
+ * Record a file read.
197
+ *
198
+ * @param path - File path that was read
199
+ * @param content - File content that was read
200
+ */
201
+ trackRead(path: string, content: string): void;
202
+ /**
203
+ * Advance to next turn.
204
+ */
205
+ nextTurn(): void;
206
+ /**
207
+ * Get current turn index.
208
+ */
209
+ getTurn(): number;
210
+ /**
211
+ * Check if a file has been read before.
212
+ *
213
+ * @param path - File path to check
214
+ * @returns Previous read record if exists
215
+ */
216
+ getPreviousRead(path: string): FileReadRecord | undefined;
217
+ /**
218
+ * Get all files that have been read multiple times.
219
+ *
220
+ * @returns Map of path to read count
221
+ */
222
+ getDuplicateReads(): Map<string, number>;
223
+ /**
224
+ * Calculate potential token savings from deduplication.
225
+ */
226
+ getPotentialSavings(): number;
227
+ /**
228
+ * Generate a deduplication reference message.
229
+ *
230
+ * @param path - File path
231
+ * @param previousTurn - Turn where file was previously read
232
+ */
233
+ static createReference(path: string, previousTurn: number): string;
234
+ /**
235
+ * Reset tracker state.
236
+ */
237
+ reset(): void;
238
+ /**
239
+ * Get statistics for debugging.
240
+ */
241
+ getStats(): {
242
+ filesTracked: number;
243
+ totalReads: number;
244
+ duplicateReads: number;
245
+ potentialSavings: number;
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Token-based pruning for context compression.
251
+ *
252
+ * Based on OpenCode's approach: protect recent tool outputs (40K tokens)
253
+ * while pruning older outputs that exceed the threshold.
254
+ *
255
+ * Key insight: Recent context is critical for coherence. Older tool
256
+ * outputs can be removed entirely without summarization.
257
+ */
258
+ /**
259
+ * Configuration for token pruning.
260
+ */
261
+ interface PruneConfig {
262
+ /** Tokens to protect from pruning (recent window). Default: 40000 */
263
+ protectRecent: number;
264
+ /** Minimum tokens that must be prunable before we prune. Default: 20000 */
265
+ minimumPrunable: number;
266
+ /** Tool types that should never be pruned */
267
+ protectedTools: string[];
268
+ }
269
+ /**
270
+ * Message structure for pruning.
271
+ */
272
+ interface PrunableMessage {
273
+ role: string;
274
+ content: string | Array<MessagePart>;
275
+ /** Internal: marks message as prunable */
276
+ _prunable?: boolean;
277
+ /** Internal: token count for this message */
278
+ _tokens?: number;
279
+ }
280
+ interface MessagePart {
281
+ type: string;
282
+ text?: string;
283
+ tool_use_id?: string;
284
+ name?: string;
285
+ /** Internal: marks part as pruned */
286
+ _pruned?: boolean;
287
+ /** Internal: timestamp when pruned */
288
+ _prunedAt?: number;
289
+ }
290
+ /**
291
+ * Token pruner for conversation context.
292
+ */
293
+ declare class TokenPruner {
294
+ private config;
295
+ constructor(config?: Partial<PruneConfig>);
296
+ /**
297
+ * Prune messages to reduce token count.
298
+ *
299
+ * Strategy:
300
+ * 1. Scan messages backward from newest to oldest
301
+ * 2. Accumulate tokens for tool outputs
302
+ * 3. Mark outputs beyond protection window for pruning
303
+ * 4. Replace pruned outputs with placeholders
304
+ *
305
+ * @param messages - Messages to prune
306
+ * @returns Pruned messages (new array, originals not mutated)
307
+ */
308
+ pruneMessages(messages: PrunableMessage[]): PrunableMessage[];
309
+ /**
310
+ * Analyze which messages can be pruned.
311
+ */
312
+ private analyzePrunability;
313
+ /**
314
+ * Apply pruning to messages before the protection index.
315
+ */
316
+ private applyPruning;
317
+ /**
318
+ * Create a pruned version of a message.
319
+ */
320
+ private createPrunedMessage;
321
+ /**
322
+ * Check if a message is a tool result.
323
+ */
324
+ private isToolResult;
325
+ /**
326
+ * Check if a tool is in the protected list.
327
+ */
328
+ private isProtectedTool;
329
+ /**
330
+ * Extract tool name from a message.
331
+ */
332
+ private getToolName;
333
+ /**
334
+ * Get statistics about potential pruning.
335
+ */
336
+ getStats(messages: PrunableMessage[]): {
337
+ totalTokens: number;
338
+ prunableTokens: number;
339
+ protectedTokens: number;
340
+ savingsPercentage: number;
341
+ };
342
+ }
343
+
344
+ /**
345
+ * LLM-based summarization for heavy context compression.
346
+ *
347
+ * Based on OpenHands' Context Condenser approach:
348
+ * - Keep first N events (initial context)
349
+ * - Keep last M events (recent context)
350
+ * - Summarize the middle section via LLM
351
+ *
352
+ * This is the "last resort" compression - only used when at 95%+ context.
353
+ */
354
+ /**
355
+ * Configuration for LLM summarization.
356
+ */
357
+ interface SummaryConfig {
358
+ /** Number of messages to preserve from start. Default: 4 */
359
+ keepFirst: number;
360
+ /** Number of messages to preserve from end. Default: 20 */
361
+ keepLast: number;
362
+ /** Model to use for summarization. Default: 'claude-3-5-haiku-20241022' */
363
+ model: string;
364
+ /** Maximum tokens for summary output. Default: 2000 */
365
+ maxSummaryTokens: number;
366
+ }
367
+ /**
368
+ * Message structure for summarization.
369
+ */
370
+ interface SummarizableMessage {
371
+ role: string;
372
+ content: string | Array<{
373
+ type: string;
374
+ text?: string;
375
+ }>;
376
+ }
377
+ /**
378
+ * LLM-based conversation summarizer.
379
+ */
380
+ declare class ConversationSummarizer {
381
+ private config;
382
+ private client;
383
+ constructor(config?: Partial<SummaryConfig>);
384
+ /**
385
+ * Get or create Anthropic client.
386
+ */
387
+ private getClient;
388
+ /**
389
+ * Summarize messages to reduce token count.
390
+ *
391
+ * Strategy:
392
+ * 1. Keep first N messages (system prompt, initial context)
393
+ * 2. Keep last M messages (recent context, current task)
394
+ * 3. Summarize everything in between
395
+ *
396
+ * @param messages - Messages to summarize
397
+ * @returns Summarized messages
398
+ */
399
+ summarize(messages: SummarizableMessage[]): Promise<SummarizableMessage[]>;
400
+ /**
401
+ * Generate a summary of the middle messages.
402
+ */
403
+ private generateSummary;
404
+ /**
405
+ * Format messages for the summarization prompt.
406
+ */
407
+ private formatMessagesForSummary;
408
+ /**
409
+ * Extract text content from a message.
410
+ */
411
+ private extractContent;
412
+ /**
413
+ * Truncate very long content for summary input.
414
+ */
415
+ private truncateContent;
416
+ /**
417
+ * Estimate the cost of summarization.
418
+ *
419
+ * @param messages - Messages that would be summarized
420
+ * @returns Estimated cost in USD
421
+ */
422
+ estimateCost(messages: SummarizableMessage[]): number;
423
+ /**
424
+ * Get statistics about potential summarization.
425
+ */
426
+ getStats(messages: SummarizableMessage[]): {
427
+ totalMessages: number;
428
+ wouldKeep: number;
429
+ wouldSummarize: number;
430
+ estimatedCost: number;
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Context Condenser - Main Pipeline
436
+ *
437
+ * Coordinates the three compression strategies:
438
+ * 1. Deduplication (70% threshold) - Replace duplicate file reads
439
+ * 2. Pruning (85% threshold) - Remove old tool outputs
440
+ * 3. Summarization (95% threshold) - LLM-based middle section summary
441
+ *
442
+ * Based on patterns from OpenCode, OpenHands, and Cline.
443
+ */
444
+
445
+ /**
446
+ * Configuration for the context condenser.
447
+ */
448
+ interface CondenserConfig {
449
+ /** Whether context compression is enabled */
450
+ enabled: boolean;
451
+ /** Threshold for light compression (deduplication) */
452
+ lightThreshold: number;
453
+ /** Threshold for medium compression (pruning) */
454
+ mediumThreshold: number;
455
+ /** Threshold for heavy compression (summarization) */
456
+ heavyThreshold: number;
457
+ /** Model context limit */
458
+ modelLimit: number;
459
+ /** Model name for tracking */
460
+ model: string;
461
+ /** Pruning configuration */
462
+ pruning: Partial<PruneConfig>;
463
+ /** Summarization configuration */
464
+ summarization: Partial<SummaryConfig>;
465
+ }
466
+ /**
467
+ * Message type for the condenser pipeline.
468
+ */
469
+ interface CondenserMessage extends PrunableMessage, SummarizableMessage {
470
+ role: string;
471
+ content: string | Array<{
472
+ type: string;
473
+ text?: string;
474
+ tool_use_id?: string;
475
+ name?: string;
476
+ }>;
477
+ }
478
+ /**
479
+ * Result of a condense operation.
480
+ */
481
+ interface CondenserResult {
482
+ /** Condensed messages */
483
+ messages: CondenserMessage[];
484
+ /** Compression level applied */
485
+ level: CompressionLevel;
486
+ /** Tokens before compression */
487
+ tokensBefore: number;
488
+ /** Tokens after compression */
489
+ tokensAfter: number;
490
+ /** Savings percentage */
491
+ savingsPercentage: number;
492
+ /** Duration in milliseconds */
493
+ durationMs: number;
494
+ }
495
+ /**
496
+ * Context Condenser - Main class.
497
+ */
498
+ declare class ContextCondenser {
499
+ private config;
500
+ private tracker;
501
+ private deduplicator;
502
+ private pruner;
503
+ private summarizer;
504
+ /** Metrics for tracking */
505
+ private metrics;
506
+ constructor(config?: Partial<CondenserConfig>);
507
+ /**
508
+ * Main entry point - condense messages if needed.
509
+ *
510
+ * @param messages - Current conversation messages
511
+ * @returns Condensed messages and metadata
512
+ */
513
+ condense(messages: CondenserMessage[]): Promise<CondenserResult>;
514
+ /**
515
+ * Apply light compression (deduplication).
516
+ */
517
+ private applyDeduplication;
518
+ /**
519
+ * Apply medium compression (pruning).
520
+ */
521
+ private applyPruning;
522
+ /**
523
+ * Apply heavy compression (summarization).
524
+ */
525
+ private applySummarization;
526
+ /**
527
+ * Update tracker from messages.
528
+ */
529
+ private updateTrackerFromMessages;
530
+ /**
531
+ * Create result object.
532
+ */
533
+ private createResult;
534
+ /**
535
+ * Estimate tokens for messages.
536
+ */
537
+ private estimateTokens;
538
+ /**
539
+ * Get current tracker status.
540
+ */
541
+ getStatus(): string;
542
+ /**
543
+ * Get tracker for external monitoring.
544
+ */
545
+ getTracker(): TokenTracker;
546
+ /**
547
+ * Get metrics.
548
+ */
549
+ getMetrics(): typeof this.metrics;
550
+ /**
551
+ * Check if compression is needed.
552
+ */
553
+ needsCompression(): CompressionLevel;
554
+ /**
555
+ * Reset condenser state.
556
+ */
557
+ reset(): void;
558
+ /**
559
+ * Get file deduplicator for integration with tool layer.
560
+ */
561
+ getDeduplicator(): FileDeduplicator;
13
562
  }
563
+ /**
564
+ * Create a condenser with squad-specific configuration.
565
+ */
566
+ declare function createCondenser(squadConfig?: {
567
+ condenser?: {
568
+ enabled?: boolean;
569
+ light_threshold?: number;
570
+ medium_threshold?: number;
571
+ heavy_threshold?: number;
572
+ protect_recent?: number;
573
+ };
574
+ model?: {
575
+ default?: string;
576
+ };
577
+ }): ContextCondenser;
14
578
 
15
- export { type Agent, type Squad, version };
579
+ export { type Agent, type CompressionLevel, type CondenserConfig, type CondenserMessage, type CondenserResult, ContextCondenser, ConversationSummarizer, type EffortLevel, FileDeduplicator, type Goal, type Pipeline, type Squad, type SquadContext, type SquadFrontmatter, type ThresholdConfig, TokenPruner, type TokenTracker, addGoalToSquad, createCondenser, createTracker, estimateMessageTokens, estimateTokens, findProjectRoot, findSquadsDir, formatTrackerStatus, getCompressionLevel, listAgents, listSquads, loadAgentDefinition, loadSquad, parseSquadFile, updateGoalInSquad, updateTracker, version };