@compilr-dev/agents 0.3.25 → 0.3.27
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/agent.d.ts +71 -74
- package/dist/agent.js +147 -118
- package/package.json +1 -1
package/dist/agent.d.ts
CHANGED
|
@@ -256,6 +256,27 @@ export interface AgentConfig {
|
|
|
256
256
|
maxIterations: number;
|
|
257
257
|
toolCallCount: number;
|
|
258
258
|
}) => Promise<number | false>;
|
|
259
|
+
/**
|
|
260
|
+
* Callback when tool loop is detected (same tool called N times with identical input).
|
|
261
|
+
*
|
|
262
|
+
* When provided, the agent asks the user instead of throwing ToolLoopError.
|
|
263
|
+
* Return `true` to continue (reset the counter), `false` to stop.
|
|
264
|
+
*
|
|
265
|
+
* When not provided, ToolLoopError is thrown (backwards compatible).
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* onToolLoopDetected: async ({ toolName, consecutiveCalls }) => {
|
|
270
|
+
* const answer = await askUser(`${toolName} called ${consecutiveCalls} times. Continue?`);
|
|
271
|
+
* return answer === 'yes';
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
onToolLoopDetected?: (context: {
|
|
276
|
+
toolName: string;
|
|
277
|
+
consecutiveCalls: number;
|
|
278
|
+
input: Record<string, unknown>;
|
|
279
|
+
}) => Promise<boolean>;
|
|
259
280
|
/**
|
|
260
281
|
* Chat options (model, temperature, etc.)
|
|
261
282
|
*/
|
|
@@ -348,9 +369,9 @@ export interface AgentConfig {
|
|
|
348
369
|
*/
|
|
349
370
|
checkpointOnAbort?: boolean;
|
|
350
371
|
/**
|
|
351
|
-
*
|
|
372
|
+
* Pin manager options for critical information that survives context compaction.
|
|
352
373
|
*
|
|
353
|
-
*
|
|
374
|
+
* Pins are pieces of information that:
|
|
354
375
|
* - Never get compacted - Survive all context management
|
|
355
376
|
* - Always re-injected - Present in every LLM call
|
|
356
377
|
* - Scoped - Session, persistent, or temporary
|
|
@@ -359,7 +380,7 @@ export interface AgentConfig {
|
|
|
359
380
|
* ```typescript
|
|
360
381
|
* const agent = new Agent({
|
|
361
382
|
* provider,
|
|
362
|
-
*
|
|
383
|
+
* pins: {
|
|
363
384
|
* maxAnchors: 20,
|
|
364
385
|
* maxTokens: 2000,
|
|
365
386
|
* persistPath: '~/.myapp/anchors.json',
|
|
@@ -368,6 +389,8 @@ export interface AgentConfig {
|
|
|
368
389
|
* });
|
|
369
390
|
* ```
|
|
370
391
|
*/
|
|
392
|
+
pins?: AnchorManagerOptions;
|
|
393
|
+
/** @deprecated Use `pins` instead */
|
|
371
394
|
anchors?: AnchorManagerOptions;
|
|
372
395
|
/**
|
|
373
396
|
* Guardrail options for pattern-based safety checks.
|
|
@@ -848,6 +871,7 @@ export declare class Agent {
|
|
|
848
871
|
private readonly autoContextManagement;
|
|
849
872
|
private readonly onEvent?;
|
|
850
873
|
private readonly onIterationLimitReached?;
|
|
874
|
+
private readonly onToolLoopDetected?;
|
|
851
875
|
private readonly retryConfig;
|
|
852
876
|
private readonly checkpointer?;
|
|
853
877
|
private readonly _sessionId;
|
|
@@ -865,9 +889,9 @@ export declare class Agent {
|
|
|
865
889
|
*/
|
|
866
890
|
private readonly subAgents;
|
|
867
891
|
/**
|
|
868
|
-
*
|
|
892
|
+
* Pin manager for critical information that survives context compaction
|
|
869
893
|
*/
|
|
870
|
-
private readonly
|
|
894
|
+
private readonly pinManager?;
|
|
871
895
|
/**
|
|
872
896
|
* Guardrail manager for pattern-based safety checks
|
|
873
897
|
*/
|
|
@@ -1029,99 +1053,72 @@ export declare class Agent {
|
|
|
1029
1053
|
*/
|
|
1030
1054
|
recordUsage(model: string, provider: string, tokens: TokenUsage): void;
|
|
1031
1055
|
/**
|
|
1032
|
-
* Add
|
|
1056
|
+
* Add a pin (critical information that survives context compaction).
|
|
1033
1057
|
*
|
|
1034
|
-
*
|
|
1058
|
+
* Pins are injected into every LLM call and never get compacted.
|
|
1035
1059
|
* Use them for information that must not be forgotten.
|
|
1036
1060
|
*
|
|
1037
|
-
* @param input -
|
|
1038
|
-
* @returns The created
|
|
1061
|
+
* @param input - Pin input (uses AnchorInput type)
|
|
1062
|
+
* @returns The created pin, or undefined if pins are not enabled
|
|
1039
1063
|
*
|
|
1040
1064
|
* @example
|
|
1041
1065
|
* ```typescript
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
* content: 'This session we implemented: edit tool, grep tool',
|
|
1045
|
-
* priority: 'critical',
|
|
1046
|
-
* scope: 'session',
|
|
1047
|
-
* });
|
|
1048
|
-
*
|
|
1049
|
-
* // Safety anchor - persisted across sessions
|
|
1050
|
-
* agent.addAnchor({
|
|
1051
|
-
* content: 'Never delete files in /important without confirmation',
|
|
1052
|
-
* priority: 'safety',
|
|
1053
|
-
* scope: 'persistent',
|
|
1054
|
-
* });
|
|
1055
|
-
*
|
|
1056
|
-
* // Temporary anchor - expires after 1 hour
|
|
1057
|
-
* agent.addAnchor({
|
|
1058
|
-
* content: 'Currently working on feature X',
|
|
1066
|
+
* agent.addPin({
|
|
1067
|
+
* content: 'Team roster: $default, $arch',
|
|
1059
1068
|
* priority: 'info',
|
|
1060
|
-
* scope: '
|
|
1061
|
-
* expiresAt: new Date(Date.now() + 60 * 60 * 1000),
|
|
1069
|
+
* scope: 'session',
|
|
1062
1070
|
* });
|
|
1063
1071
|
* ```
|
|
1064
1072
|
*/
|
|
1065
|
-
|
|
1073
|
+
addPin(input: AnchorInput): Anchor | undefined;
|
|
1066
1074
|
/**
|
|
1067
|
-
* Get
|
|
1075
|
+
* Get a pin by ID
|
|
1068
1076
|
*/
|
|
1069
|
-
|
|
1077
|
+
getPin(id: string): Anchor | undefined;
|
|
1070
1078
|
/**
|
|
1071
|
-
* Get all
|
|
1072
|
-
*
|
|
1073
|
-
* @param options - Query options for filtering
|
|
1074
|
-
* @returns Array of anchors
|
|
1075
|
-
*
|
|
1076
|
-
* @example
|
|
1077
|
-
* ```typescript
|
|
1078
|
-
* // Get all anchors
|
|
1079
|
-
* const all = agent.getAnchors();
|
|
1080
|
-
*
|
|
1081
|
-
* // Get only safety anchors
|
|
1082
|
-
* const safety = agent.getAnchors({ priority: 'safety' });
|
|
1083
|
-
*
|
|
1084
|
-
* // Get session anchors with specific tags
|
|
1085
|
-
* const tagged = agent.getAnchors({ scope: 'session', tags: ['files'] });
|
|
1086
|
-
* ```
|
|
1079
|
+
* Get all pins, optionally filtered
|
|
1087
1080
|
*/
|
|
1088
|
-
|
|
1081
|
+
getPins(options?: AnchorQueryOptions): Anchor[];
|
|
1089
1082
|
/**
|
|
1090
|
-
* Check if
|
|
1083
|
+
* Check if a pin exists
|
|
1091
1084
|
*/
|
|
1092
|
-
|
|
1085
|
+
hasPin(id: string): boolean;
|
|
1093
1086
|
/**
|
|
1094
|
-
* Remove
|
|
1087
|
+
* Remove a pin by ID
|
|
1095
1088
|
*
|
|
1096
|
-
* @returns true if
|
|
1089
|
+
* @returns true if pin was removed, false if not found
|
|
1097
1090
|
*/
|
|
1098
|
-
|
|
1091
|
+
removePin(id: string): boolean;
|
|
1099
1092
|
/**
|
|
1100
|
-
* Clear
|
|
1101
|
-
*
|
|
1102
|
-
* @param options - Clear options for filtering which anchors to remove
|
|
1103
|
-
* @returns Number of anchors removed
|
|
1104
|
-
*
|
|
1105
|
-
* @example
|
|
1106
|
-
* ```typescript
|
|
1107
|
-
* // Clear all session anchors
|
|
1108
|
-
* agent.clearAnchors({ scope: 'session' });
|
|
1109
|
-
*
|
|
1110
|
-
* // Clear expired temporary anchors
|
|
1111
|
-
* agent.clearAnchors({ expiredOnly: true });
|
|
1093
|
+
* Clear pins based on criteria
|
|
1112
1094
|
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
* ```
|
|
1095
|
+
* @param options - Clear options for filtering which pins to remove
|
|
1096
|
+
* @returns Number of pins removed
|
|
1116
1097
|
*/
|
|
1117
|
-
|
|
1098
|
+
clearPins(options?: AnchorClearOptions): number;
|
|
1118
1099
|
/**
|
|
1119
|
-
* Get the
|
|
1100
|
+
* Get the pin manager (if configured)
|
|
1120
1101
|
*/
|
|
1121
|
-
|
|
1102
|
+
getPinManager(): AnchorManager | undefined;
|
|
1122
1103
|
/**
|
|
1123
|
-
* Check if
|
|
1104
|
+
* Check if pins are enabled
|
|
1124
1105
|
*/
|
|
1106
|
+
hasPins(): boolean;
|
|
1107
|
+
/** @deprecated Use addPin() instead */
|
|
1108
|
+
addAnchor(input: AnchorInput): Anchor | undefined;
|
|
1109
|
+
/** @deprecated Use getPin() instead */
|
|
1110
|
+
getAnchor(id: string): Anchor | undefined;
|
|
1111
|
+
/** @deprecated Use getPins() instead */
|
|
1112
|
+
getAnchors(options?: AnchorQueryOptions): Anchor[];
|
|
1113
|
+
/** @deprecated Use hasPin() instead */
|
|
1114
|
+
hasAnchor(id: string): boolean;
|
|
1115
|
+
/** @deprecated Use removePin() instead */
|
|
1116
|
+
removeAnchor(id: string): boolean;
|
|
1117
|
+
/** @deprecated Use clearPins() instead */
|
|
1118
|
+
clearAnchors(options?: AnchorClearOptions): number;
|
|
1119
|
+
/** @deprecated Use getPinManager() instead */
|
|
1120
|
+
getAnchorManager(): AnchorManager | undefined;
|
|
1121
|
+
/** @deprecated Use hasPins() instead */
|
|
1125
1122
|
hasAnchors(): boolean;
|
|
1126
1123
|
/**
|
|
1127
1124
|
* Add a custom guardrail
|
|
@@ -1657,8 +1654,8 @@ export declare class Agent {
|
|
|
1657
1654
|
* Generate a summary of messages using the LLM provider.
|
|
1658
1655
|
* Used for context summarization when approaching limits.
|
|
1659
1656
|
*
|
|
1660
|
-
* If
|
|
1661
|
-
* information related to
|
|
1657
|
+
* If pins are configured, the summary will prioritize keeping
|
|
1658
|
+
* information related to pinned content (pin-weighted summarization).
|
|
1662
1659
|
*/
|
|
1663
1660
|
private generateSummary;
|
|
1664
1661
|
/**
|
package/dist/agent.js
CHANGED
|
@@ -50,6 +50,7 @@ export class Agent {
|
|
|
50
50
|
autoContextManagement;
|
|
51
51
|
onEvent;
|
|
52
52
|
onIterationLimitReached;
|
|
53
|
+
onToolLoopDetected;
|
|
53
54
|
// Retry configuration
|
|
54
55
|
retryConfig;
|
|
55
56
|
// State management
|
|
@@ -69,9 +70,9 @@ export class Agent {
|
|
|
69
70
|
*/
|
|
70
71
|
subAgents = new Map();
|
|
71
72
|
/**
|
|
72
|
-
*
|
|
73
|
+
* Pin manager for critical information that survives context compaction
|
|
73
74
|
*/
|
|
74
|
-
|
|
75
|
+
pinManager;
|
|
75
76
|
/**
|
|
76
77
|
* Guardrail manager for pattern-based safety checks
|
|
77
78
|
*/
|
|
@@ -139,15 +140,18 @@ export class Agent {
|
|
|
139
140
|
}
|
|
140
141
|
this.onEvent = config.onEvent;
|
|
141
142
|
this.onIterationLimitReached = config.onIterationLimitReached;
|
|
143
|
+
this.onToolLoopDetected = config.onToolLoopDetected;
|
|
142
144
|
// State management
|
|
143
145
|
this.checkpointer = config.checkpointer;
|
|
144
146
|
this._sessionId = config.sessionId ?? generateSessionId();
|
|
145
147
|
this.autoCheckpoint = config.autoCheckpoint ?? false;
|
|
146
148
|
this.checkpointOnAbort = config.checkpointOnAbort ?? false;
|
|
147
149
|
this._createdAt = new Date().toISOString();
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
// Pin management (backwards compat: accept `anchors` as alias for `pins`)
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
152
|
+
const pinConfig = config.pins ?? config.anchors;
|
|
153
|
+
if (pinConfig !== undefined) {
|
|
154
|
+
this.pinManager = new AnchorManager(pinConfig);
|
|
151
155
|
}
|
|
152
156
|
// Guardrail management
|
|
153
157
|
if (config.guardrails !== undefined) {
|
|
@@ -427,128 +431,118 @@ export class Agent {
|
|
|
427
431
|
this.usageTracker?.record({ model, provider, tokens });
|
|
428
432
|
}
|
|
429
433
|
// ==========================================================================
|
|
430
|
-
//
|
|
434
|
+
// Pin Management - Critical information that survives context compaction
|
|
431
435
|
// ==========================================================================
|
|
432
436
|
/**
|
|
433
|
-
* Add
|
|
437
|
+
* Add a pin (critical information that survives context compaction).
|
|
434
438
|
*
|
|
435
|
-
*
|
|
439
|
+
* Pins are injected into every LLM call and never get compacted.
|
|
436
440
|
* Use them for information that must not be forgotten.
|
|
437
441
|
*
|
|
438
|
-
* @param input -
|
|
439
|
-
* @returns The created
|
|
442
|
+
* @param input - Pin input (uses AnchorInput type)
|
|
443
|
+
* @returns The created pin, or undefined if pins are not enabled
|
|
440
444
|
*
|
|
441
445
|
* @example
|
|
442
446
|
* ```typescript
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* content: 'This session we implemented: edit tool, grep tool',
|
|
446
|
-
* priority: 'critical',
|
|
447
|
-
* scope: 'session',
|
|
448
|
-
* });
|
|
449
|
-
*
|
|
450
|
-
* // Safety anchor - persisted across sessions
|
|
451
|
-
* agent.addAnchor({
|
|
452
|
-
* content: 'Never delete files in /important without confirmation',
|
|
453
|
-
* priority: 'safety',
|
|
454
|
-
* scope: 'persistent',
|
|
455
|
-
* });
|
|
456
|
-
*
|
|
457
|
-
* // Temporary anchor - expires after 1 hour
|
|
458
|
-
* agent.addAnchor({
|
|
459
|
-
* content: 'Currently working on feature X',
|
|
447
|
+
* agent.addPin({
|
|
448
|
+
* content: 'Team roster: $default, $arch',
|
|
460
449
|
* priority: 'info',
|
|
461
|
-
* scope: '
|
|
462
|
-
* expiresAt: new Date(Date.now() + 60 * 60 * 1000),
|
|
450
|
+
* scope: 'session',
|
|
463
451
|
* });
|
|
464
452
|
* ```
|
|
465
453
|
*/
|
|
466
|
-
|
|
467
|
-
if (!this.
|
|
454
|
+
addPin(input) {
|
|
455
|
+
if (!this.pinManager)
|
|
468
456
|
return undefined;
|
|
469
|
-
const anchor = this.
|
|
457
|
+
const anchor = this.pinManager.add(input);
|
|
470
458
|
this.onEvent?.({ type: 'anchor_added', anchor });
|
|
471
459
|
return anchor;
|
|
472
460
|
}
|
|
473
461
|
/**
|
|
474
|
-
* Get
|
|
462
|
+
* Get a pin by ID
|
|
475
463
|
*/
|
|
476
|
-
|
|
477
|
-
return this.
|
|
464
|
+
getPin(id) {
|
|
465
|
+
return this.pinManager?.get(id);
|
|
478
466
|
}
|
|
479
467
|
/**
|
|
480
|
-
* Get all
|
|
481
|
-
*
|
|
482
|
-
* @param options - Query options for filtering
|
|
483
|
-
* @returns Array of anchors
|
|
484
|
-
*
|
|
485
|
-
* @example
|
|
486
|
-
* ```typescript
|
|
487
|
-
* // Get all anchors
|
|
488
|
-
* const all = agent.getAnchors();
|
|
489
|
-
*
|
|
490
|
-
* // Get only safety anchors
|
|
491
|
-
* const safety = agent.getAnchors({ priority: 'safety' });
|
|
492
|
-
*
|
|
493
|
-
* // Get session anchors with specific tags
|
|
494
|
-
* const tagged = agent.getAnchors({ scope: 'session', tags: ['files'] });
|
|
495
|
-
* ```
|
|
468
|
+
* Get all pins, optionally filtered
|
|
496
469
|
*/
|
|
497
|
-
|
|
498
|
-
return this.
|
|
470
|
+
getPins(options) {
|
|
471
|
+
return this.pinManager?.getAll(options) ?? [];
|
|
499
472
|
}
|
|
500
473
|
/**
|
|
501
|
-
* Check if
|
|
474
|
+
* Check if a pin exists
|
|
502
475
|
*/
|
|
503
|
-
|
|
504
|
-
return this.
|
|
476
|
+
hasPin(id) {
|
|
477
|
+
return this.pinManager?.has(id) ?? false;
|
|
505
478
|
}
|
|
506
479
|
/**
|
|
507
|
-
* Remove
|
|
480
|
+
* Remove a pin by ID
|
|
508
481
|
*
|
|
509
|
-
* @returns true if
|
|
482
|
+
* @returns true if pin was removed, false if not found
|
|
510
483
|
*/
|
|
511
|
-
|
|
512
|
-
if (!this.
|
|
484
|
+
removePin(id) {
|
|
485
|
+
if (!this.pinManager)
|
|
513
486
|
return false;
|
|
514
|
-
const removed = this.
|
|
487
|
+
const removed = this.pinManager.remove(id);
|
|
515
488
|
if (removed) {
|
|
516
489
|
this.onEvent?.({ type: 'anchor_removed', anchorId: id });
|
|
517
490
|
}
|
|
518
491
|
return removed;
|
|
519
492
|
}
|
|
520
493
|
/**
|
|
521
|
-
* Clear
|
|
522
|
-
*
|
|
523
|
-
* @param options - Clear options for filtering which anchors to remove
|
|
524
|
-
* @returns Number of anchors removed
|
|
525
|
-
*
|
|
526
|
-
* @example
|
|
527
|
-
* ```typescript
|
|
528
|
-
* // Clear all session anchors
|
|
529
|
-
* agent.clearAnchors({ scope: 'session' });
|
|
494
|
+
* Clear pins based on criteria
|
|
530
495
|
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* // Clear anchors with specific tags
|
|
535
|
-
* agent.clearAnchors({ tags: ['auto'] });
|
|
536
|
-
* ```
|
|
496
|
+
* @param options - Clear options for filtering which pins to remove
|
|
497
|
+
* @returns Number of pins removed
|
|
537
498
|
*/
|
|
538
|
-
|
|
539
|
-
return this.
|
|
499
|
+
clearPins(options) {
|
|
500
|
+
return this.pinManager?.clear(options) ?? 0;
|
|
540
501
|
}
|
|
541
502
|
/**
|
|
542
|
-
* Get the
|
|
503
|
+
* Get the pin manager (if configured)
|
|
543
504
|
*/
|
|
544
|
-
|
|
545
|
-
return this.
|
|
505
|
+
getPinManager() {
|
|
506
|
+
return this.pinManager;
|
|
546
507
|
}
|
|
547
508
|
/**
|
|
548
|
-
* Check if
|
|
509
|
+
* Check if pins are enabled
|
|
549
510
|
*/
|
|
511
|
+
hasPins() {
|
|
512
|
+
return this.pinManager !== undefined;
|
|
513
|
+
}
|
|
514
|
+
// --- Deprecated aliases (use pin methods instead) --------------------------
|
|
515
|
+
/** @deprecated Use addPin() instead */
|
|
516
|
+
addAnchor(input) {
|
|
517
|
+
return this.addPin(input);
|
|
518
|
+
}
|
|
519
|
+
/** @deprecated Use getPin() instead */
|
|
520
|
+
getAnchor(id) {
|
|
521
|
+
return this.getPin(id);
|
|
522
|
+
}
|
|
523
|
+
/** @deprecated Use getPins() instead */
|
|
524
|
+
getAnchors(options) {
|
|
525
|
+
return this.getPins(options);
|
|
526
|
+
}
|
|
527
|
+
/** @deprecated Use hasPin() instead */
|
|
528
|
+
hasAnchor(id) {
|
|
529
|
+
return this.hasPin(id);
|
|
530
|
+
}
|
|
531
|
+
/** @deprecated Use removePin() instead */
|
|
532
|
+
removeAnchor(id) {
|
|
533
|
+
return this.removePin(id);
|
|
534
|
+
}
|
|
535
|
+
/** @deprecated Use clearPins() instead */
|
|
536
|
+
clearAnchors(options) {
|
|
537
|
+
return this.clearPins(options);
|
|
538
|
+
}
|
|
539
|
+
/** @deprecated Use getPinManager() instead */
|
|
540
|
+
getAnchorManager() {
|
|
541
|
+
return this.getPinManager();
|
|
542
|
+
}
|
|
543
|
+
/** @deprecated Use hasPins() instead */
|
|
550
544
|
hasAnchors() {
|
|
551
|
-
return this.
|
|
545
|
+
return this.hasPins();
|
|
552
546
|
}
|
|
553
547
|
// ==========================================================================
|
|
554
548
|
// Guardrail Management - Pattern-based safety checks
|
|
@@ -1535,25 +1529,25 @@ export class Agent {
|
|
|
1535
1529
|
// Build messages: system prompt + history + new user message
|
|
1536
1530
|
let messages = [];
|
|
1537
1531
|
// Add system message if present
|
|
1538
|
-
// NOTE:
|
|
1532
|
+
// NOTE: Pins are appended to the system prompt (not a separate message)
|
|
1539
1533
|
// to avoid multiple system messages which can confuse Claude's role identity
|
|
1540
1534
|
let systemContent = this.systemPrompt;
|
|
1541
|
-
// Inject
|
|
1542
|
-
if (this.
|
|
1543
|
-
const
|
|
1544
|
-
if (
|
|
1545
|
-
// Insert
|
|
1535
|
+
// Inject pins into the system prompt (not as separate message)
|
|
1536
|
+
if (this.pinManager && this.pinManager.size > 0) {
|
|
1537
|
+
const pinsContent = this.pinManager.format();
|
|
1538
|
+
if (pinsContent) {
|
|
1539
|
+
// Insert pins BEFORE the role ending (if present) so role reinforcement stays at the end
|
|
1546
1540
|
const roleEndingMarker = '## YOUR ASSIGNED ROLE:';
|
|
1547
1541
|
const roleEndingIndex = systemContent.indexOf(roleEndingMarker);
|
|
1548
1542
|
if (roleEndingIndex > 0) {
|
|
1549
|
-
// Insert
|
|
1543
|
+
// Insert pins before the role ending
|
|
1550
1544
|
const beforeRole = systemContent.substring(0, roleEndingIndex);
|
|
1551
1545
|
const roleEnding = systemContent.substring(roleEndingIndex);
|
|
1552
|
-
systemContent = `${beforeRole}\n\n---\n\n##
|
|
1546
|
+
systemContent = `${beforeRole}\n\n---\n\n## Pinned Context (Critical Information)\n\n${pinsContent}\n\n---\n\n${roleEnding}`;
|
|
1553
1547
|
}
|
|
1554
1548
|
else {
|
|
1555
|
-
// No role ending, just append
|
|
1556
|
-
systemContent = `${systemContent}\n\n---\n\n##
|
|
1549
|
+
// No role ending, just append pins
|
|
1550
|
+
systemContent = `${systemContent}\n\n---\n\n## Pinned Context (Critical Information)\n\n${pinsContent}`;
|
|
1557
1551
|
}
|
|
1558
1552
|
}
|
|
1559
1553
|
}
|
|
@@ -2099,13 +2093,36 @@ export class Agent {
|
|
|
2099
2093
|
aborted = true;
|
|
2100
2094
|
break;
|
|
2101
2095
|
}
|
|
2096
|
+
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2097
|
+
toolCalls.push(toolCallEntry);
|
|
2098
|
+
iterationToolCalls.push(toolCallEntry);
|
|
2099
|
+
// Always push the tool_result BEFORE loop detection
|
|
2100
|
+
// so the conversation history stays valid if we throw
|
|
2101
|
+
messages.push(toolResultMsg);
|
|
2102
|
+
newMessages.push(toolResultMsg);
|
|
2102
2103
|
// Tool loop detection (still applies per-tool)
|
|
2103
2104
|
if (this.maxConsecutiveToolCalls > 0) {
|
|
2104
2105
|
const currentHash = hashToolCall(toolUse.name, toolUse.input);
|
|
2105
2106
|
if (currentHash === lastToolCallHash) {
|
|
2106
2107
|
consecutiveIdenticalCalls++;
|
|
2107
2108
|
if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
|
|
2108
|
-
|
|
2109
|
+
if (this.onToolLoopDetected) {
|
|
2110
|
+
// Ask user: continue or stop?
|
|
2111
|
+
const shouldContinue = await this.onToolLoopDetected({
|
|
2112
|
+
toolName: toolUse.name,
|
|
2113
|
+
consecutiveCalls: consecutiveIdenticalCalls,
|
|
2114
|
+
input: toolUse.input,
|
|
2115
|
+
});
|
|
2116
|
+
if (shouldContinue) {
|
|
2117
|
+
consecutiveIdenticalCalls = 0; // Reset counter
|
|
2118
|
+
}
|
|
2119
|
+
else {
|
|
2120
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
else {
|
|
2124
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2125
|
+
}
|
|
2109
2126
|
}
|
|
2110
2127
|
emit({
|
|
2111
2128
|
type: 'tool_loop_warning',
|
|
@@ -2118,11 +2135,6 @@ export class Agent {
|
|
|
2118
2135
|
consecutiveIdenticalCalls = 1;
|
|
2119
2136
|
}
|
|
2120
2137
|
}
|
|
2121
|
-
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2122
|
-
toolCalls.push(toolCallEntry);
|
|
2123
|
-
iterationToolCalls.push(toolCallEntry);
|
|
2124
|
-
messages.push(toolResultMsg);
|
|
2125
|
-
newMessages.push(toolResultMsg);
|
|
2126
2138
|
// Stamp for observation masking
|
|
2127
2139
|
if (this.observationMasker) {
|
|
2128
2140
|
const block = toolResultMsg.content[0];
|
|
@@ -2142,13 +2154,35 @@ export class Agent {
|
|
|
2142
2154
|
aborted = true;
|
|
2143
2155
|
break;
|
|
2144
2156
|
}
|
|
2157
|
+
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2158
|
+
toolCalls.push(toolCallEntry);
|
|
2159
|
+
iterationToolCalls.push(toolCallEntry);
|
|
2160
|
+
// Always push the tool_result BEFORE loop detection
|
|
2161
|
+
// so the conversation history stays valid if we throw
|
|
2162
|
+
messages.push(toolResultMsg);
|
|
2163
|
+
newMessages.push(toolResultMsg);
|
|
2145
2164
|
// Tool loop detection
|
|
2146
2165
|
if (this.maxConsecutiveToolCalls > 0) {
|
|
2147
2166
|
const currentHash = hashToolCall(toolUse.name, toolUse.input);
|
|
2148
2167
|
if (currentHash === lastToolCallHash) {
|
|
2149
2168
|
consecutiveIdenticalCalls++;
|
|
2150
2169
|
if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
|
|
2151
|
-
|
|
2170
|
+
if (this.onToolLoopDetected) {
|
|
2171
|
+
const shouldContinue = await this.onToolLoopDetected({
|
|
2172
|
+
toolName: toolUse.name,
|
|
2173
|
+
consecutiveCalls: consecutiveIdenticalCalls,
|
|
2174
|
+
input: toolUse.input,
|
|
2175
|
+
});
|
|
2176
|
+
if (shouldContinue) {
|
|
2177
|
+
consecutiveIdenticalCalls = 0;
|
|
2178
|
+
}
|
|
2179
|
+
else {
|
|
2180
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
else {
|
|
2184
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2185
|
+
}
|
|
2152
2186
|
}
|
|
2153
2187
|
emit({
|
|
2154
2188
|
type: 'tool_loop_warning',
|
|
@@ -2161,11 +2195,6 @@ export class Agent {
|
|
|
2161
2195
|
consecutiveIdenticalCalls = 1;
|
|
2162
2196
|
}
|
|
2163
2197
|
}
|
|
2164
|
-
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2165
|
-
toolCalls.push(toolCallEntry);
|
|
2166
|
-
iterationToolCalls.push(toolCallEntry);
|
|
2167
|
-
messages.push(toolResultMsg);
|
|
2168
|
-
newMessages.push(toolResultMsg);
|
|
2169
2198
|
// Stamp for observation masking
|
|
2170
2199
|
if (this.observationMasker) {
|
|
2171
2200
|
const block = toolResultMsg.content[0];
|
|
@@ -2498,25 +2527,25 @@ export class Agent {
|
|
|
2498
2527
|
* Generate a summary of messages using the LLM provider.
|
|
2499
2528
|
* Used for context summarization when approaching limits.
|
|
2500
2529
|
*
|
|
2501
|
-
* If
|
|
2502
|
-
* information related to
|
|
2530
|
+
* If pins are configured, the summary will prioritize keeping
|
|
2531
|
+
* information related to pinned content (pin-weighted summarization).
|
|
2503
2532
|
*/
|
|
2504
2533
|
async generateSummary(messages) {
|
|
2505
|
-
// Build
|
|
2534
|
+
// Build pin context if available (for weighted summarization)
|
|
2506
2535
|
let anchorContext = '';
|
|
2507
|
-
if (this.
|
|
2508
|
-
const
|
|
2509
|
-
if (
|
|
2510
|
-
const
|
|
2536
|
+
if (this.pinManager && this.pinManager.size > 0) {
|
|
2537
|
+
const pins = this.pinManager.getAll();
|
|
2538
|
+
if (pins.length > 0) {
|
|
2539
|
+
const pinItems = pins.map((a) => `- [${a.priority}] ${a.content}`).join('\n');
|
|
2511
2540
|
anchorContext = `
|
|
2512
|
-
## PRIORITY CONTEXT (
|
|
2541
|
+
## PRIORITY CONTEXT (Pinned)
|
|
2513
2542
|
|
|
2514
|
-
The following are
|
|
2515
|
-
PRIORITIZE keeping information that relates to these
|
|
2543
|
+
The following are pinned items that should be preserved in context. When summarizing,
|
|
2544
|
+
PRIORITIZE keeping information that relates to these pins:
|
|
2516
2545
|
|
|
2517
|
-
${
|
|
2546
|
+
${pinItems}
|
|
2518
2547
|
|
|
2519
|
-
Ensure the summary captures any conversation details related to these
|
|
2548
|
+
Ensure the summary captures any conversation details related to these pinned items.
|
|
2520
2549
|
`;
|
|
2521
2550
|
}
|
|
2522
2551
|
}
|
|
@@ -2525,7 +2554,7 @@ Ensure the summary captures any conversation details related to these anchors.
|
|
|
2525
2554
|
2. Important context established
|
|
2526
2555
|
3. Tasks completed or in progress
|
|
2527
2556
|
4. Any relevant file paths or code snippets mentioned
|
|
2528
|
-
${anchorContext ? '5. Information related to the priority
|
|
2557
|
+
${anchorContext ? '5. Information related to the pinned priority items below' : ''}
|
|
2529
2558
|
${anchorContext}
|
|
2530
2559
|
Keep the summary under 500 words.
|
|
2531
2560
|
|