squads-cli 0.4.10 → 0.4.13

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