@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.
- package/README.md +1277 -0
- package/dist/agent.d.ts +1272 -0
- package/dist/agent.js +1912 -0
- package/dist/anchors/builtin.d.ts +24 -0
- package/dist/anchors/builtin.js +61 -0
- package/dist/anchors/index.d.ts +6 -0
- package/dist/anchors/index.js +5 -0
- package/dist/anchors/manager.d.ts +115 -0
- package/dist/anchors/manager.js +412 -0
- package/dist/anchors/types.d.ts +168 -0
- package/dist/anchors/types.js +10 -0
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.js +10 -0
- package/dist/context/manager.d.ts +224 -0
- package/dist/context/manager.js +770 -0
- package/dist/context/types.d.ts +377 -0
- package/dist/context/types.js +7 -0
- package/dist/costs/index.d.ts +8 -0
- package/dist/costs/index.js +7 -0
- package/dist/costs/tracker.d.ts +121 -0
- package/dist/costs/tracker.js +295 -0
- package/dist/costs/types.d.ts +157 -0
- package/dist/costs/types.js +8 -0
- package/dist/errors.d.ts +178 -0
- package/dist/errors.js +249 -0
- package/dist/guardrails/builtin.d.ts +27 -0
- package/dist/guardrails/builtin.js +223 -0
- package/dist/guardrails/index.d.ts +6 -0
- package/dist/guardrails/index.js +5 -0
- package/dist/guardrails/manager.d.ts +117 -0
- package/dist/guardrails/manager.js +288 -0
- package/dist/guardrails/types.d.ts +159 -0
- package/dist/guardrails/types.js +7 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.js +29 -0
- package/dist/hooks/manager.d.ts +147 -0
- package/dist/hooks/manager.js +600 -0
- package/dist/hooks/types.d.ts +368 -0
- package/dist/hooks/types.js +12 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +73 -0
- package/dist/mcp/client.d.ts +93 -0
- package/dist/mcp/client.js +287 -0
- package/dist/mcp/errors.d.ts +60 -0
- package/dist/mcp/errors.js +78 -0
- package/dist/mcp/index.d.ts +43 -0
- package/dist/mcp/index.js +45 -0
- package/dist/mcp/manager.d.ts +120 -0
- package/dist/mcp/manager.js +276 -0
- package/dist/mcp/tools.d.ts +54 -0
- package/dist/mcp/tools.js +99 -0
- package/dist/mcp/types.d.ts +150 -0
- package/dist/mcp/types.js +40 -0
- package/dist/memory/index.d.ts +8 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/loader.d.ts +114 -0
- package/dist/memory/loader.js +463 -0
- package/dist/memory/types.d.ts +182 -0
- package/dist/memory/types.js +8 -0
- package/dist/messages/index.d.ts +82 -0
- package/dist/messages/index.js +155 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.js +4 -0
- package/dist/permissions/manager.d.ts +125 -0
- package/dist/permissions/manager.js +379 -0
- package/dist/permissions/types.d.ts +162 -0
- package/dist/permissions/types.js +7 -0
- package/dist/providers/claude.d.ts +90 -0
- package/dist/providers/claude.js +348 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/mock.d.ts +133 -0
- package/dist/providers/mock.js +204 -0
- package/dist/providers/types.d.ts +168 -0
- package/dist/providers/types.js +4 -0
- package/dist/rate-limit/index.d.ts +45 -0
- package/dist/rate-limit/index.js +47 -0
- package/dist/rate-limit/limiter.d.ts +104 -0
- package/dist/rate-limit/limiter.js +326 -0
- package/dist/rate-limit/provider-wrapper.d.ts +112 -0
- package/dist/rate-limit/provider-wrapper.js +201 -0
- package/dist/rate-limit/retry.d.ts +108 -0
- package/dist/rate-limit/retry.js +287 -0
- package/dist/rate-limit/types.d.ts +181 -0
- package/dist/rate-limit/types.js +22 -0
- package/dist/rehearsal/file-analyzer.d.ts +22 -0
- package/dist/rehearsal/file-analyzer.js +351 -0
- package/dist/rehearsal/git-analyzer.d.ts +22 -0
- package/dist/rehearsal/git-analyzer.js +472 -0
- package/dist/rehearsal/index.d.ts +35 -0
- package/dist/rehearsal/index.js +36 -0
- package/dist/rehearsal/manager.d.ts +100 -0
- package/dist/rehearsal/manager.js +290 -0
- package/dist/rehearsal/types.d.ts +235 -0
- package/dist/rehearsal/types.js +8 -0
- package/dist/skills/index.d.ts +160 -0
- package/dist/skills/index.js +282 -0
- package/dist/state/agent-state.d.ts +41 -0
- package/dist/state/agent-state.js +88 -0
- package/dist/state/checkpointer.d.ts +110 -0
- package/dist/state/checkpointer.js +362 -0
- package/dist/state/errors.d.ts +66 -0
- package/dist/state/errors.js +88 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.js +37 -0
- package/dist/state/serializer.d.ts +55 -0
- package/dist/state/serializer.js +172 -0
- package/dist/state/types.d.ts +312 -0
- package/dist/state/types.js +14 -0
- package/dist/tools/builtin/bash-output.d.ts +61 -0
- package/dist/tools/builtin/bash-output.js +90 -0
- package/dist/tools/builtin/bash.d.ts +150 -0
- package/dist/tools/builtin/bash.js +354 -0
- package/dist/tools/builtin/edit.d.ts +50 -0
- package/dist/tools/builtin/edit.js +215 -0
- package/dist/tools/builtin/glob.d.ts +62 -0
- package/dist/tools/builtin/glob.js +244 -0
- package/dist/tools/builtin/grep.d.ts +74 -0
- package/dist/tools/builtin/grep.js +363 -0
- package/dist/tools/builtin/index.d.ts +44 -0
- package/dist/tools/builtin/index.js +69 -0
- package/dist/tools/builtin/kill-shell.d.ts +44 -0
- package/dist/tools/builtin/kill-shell.js +80 -0
- package/dist/tools/builtin/read-file.d.ts +57 -0
- package/dist/tools/builtin/read-file.js +184 -0
- package/dist/tools/builtin/shell-manager.d.ts +176 -0
- package/dist/tools/builtin/shell-manager.js +337 -0
- package/dist/tools/builtin/task.d.ts +202 -0
- package/dist/tools/builtin/task.js +350 -0
- package/dist/tools/builtin/todo.d.ts +207 -0
- package/dist/tools/builtin/todo.js +453 -0
- package/dist/tools/builtin/utils.d.ts +27 -0
- package/dist/tools/builtin/utils.js +70 -0
- package/dist/tools/builtin/web-fetch.d.ts +96 -0
- package/dist/tools/builtin/web-fetch.js +290 -0
- package/dist/tools/builtin/write-file.d.ts +54 -0
- package/dist/tools/builtin/write-file.js +147 -0
- package/dist/tools/define.d.ts +60 -0
- package/dist/tools/define.js +65 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +37 -0
- package/dist/tools/registry.d.ts +79 -0
- package/dist/tools/registry.js +151 -0
- package/dist/tools/types.d.ts +59 -0
- package/dist/tools/types.js +4 -0
- package/dist/tracing/hooks.d.ts +58 -0
- package/dist/tracing/hooks.js +377 -0
- package/dist/tracing/index.d.ts +51 -0
- package/dist/tracing/index.js +55 -0
- package/dist/tracing/logging.d.ts +78 -0
- package/dist/tracing/logging.js +310 -0
- package/dist/tracing/manager.d.ts +160 -0
- package/dist/tracing/manager.js +468 -0
- package/dist/tracing/otel.d.ts +102 -0
- package/dist/tracing/otel.js +246 -0
- package/dist/tracing/types.d.ts +346 -0
- package/dist/tracing/types.js +38 -0
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.js +44 -0
- 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,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
|
+
}
|