@compilr-dev/agents 0.0.1

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 (160) hide show
  1. package/README.md +1277 -0
  2. package/dist/agent.d.ts +1272 -0
  3. package/dist/agent.js +1912 -0
  4. package/dist/anchors/builtin.d.ts +24 -0
  5. package/dist/anchors/builtin.js +61 -0
  6. package/dist/anchors/index.d.ts +6 -0
  7. package/dist/anchors/index.js +5 -0
  8. package/dist/anchors/manager.d.ts +115 -0
  9. package/dist/anchors/manager.js +412 -0
  10. package/dist/anchors/types.d.ts +168 -0
  11. package/dist/anchors/types.js +10 -0
  12. package/dist/context/index.d.ts +12 -0
  13. package/dist/context/index.js +10 -0
  14. package/dist/context/manager.d.ts +224 -0
  15. package/dist/context/manager.js +770 -0
  16. package/dist/context/types.d.ts +377 -0
  17. package/dist/context/types.js +7 -0
  18. package/dist/costs/index.d.ts +8 -0
  19. package/dist/costs/index.js +7 -0
  20. package/dist/costs/tracker.d.ts +121 -0
  21. package/dist/costs/tracker.js +295 -0
  22. package/dist/costs/types.d.ts +157 -0
  23. package/dist/costs/types.js +8 -0
  24. package/dist/errors.d.ts +178 -0
  25. package/dist/errors.js +249 -0
  26. package/dist/guardrails/builtin.d.ts +27 -0
  27. package/dist/guardrails/builtin.js +223 -0
  28. package/dist/guardrails/index.d.ts +6 -0
  29. package/dist/guardrails/index.js +5 -0
  30. package/dist/guardrails/manager.d.ts +117 -0
  31. package/dist/guardrails/manager.js +288 -0
  32. package/dist/guardrails/types.d.ts +159 -0
  33. package/dist/guardrails/types.js +7 -0
  34. package/dist/hooks/index.d.ts +31 -0
  35. package/dist/hooks/index.js +29 -0
  36. package/dist/hooks/manager.d.ts +147 -0
  37. package/dist/hooks/manager.js +600 -0
  38. package/dist/hooks/types.d.ts +368 -0
  39. package/dist/hooks/types.js +12 -0
  40. package/dist/index.d.ts +45 -0
  41. package/dist/index.js +73 -0
  42. package/dist/mcp/client.d.ts +93 -0
  43. package/dist/mcp/client.js +287 -0
  44. package/dist/mcp/errors.d.ts +60 -0
  45. package/dist/mcp/errors.js +78 -0
  46. package/dist/mcp/index.d.ts +43 -0
  47. package/dist/mcp/index.js +45 -0
  48. package/dist/mcp/manager.d.ts +120 -0
  49. package/dist/mcp/manager.js +276 -0
  50. package/dist/mcp/tools.d.ts +54 -0
  51. package/dist/mcp/tools.js +99 -0
  52. package/dist/mcp/types.d.ts +150 -0
  53. package/dist/mcp/types.js +40 -0
  54. package/dist/memory/index.d.ts +8 -0
  55. package/dist/memory/index.js +7 -0
  56. package/dist/memory/loader.d.ts +114 -0
  57. package/dist/memory/loader.js +463 -0
  58. package/dist/memory/types.d.ts +182 -0
  59. package/dist/memory/types.js +8 -0
  60. package/dist/messages/index.d.ts +82 -0
  61. package/dist/messages/index.js +155 -0
  62. package/dist/permissions/index.d.ts +5 -0
  63. package/dist/permissions/index.js +4 -0
  64. package/dist/permissions/manager.d.ts +125 -0
  65. package/dist/permissions/manager.js +379 -0
  66. package/dist/permissions/types.d.ts +162 -0
  67. package/dist/permissions/types.js +7 -0
  68. package/dist/providers/claude.d.ts +90 -0
  69. package/dist/providers/claude.js +348 -0
  70. package/dist/providers/index.d.ts +8 -0
  71. package/dist/providers/index.js +11 -0
  72. package/dist/providers/mock.d.ts +133 -0
  73. package/dist/providers/mock.js +204 -0
  74. package/dist/providers/types.d.ts +168 -0
  75. package/dist/providers/types.js +4 -0
  76. package/dist/rate-limit/index.d.ts +45 -0
  77. package/dist/rate-limit/index.js +47 -0
  78. package/dist/rate-limit/limiter.d.ts +104 -0
  79. package/dist/rate-limit/limiter.js +326 -0
  80. package/dist/rate-limit/provider-wrapper.d.ts +112 -0
  81. package/dist/rate-limit/provider-wrapper.js +201 -0
  82. package/dist/rate-limit/retry.d.ts +108 -0
  83. package/dist/rate-limit/retry.js +287 -0
  84. package/dist/rate-limit/types.d.ts +181 -0
  85. package/dist/rate-limit/types.js +22 -0
  86. package/dist/rehearsal/file-analyzer.d.ts +22 -0
  87. package/dist/rehearsal/file-analyzer.js +351 -0
  88. package/dist/rehearsal/git-analyzer.d.ts +22 -0
  89. package/dist/rehearsal/git-analyzer.js +472 -0
  90. package/dist/rehearsal/index.d.ts +35 -0
  91. package/dist/rehearsal/index.js +36 -0
  92. package/dist/rehearsal/manager.d.ts +100 -0
  93. package/dist/rehearsal/manager.js +290 -0
  94. package/dist/rehearsal/types.d.ts +235 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/skills/index.d.ts +160 -0
  97. package/dist/skills/index.js +282 -0
  98. package/dist/state/agent-state.d.ts +41 -0
  99. package/dist/state/agent-state.js +88 -0
  100. package/dist/state/checkpointer.d.ts +110 -0
  101. package/dist/state/checkpointer.js +362 -0
  102. package/dist/state/errors.d.ts +66 -0
  103. package/dist/state/errors.js +88 -0
  104. package/dist/state/index.d.ts +35 -0
  105. package/dist/state/index.js +37 -0
  106. package/dist/state/serializer.d.ts +55 -0
  107. package/dist/state/serializer.js +172 -0
  108. package/dist/state/types.d.ts +312 -0
  109. package/dist/state/types.js +14 -0
  110. package/dist/tools/builtin/bash-output.d.ts +61 -0
  111. package/dist/tools/builtin/bash-output.js +90 -0
  112. package/dist/tools/builtin/bash.d.ts +150 -0
  113. package/dist/tools/builtin/bash.js +354 -0
  114. package/dist/tools/builtin/edit.d.ts +50 -0
  115. package/dist/tools/builtin/edit.js +215 -0
  116. package/dist/tools/builtin/glob.d.ts +62 -0
  117. package/dist/tools/builtin/glob.js +244 -0
  118. package/dist/tools/builtin/grep.d.ts +74 -0
  119. package/dist/tools/builtin/grep.js +363 -0
  120. package/dist/tools/builtin/index.d.ts +44 -0
  121. package/dist/tools/builtin/index.js +69 -0
  122. package/dist/tools/builtin/kill-shell.d.ts +44 -0
  123. package/dist/tools/builtin/kill-shell.js +80 -0
  124. package/dist/tools/builtin/read-file.d.ts +57 -0
  125. package/dist/tools/builtin/read-file.js +184 -0
  126. package/dist/tools/builtin/shell-manager.d.ts +176 -0
  127. package/dist/tools/builtin/shell-manager.js +337 -0
  128. package/dist/tools/builtin/task.d.ts +202 -0
  129. package/dist/tools/builtin/task.js +350 -0
  130. package/dist/tools/builtin/todo.d.ts +207 -0
  131. package/dist/tools/builtin/todo.js +453 -0
  132. package/dist/tools/builtin/utils.d.ts +27 -0
  133. package/dist/tools/builtin/utils.js +70 -0
  134. package/dist/tools/builtin/web-fetch.d.ts +96 -0
  135. package/dist/tools/builtin/web-fetch.js +290 -0
  136. package/dist/tools/builtin/write-file.d.ts +54 -0
  137. package/dist/tools/builtin/write-file.js +147 -0
  138. package/dist/tools/define.d.ts +60 -0
  139. package/dist/tools/define.js +65 -0
  140. package/dist/tools/index.d.ts +10 -0
  141. package/dist/tools/index.js +37 -0
  142. package/dist/tools/registry.d.ts +79 -0
  143. package/dist/tools/registry.js +151 -0
  144. package/dist/tools/types.d.ts +59 -0
  145. package/dist/tools/types.js +4 -0
  146. package/dist/tracing/hooks.d.ts +58 -0
  147. package/dist/tracing/hooks.js +377 -0
  148. package/dist/tracing/index.d.ts +51 -0
  149. package/dist/tracing/index.js +55 -0
  150. package/dist/tracing/logging.d.ts +78 -0
  151. package/dist/tracing/logging.js +310 -0
  152. package/dist/tracing/manager.d.ts +160 -0
  153. package/dist/tracing/manager.js +468 -0
  154. package/dist/tracing/otel.d.ts +102 -0
  155. package/dist/tracing/otel.js +246 -0
  156. package/dist/tracing/types.d.ts +346 -0
  157. package/dist/tracing/types.js +38 -0
  158. package/dist/utils/index.d.ts +23 -0
  159. package/dist/utils/index.js +44 -0
  160. package/package.json +79 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Built-in default safety anchors
3
+ *
4
+ * These anchors provide sensible safety defaults that can be disabled
5
+ * by setting includeDefaults: false in AnchorManagerOptions.
6
+ */
7
+ import type { Anchor } from './types.js';
8
+ /**
9
+ * Default safety anchors shipped with the library
10
+ */
11
+ export declare const DEFAULT_SAFETY_ANCHORS: Anchor[];
12
+ /**
13
+ * Get a copy of the default safety anchors
14
+ * Clones the anchors to prevent mutation
15
+ */
16
+ export declare function getDefaultAnchors(): Anchor[];
17
+ /**
18
+ * Check if an anchor is a built-in default
19
+ */
20
+ export declare function isBuiltinAnchor(anchor: Anchor): boolean;
21
+ /**
22
+ * Get built-in anchor IDs
23
+ */
24
+ export declare function getBuiltinAnchorIds(): string[];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Built-in default safety anchors
3
+ *
4
+ * These anchors provide sensible safety defaults that can be disabled
5
+ * by setting includeDefaults: false in AnchorManagerOptions.
6
+ */
7
+ /**
8
+ * Default safety anchors shipped with the library
9
+ */
10
+ export const DEFAULT_SAFETY_ANCHORS = [
11
+ {
12
+ id: 'builtin-git-safety',
13
+ content: 'Before destructive git operations (reset --hard, checkout --, restore, clean), ' +
14
+ 'verify current state with git status and git diff. Consider stashing changes first.',
15
+ priority: 'safety',
16
+ scope: 'persistent',
17
+ createdAt: new Date(0), // Epoch - always oldest
18
+ tags: ['builtin', 'git', 'safety'],
19
+ },
20
+ {
21
+ id: 'builtin-file-safety',
22
+ content: 'Before deleting files or directories, confirm the path is correct and consider ' +
23
+ 'if content should be backed up. Never delete root, home, or project root directories.',
24
+ priority: 'safety',
25
+ scope: 'persistent',
26
+ createdAt: new Date(0),
27
+ tags: ['builtin', 'filesystem', 'safety'],
28
+ },
29
+ {
30
+ id: 'builtin-database-safety',
31
+ content: 'Before DROP TABLE, TRUNCATE, or DELETE without WHERE, confirm the operation is ' +
32
+ 'intentional and consider backing up the data first. Never run on production without explicit confirmation.',
33
+ priority: 'safety',
34
+ scope: 'persistent',
35
+ createdAt: new Date(0),
36
+ tags: ['builtin', 'database', 'safety'],
37
+ },
38
+ ];
39
+ /**
40
+ * Get a copy of the default safety anchors
41
+ * Clones the anchors to prevent mutation
42
+ */
43
+ export function getDefaultAnchors() {
44
+ return DEFAULT_SAFETY_ANCHORS.map((anchor) => ({
45
+ ...anchor,
46
+ createdAt: new Date(anchor.createdAt),
47
+ tags: anchor.tags ? [...anchor.tags] : undefined,
48
+ }));
49
+ }
50
+ /**
51
+ * Check if an anchor is a built-in default
52
+ */
53
+ export function isBuiltinAnchor(anchor) {
54
+ return anchor.id.startsWith('builtin-');
55
+ }
56
+ /**
57
+ * Get built-in anchor IDs
58
+ */
59
+ export function getBuiltinAnchorIds() {
60
+ return DEFAULT_SAFETY_ANCHORS.map((a) => a.id);
61
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Anchors module - Critical information that survives context compaction
3
+ */
4
+ export { AnchorManager } from './manager.js';
5
+ export { getDefaultAnchors, isBuiltinAnchor, getBuiltinAnchorIds, DEFAULT_SAFETY_ANCHORS, } from './builtin.js';
6
+ export type { Anchor, AnchorInput, AnchorPriority, AnchorScope, AnchorQueryOptions, AnchorClearOptions, AnchorManagerOptions, AnchorEventType, AnchorEvent, AnchorEventHandler, SerializedAnchor, } from './types.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Anchors module - Critical information that survives context compaction
3
+ */
4
+ export { AnchorManager } from './manager.js';
5
+ export { getDefaultAnchors, isBuiltinAnchor, getBuiltinAnchorIds, DEFAULT_SAFETY_ANCHORS, } from './builtin.js';
@@ -0,0 +1,115 @@
1
+ /**
2
+ * AnchorManager - Manages anchors (critical information that survives context compaction)
3
+ */
4
+ import type { Anchor, AnchorInput, AnchorQueryOptions, AnchorClearOptions, AnchorManagerOptions, AnchorEventHandler } from './types.js';
5
+ /**
6
+ * AnchorManager - Manages critical information that survives context compaction
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const manager = new AnchorManager({
11
+ * maxAnchors: 20,
12
+ * maxTokens: 2000,
13
+ * persistPath: '~/.myapp/anchors.json',
14
+ * });
15
+ *
16
+ * // Add a session anchor
17
+ * manager.add({
18
+ * content: 'This session we implemented: edit tool, grep tool',
19
+ * priority: 'critical',
20
+ * scope: 'session',
21
+ * });
22
+ *
23
+ * // Get formatted anchors for injection into LLM messages
24
+ * const formatted = manager.format();
25
+ * ```
26
+ */
27
+ export declare class AnchorManager {
28
+ private readonly anchors;
29
+ private readonly options;
30
+ private eventHandler?;
31
+ constructor(options?: AnchorManagerOptions);
32
+ /**
33
+ * Set the event handler for anchor events
34
+ */
35
+ onEvent(handler: AnchorEventHandler): void;
36
+ /**
37
+ * Emit an event
38
+ */
39
+ private emit;
40
+ /**
41
+ * Add a new anchor
42
+ *
43
+ * @param input - Anchor input (id and createdAt auto-generated if not provided)
44
+ * @returns The created anchor
45
+ */
46
+ add(input: AnchorInput): Anchor;
47
+ /**
48
+ * Get an anchor by ID
49
+ */
50
+ get(id: string): Anchor | undefined;
51
+ /**
52
+ * Get all anchors, optionally filtered
53
+ */
54
+ getAll(options?: AnchorQueryOptions): Anchor[];
55
+ /**
56
+ * Check if an anchor exists
57
+ */
58
+ has(id: string): boolean;
59
+ /**
60
+ * Remove an anchor by ID
61
+ *
62
+ * @returns true if anchor was removed, false if not found
63
+ */
64
+ remove(id: string): boolean;
65
+ /**
66
+ * Clear anchors based on criteria
67
+ */
68
+ clear(options?: AnchorClearOptions): number;
69
+ /**
70
+ * Get the current number of anchors
71
+ */
72
+ get size(): number;
73
+ /**
74
+ * Get total tokens used by all anchors
75
+ */
76
+ getTotalTokens(): number;
77
+ /**
78
+ * Get token budget utilization (0-1)
79
+ */
80
+ getUtilization(): number;
81
+ /**
82
+ * Format anchors for injection into LLM messages
83
+ *
84
+ * Groups anchors by priority and formats them for the system message.
85
+ */
86
+ format(): string;
87
+ /**
88
+ * Check if an anchor is expired
89
+ */
90
+ private isExpired;
91
+ /**
92
+ * Clean up expired temporary anchors
93
+ */
94
+ private cleanupExpired;
95
+ /**
96
+ * Make room for new content by removing low-priority anchors
97
+ */
98
+ private makeRoom;
99
+ /**
100
+ * Remove the oldest low-priority anchor to make room
101
+ */
102
+ private removeOldestLowPriority;
103
+ /**
104
+ * Load persistent anchors from disk
105
+ */
106
+ private loadPersistent;
107
+ /**
108
+ * Save persistent anchors to disk
109
+ */
110
+ private savePersistent;
111
+ /**
112
+ * Expand ~ in paths
113
+ */
114
+ private expandPath;
115
+ }
@@ -0,0 +1,412 @@
1
+ /**
2
+ * AnchorManager - Manages anchors (critical information that survives context compaction)
3
+ */
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import { generateId } from '../utils/index.js';
7
+ import { getDefaultAnchors, isBuiltinAnchor } from './builtin.js';
8
+ /**
9
+ * Default options for AnchorManager
10
+ */
11
+ const DEFAULT_OPTIONS = {
12
+ maxAnchors: 20,
13
+ maxTokens: 2000,
14
+ includeDefaults: true,
15
+ };
16
+ /**
17
+ * Default token estimator (rough estimate: ~4 chars per token)
18
+ */
19
+ function defaultEstimateTokens(content) {
20
+ return Math.ceil(content.length / 4);
21
+ }
22
+ /**
23
+ * AnchorManager - Manages critical information that survives context compaction
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const manager = new AnchorManager({
28
+ * maxAnchors: 20,
29
+ * maxTokens: 2000,
30
+ * persistPath: '~/.myapp/anchors.json',
31
+ * });
32
+ *
33
+ * // Add a session anchor
34
+ * manager.add({
35
+ * content: 'This session we implemented: edit tool, grep tool',
36
+ * priority: 'critical',
37
+ * scope: 'session',
38
+ * });
39
+ *
40
+ * // Get formatted anchors for injection into LLM messages
41
+ * const formatted = manager.format();
42
+ * ```
43
+ */
44
+ export class AnchorManager {
45
+ anchors = new Map();
46
+ options;
47
+ eventHandler;
48
+ constructor(options = {}) {
49
+ this.options = {
50
+ ...DEFAULT_OPTIONS,
51
+ ...options,
52
+ estimateTokens: options.estimateTokens ?? defaultEstimateTokens,
53
+ };
54
+ // Load persistent anchors from disk
55
+ if (this.options.persistPath) {
56
+ this.loadPersistent();
57
+ }
58
+ // Add built-in default anchors
59
+ if (this.options.includeDefaults) {
60
+ for (const anchor of getDefaultAnchors()) {
61
+ this.anchors.set(anchor.id, anchor);
62
+ }
63
+ }
64
+ }
65
+ /**
66
+ * Set the event handler for anchor events
67
+ */
68
+ onEvent(handler) {
69
+ this.eventHandler = handler;
70
+ }
71
+ /**
72
+ * Emit an event
73
+ */
74
+ emit(type, anchor, message) {
75
+ this.eventHandler?.({ type, anchor, message });
76
+ }
77
+ /**
78
+ * Add a new anchor
79
+ *
80
+ * @param input - Anchor input (id and createdAt auto-generated if not provided)
81
+ * @returns The created anchor
82
+ */
83
+ add(input) {
84
+ // Clean up expired anchors first
85
+ this.cleanupExpired();
86
+ // Check budget before adding
87
+ const newTokens = this.options.estimateTokens(input.content);
88
+ const currentTokens = this.getTotalTokens();
89
+ if (currentTokens + newTokens > this.options.maxTokens) {
90
+ // Try to make room by removing low-priority anchors
91
+ this.makeRoom(newTokens);
92
+ }
93
+ // Check anchor count limit
94
+ if (this.anchors.size >= this.options.maxAnchors) {
95
+ this.removeOldestLowPriority();
96
+ }
97
+ // Create the anchor
98
+ const anchor = {
99
+ id: input.id ?? `anchor_${generateId()}`,
100
+ content: input.content,
101
+ priority: input.priority,
102
+ scope: input.scope,
103
+ createdAt: new Date(),
104
+ expiresAt: input.expiresAt,
105
+ tags: input.tags,
106
+ metadata: input.metadata,
107
+ };
108
+ // Store the anchor
109
+ this.anchors.set(anchor.id, anchor);
110
+ this.emit('anchor:added', anchor);
111
+ // Persist if it's a persistent anchor
112
+ if (anchor.scope === 'persistent' && this.options.persistPath) {
113
+ this.savePersistent();
114
+ }
115
+ return anchor;
116
+ }
117
+ /**
118
+ * Get an anchor by ID
119
+ */
120
+ get(id) {
121
+ const anchor = this.anchors.get(id);
122
+ if (anchor && this.isExpired(anchor)) {
123
+ this.remove(id);
124
+ return undefined;
125
+ }
126
+ return anchor;
127
+ }
128
+ /**
129
+ * Get all anchors, optionally filtered
130
+ */
131
+ getAll(options) {
132
+ this.cleanupExpired();
133
+ let anchors = Array.from(this.anchors.values());
134
+ // Filter by priority
135
+ if (options?.priority) {
136
+ anchors = anchors.filter((a) => a.priority === options.priority);
137
+ }
138
+ // Filter by scope
139
+ if (options?.scope) {
140
+ anchors = anchors.filter((a) => a.scope === options.scope);
141
+ }
142
+ // Filter by tags (must have ALL specified tags)
143
+ const tagsFilter = options?.tags;
144
+ if (tagsFilter && tagsFilter.length > 0) {
145
+ anchors = anchors.filter((a) => {
146
+ const anchorTags = a.tags;
147
+ if (!anchorTags)
148
+ return false;
149
+ return tagsFilter.every((tag) => anchorTags.includes(tag));
150
+ });
151
+ }
152
+ // Filter out expired unless requested
153
+ if (!options?.includeExpired) {
154
+ anchors = anchors.filter((a) => !this.isExpired(a));
155
+ }
156
+ return anchors;
157
+ }
158
+ /**
159
+ * Check if an anchor exists
160
+ */
161
+ has(id) {
162
+ return this.get(id) !== undefined;
163
+ }
164
+ /**
165
+ * Remove an anchor by ID
166
+ *
167
+ * @returns true if anchor was removed, false if not found
168
+ */
169
+ remove(id) {
170
+ const anchor = this.anchors.get(id);
171
+ if (!anchor)
172
+ return false;
173
+ this.anchors.delete(id);
174
+ this.emit('anchor:removed', anchor);
175
+ // Update persistence if it was a persistent anchor
176
+ if (anchor.scope === 'persistent' && this.options.persistPath) {
177
+ this.savePersistent();
178
+ }
179
+ return true;
180
+ }
181
+ /**
182
+ * Clear anchors based on criteria
183
+ */
184
+ clear(options) {
185
+ let removed = 0;
186
+ const toRemove = [];
187
+ for (const [id, anchor] of this.anchors) {
188
+ // Skip built-in anchors unless explicitly clearing by scope
189
+ if (isBuiltinAnchor(anchor) && !options?.scope) {
190
+ continue;
191
+ }
192
+ // Filter by scope
193
+ if (options?.scope && anchor.scope !== options.scope) {
194
+ continue;
195
+ }
196
+ // Filter by tags
197
+ if (options?.tags && options.tags.length > 0) {
198
+ const anchorTags = anchor.tags;
199
+ if (!anchorTags || !options.tags.every((tag) => anchorTags.includes(tag))) {
200
+ continue;
201
+ }
202
+ }
203
+ // Filter by expired
204
+ if (options?.expiredOnly && !this.isExpired(anchor)) {
205
+ continue;
206
+ }
207
+ toRemove.push(id);
208
+ }
209
+ for (const id of toRemove) {
210
+ this.remove(id);
211
+ removed++;
212
+ }
213
+ return removed;
214
+ }
215
+ /**
216
+ * Get the current number of anchors
217
+ */
218
+ get size() {
219
+ return this.anchors.size;
220
+ }
221
+ /**
222
+ * Get total tokens used by all anchors
223
+ */
224
+ getTotalTokens() {
225
+ let total = 0;
226
+ for (const anchor of this.anchors.values()) {
227
+ if (!this.isExpired(anchor)) {
228
+ total += this.options.estimateTokens(anchor.content);
229
+ }
230
+ }
231
+ return total;
232
+ }
233
+ /**
234
+ * Get token budget utilization (0-1)
235
+ */
236
+ getUtilization() {
237
+ return this.getTotalTokens() / this.options.maxTokens;
238
+ }
239
+ /**
240
+ * Format anchors for injection into LLM messages
241
+ *
242
+ * Groups anchors by priority and formats them for the system message.
243
+ */
244
+ format() {
245
+ const anchors = this.getAll();
246
+ if (anchors.length === 0)
247
+ return '';
248
+ // Group by priority
249
+ const critical = anchors.filter((a) => a.priority === 'critical');
250
+ const safety = anchors.filter((a) => a.priority === 'safety');
251
+ const info = anchors.filter((a) => a.priority === 'info');
252
+ const parts = [];
253
+ if (critical.length > 0) {
254
+ parts.push('### CRITICAL (Must Remember)');
255
+ for (const a of critical) {
256
+ parts.push(`- ${a.content}`);
257
+ }
258
+ }
259
+ if (safety.length > 0) {
260
+ if (parts.length > 0)
261
+ parts.push('');
262
+ parts.push('### SAFETY (Check Before Acting)');
263
+ for (const a of safety) {
264
+ parts.push(`- ${a.content}`);
265
+ }
266
+ }
267
+ if (info.length > 0) {
268
+ if (parts.length > 0)
269
+ parts.push('');
270
+ parts.push('### INFO');
271
+ for (const a of info) {
272
+ parts.push(`- ${a.content}`);
273
+ }
274
+ }
275
+ return parts.join('\n');
276
+ }
277
+ /**
278
+ * Check if an anchor is expired
279
+ */
280
+ isExpired(anchor) {
281
+ if (anchor.scope !== 'temporary' || !anchor.expiresAt) {
282
+ return false;
283
+ }
284
+ return new Date() > anchor.expiresAt;
285
+ }
286
+ /**
287
+ * Clean up expired temporary anchors
288
+ */
289
+ cleanupExpired() {
290
+ const toRemove = [];
291
+ for (const [id, anchor] of this.anchors) {
292
+ if (this.isExpired(anchor)) {
293
+ toRemove.push(id);
294
+ }
295
+ }
296
+ for (const id of toRemove) {
297
+ const anchor = this.anchors.get(id);
298
+ this.anchors.delete(id);
299
+ this.emit('anchor:expired', anchor);
300
+ }
301
+ }
302
+ /**
303
+ * Make room for new content by removing low-priority anchors
304
+ */
305
+ makeRoom(neededTokens) {
306
+ // Sort by priority (info first, then safety, then critical)
307
+ // Within same priority, oldest first
308
+ const priorityOrder = { info: 0, safety: 1, critical: 2 };
309
+ const sortedAnchors = this.getAll()
310
+ .filter((a) => !isBuiltinAnchor(a)) // Don't remove built-ins
311
+ .sort((a, b) => {
312
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
313
+ if (priorityDiff !== 0)
314
+ return priorityDiff;
315
+ return a.createdAt.getTime() - b.createdAt.getTime();
316
+ });
317
+ let freedTokens = 0;
318
+ for (const anchor of sortedAnchors) {
319
+ if (freedTokens >= neededTokens)
320
+ break;
321
+ const tokens = this.options.estimateTokens(anchor.content);
322
+ this.remove(anchor.id);
323
+ freedTokens += tokens;
324
+ this.emit('anchor:budget_exceeded', anchor, `Removed to make room for new anchor`);
325
+ }
326
+ }
327
+ /**
328
+ * Remove the oldest low-priority anchor to make room
329
+ */
330
+ removeOldestLowPriority() {
331
+ const priorityOrder = { info: 0, safety: 1, critical: 2 };
332
+ const sortedAnchors = this.getAll()
333
+ .filter((a) => !isBuiltinAnchor(a))
334
+ .sort((a, b) => {
335
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
336
+ if (priorityDiff !== 0)
337
+ return priorityDiff;
338
+ return a.createdAt.getTime() - b.createdAt.getTime();
339
+ });
340
+ if (sortedAnchors.length > 0) {
341
+ this.remove(sortedAnchors[0].id);
342
+ }
343
+ }
344
+ /**
345
+ * Load persistent anchors from disk
346
+ */
347
+ loadPersistent() {
348
+ if (!this.options.persistPath)
349
+ return;
350
+ try {
351
+ const expandedPath = this.expandPath(this.options.persistPath);
352
+ if (!fs.existsSync(expandedPath))
353
+ return;
354
+ const data = fs.readFileSync(expandedPath, 'utf-8');
355
+ const serialized = JSON.parse(data);
356
+ for (const s of serialized) {
357
+ const anchor = {
358
+ ...s,
359
+ createdAt: new Date(s.createdAt),
360
+ expiresAt: s.expiresAt ? new Date(s.expiresAt) : undefined,
361
+ };
362
+ this.anchors.set(anchor.id, anchor);
363
+ }
364
+ this.emit('anchor:loaded', undefined, `Loaded ${String(serialized.length)} persistent anchors`);
365
+ }
366
+ catch {
367
+ // Ignore errors loading - start fresh
368
+ }
369
+ }
370
+ /**
371
+ * Save persistent anchors to disk
372
+ */
373
+ savePersistent() {
374
+ if (!this.options.persistPath)
375
+ return;
376
+ try {
377
+ const expandedPath = this.expandPath(this.options.persistPath);
378
+ // Ensure directory exists
379
+ const dir = path.dirname(expandedPath);
380
+ if (!fs.existsSync(dir)) {
381
+ fs.mkdirSync(dir, { recursive: true });
382
+ }
383
+ // Get only persistent anchors (excluding built-ins)
384
+ const persistent = this.getAll({ scope: 'persistent' }).filter((a) => !isBuiltinAnchor(a));
385
+ const serialized = persistent.map((a) => ({
386
+ id: a.id,
387
+ content: a.content,
388
+ priority: a.priority,
389
+ scope: a.scope,
390
+ createdAt: a.createdAt.toISOString(),
391
+ expiresAt: a.expiresAt?.toISOString(),
392
+ tags: a.tags,
393
+ metadata: a.metadata,
394
+ }));
395
+ fs.writeFileSync(expandedPath, JSON.stringify(serialized, null, 2));
396
+ this.emit('anchor:persisted', undefined, `Saved ${String(serialized.length)} persistent anchors`);
397
+ }
398
+ catch {
399
+ // Ignore errors saving
400
+ }
401
+ }
402
+ /**
403
+ * Expand ~ in paths
404
+ */
405
+ expandPath(p) {
406
+ if (p.startsWith('~/')) {
407
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
408
+ return path.join(home, p.slice(2));
409
+ }
410
+ return p;
411
+ }
412
+ }