agent-relay 1.3.1 → 1.3.3
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/.trajectories/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/README.md +23 -9
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +58 -76
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +297 -30
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +51 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +113 -22
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +57 -113
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +2 -2
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -3
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/dashboard-server/server.js +244 -28
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +40 -0
- package/dist/wrapper/base-wrapper.js +60 -6
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +89 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +14 -2
- package/dist/wrapper/pty-wrapper.js +132 -32
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +20 -2
- package/dist/wrapper/tmux-wrapper.js +163 -40
- package/package.json +3 -1
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
- package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compaction for Long Agent Sessions
|
|
3
|
+
*
|
|
4
|
+
* Manages conversation context to prevent token limit exhaustion.
|
|
5
|
+
* Provides token counting, message summarization, and context pruning.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by russian-code-ts context management targets:
|
|
8
|
+
* - Token estimation: <20ms
|
|
9
|
+
* - Embeddings-based semantic search for relevant context
|
|
10
|
+
*
|
|
11
|
+
* Strategies:
|
|
12
|
+
* 1. Fast token estimation (character-based heuristic)
|
|
13
|
+
* 2. Importance-weighted message retention
|
|
14
|
+
* 3. Sliding window with summary injection
|
|
15
|
+
* 4. Semantic deduplication of similar messages
|
|
16
|
+
*/
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Default Configuration
|
|
19
|
+
// =============================================================================
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
maxTokens: 100000, // 100k tokens (Claude's typical limit)
|
|
22
|
+
targetUsage: 0.7, // Target 70% after compaction
|
|
23
|
+
compactionThreshold: 0.85, // Trigger at 85% usage
|
|
24
|
+
minImportanceRetain: 30, // Keep messages with importance >= 30
|
|
25
|
+
keepRecentCount: 10, // Always keep last 10 messages
|
|
26
|
+
enableSummarization: true,
|
|
27
|
+
enableDeduplication: true,
|
|
28
|
+
deduplicationThreshold: 0.85,
|
|
29
|
+
};
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Token Estimation
|
|
32
|
+
// =============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Fast token estimation using character-based heuristic.
|
|
35
|
+
* Targets <20ms latency for large texts.
|
|
36
|
+
*
|
|
37
|
+
* Heuristic: ~4 characters per token for English text.
|
|
38
|
+
* Adjusts for code (more tokens per char) and whitespace.
|
|
39
|
+
*/
|
|
40
|
+
export function estimateTokens(text) {
|
|
41
|
+
if (!text)
|
|
42
|
+
return 0;
|
|
43
|
+
const length = text.length;
|
|
44
|
+
// Fast path for short texts
|
|
45
|
+
if (length < 100) {
|
|
46
|
+
return Math.ceil(length / 3.5);
|
|
47
|
+
}
|
|
48
|
+
// Sample-based estimation for longer texts
|
|
49
|
+
// Count different character types in sample
|
|
50
|
+
const sampleSize = Math.min(1000, length);
|
|
51
|
+
const sample = text.substring(0, sampleSize);
|
|
52
|
+
let codeChars = 0;
|
|
53
|
+
let whitespaceChars = 0;
|
|
54
|
+
let _punctuationChars = 0;
|
|
55
|
+
for (let i = 0; i < sample.length; i++) {
|
|
56
|
+
const char = sample[i];
|
|
57
|
+
if (/\s/.test(char)) {
|
|
58
|
+
whitespaceChars++;
|
|
59
|
+
}
|
|
60
|
+
else if (/[{}[\]();:,.<>!=+\-*/&|^~`@#$%]/.test(char)) {
|
|
61
|
+
_punctuationChars++;
|
|
62
|
+
codeChars++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const codeRatio = codeChars / sampleSize;
|
|
66
|
+
const whitespaceRatio = whitespaceChars / sampleSize;
|
|
67
|
+
// Adjust chars per token based on content type
|
|
68
|
+
// Heuristics based on tokenization patterns:
|
|
69
|
+
// - Base prose: ~4 chars/token (average English text)
|
|
70
|
+
// - Code: ~3 chars/token (more tokens due to symbols/structure)
|
|
71
|
+
// - High whitespace: ~3.5 chars/token (more word boundaries = more tokens)
|
|
72
|
+
const baseCharsPerToken = 4;
|
|
73
|
+
const codeAdjustment = codeRatio * 1.5; // Code reduces chars/token (more tokens)
|
|
74
|
+
const whitespaceAdjustment = whitespaceRatio * 0.5; // Whitespace reduces chars/token (more word boundaries)
|
|
75
|
+
const charsPerToken = baseCharsPerToken - codeAdjustment - whitespaceAdjustment;
|
|
76
|
+
const adjustedCharsPerToken = Math.max(2.5, Math.min(5, charsPerToken));
|
|
77
|
+
return Math.ceil(length / adjustedCharsPerToken);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Estimate tokens for a message (uses caching).
|
|
81
|
+
*/
|
|
82
|
+
export function estimateMessageTokens(message) {
|
|
83
|
+
if (message.tokenCount !== undefined) {
|
|
84
|
+
return message.tokenCount;
|
|
85
|
+
}
|
|
86
|
+
// Role overhead: ~4 tokens for role markers
|
|
87
|
+
const roleOverhead = 4;
|
|
88
|
+
const contentTokens = estimateTokens(message.content);
|
|
89
|
+
message.tokenCount = roleOverhead + contentTokens;
|
|
90
|
+
return message.tokenCount;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Estimate tokens for entire context.
|
|
94
|
+
*/
|
|
95
|
+
export function estimateContextTokens(messages) {
|
|
96
|
+
let total = 0;
|
|
97
|
+
for (const msg of messages) {
|
|
98
|
+
total += estimateMessageTokens(msg);
|
|
99
|
+
}
|
|
100
|
+
// Add overhead for message separators (~2 tokens per message)
|
|
101
|
+
total += messages.length * 2;
|
|
102
|
+
return total;
|
|
103
|
+
}
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// Importance Scoring
|
|
106
|
+
// =============================================================================
|
|
107
|
+
/**
|
|
108
|
+
* Calculate importance score for a message.
|
|
109
|
+
* Higher scores = more important to retain.
|
|
110
|
+
*/
|
|
111
|
+
export function calculateImportance(message, index, total) {
|
|
112
|
+
let score = 50; // Base score
|
|
113
|
+
// Recency bonus (0-20 points)
|
|
114
|
+
const recencyRatio = index / total;
|
|
115
|
+
score += recencyRatio * 20;
|
|
116
|
+
// System messages are important
|
|
117
|
+
if (message.role === 'system') {
|
|
118
|
+
score += 30;
|
|
119
|
+
}
|
|
120
|
+
// Check for important content patterns
|
|
121
|
+
const content = message.content.toLowerCase();
|
|
122
|
+
// Task-related keywords
|
|
123
|
+
if (/\b(todo|task|implement|fix|bug|error|important|critical|urgent)\b/.test(content)) {
|
|
124
|
+
score += 15;
|
|
125
|
+
}
|
|
126
|
+
// Code blocks are often important context
|
|
127
|
+
if (/```[\s\S]*```/.test(message.content)) {
|
|
128
|
+
score += 10;
|
|
129
|
+
}
|
|
130
|
+
// Questions that might need answers retained
|
|
131
|
+
if (/\?/.test(content) && message.role === 'user') {
|
|
132
|
+
score += 10;
|
|
133
|
+
}
|
|
134
|
+
// Acknowledgments and status updates can be lower priority
|
|
135
|
+
if (/^(ok|ack|got it|understood|done|completed)/i.test(content)) {
|
|
136
|
+
score -= 20;
|
|
137
|
+
}
|
|
138
|
+
// Very short messages are usually less important
|
|
139
|
+
if (message.content.length < 50) {
|
|
140
|
+
score -= 10;
|
|
141
|
+
}
|
|
142
|
+
// Summaries should be kept
|
|
143
|
+
if (message.isSummary) {
|
|
144
|
+
score += 25;
|
|
145
|
+
}
|
|
146
|
+
// User-specified importance overrides
|
|
147
|
+
if (message.importance !== undefined) {
|
|
148
|
+
score = (score + message.importance) / 2;
|
|
149
|
+
}
|
|
150
|
+
return Math.max(0, Math.min(100, score));
|
|
151
|
+
}
|
|
152
|
+
// =============================================================================
|
|
153
|
+
// Similarity Detection
|
|
154
|
+
// =============================================================================
|
|
155
|
+
/**
|
|
156
|
+
* Simple similarity score between two strings (Jaccard on word set).
|
|
157
|
+
* Returns 0-1 where 1 = identical.
|
|
158
|
+
*/
|
|
159
|
+
export function calculateSimilarity(a, b) {
|
|
160
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter(w => w.length > 2));
|
|
161
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/).filter(w => w.length > 2));
|
|
162
|
+
if (wordsA.size === 0 || wordsB.size === 0) {
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
let intersection = 0;
|
|
166
|
+
for (const word of wordsA) {
|
|
167
|
+
if (wordsB.has(word)) {
|
|
168
|
+
intersection++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const union = wordsA.size + wordsB.size - intersection;
|
|
172
|
+
return intersection / union;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Find duplicate/similar messages.
|
|
176
|
+
*/
|
|
177
|
+
export function findDuplicates(messages, threshold = 0.85) {
|
|
178
|
+
const duplicates = new Map();
|
|
179
|
+
for (let i = 0; i < messages.length; i++) {
|
|
180
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
181
|
+
const similarity = calculateSimilarity(messages[i].content, messages[j].content);
|
|
182
|
+
if (similarity >= threshold) {
|
|
183
|
+
const key = messages[i].id;
|
|
184
|
+
const existing = duplicates.get(key) ?? [];
|
|
185
|
+
existing.push(messages[j].id);
|
|
186
|
+
duplicates.set(key, existing);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return duplicates;
|
|
191
|
+
}
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// Summarization
|
|
194
|
+
// =============================================================================
|
|
195
|
+
/**
|
|
196
|
+
* Create a summary of multiple messages.
|
|
197
|
+
* This is a simple extractive summary - in production, use an LLM.
|
|
198
|
+
*/
|
|
199
|
+
export function createSummary(messages) {
|
|
200
|
+
const messageCount = messages.length;
|
|
201
|
+
const roles = new Set(messages.map(m => m.role));
|
|
202
|
+
const threads = new Set(messages.filter(m => m.thread).map(m => m.thread));
|
|
203
|
+
// Extract key sentences (first sentence of each message, or first 100 chars)
|
|
204
|
+
const keyPoints = [];
|
|
205
|
+
for (const msg of messages.slice(0, 5)) { // Take up to 5 key points
|
|
206
|
+
const firstSentence = msg.content.split(/[.!?]\s/)[0];
|
|
207
|
+
if (firstSentence && firstSentence.length < 200) {
|
|
208
|
+
keyPoints.push(`- ${firstSentence}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const content = [
|
|
212
|
+
`[Summary of ${messageCount} messages]`,
|
|
213
|
+
`Participants: ${Array.from(roles).join(', ')}`,
|
|
214
|
+
threads.size > 0 ? `Threads: ${Array.from(threads).join(', ')}` : '',
|
|
215
|
+
'Key points:',
|
|
216
|
+
...keyPoints,
|
|
217
|
+
`[End summary]`,
|
|
218
|
+
].filter(Boolean).join('\n');
|
|
219
|
+
return {
|
|
220
|
+
id: `summary_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
221
|
+
role: 'system',
|
|
222
|
+
content,
|
|
223
|
+
timestamp: Date.now(),
|
|
224
|
+
importance: 70,
|
|
225
|
+
isSummary: true,
|
|
226
|
+
summarizes: messages.map(m => m.id),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// Context Compaction
|
|
231
|
+
// =============================================================================
|
|
232
|
+
/**
|
|
233
|
+
* Context compaction manager.
|
|
234
|
+
*/
|
|
235
|
+
export class ContextCompactor {
|
|
236
|
+
config;
|
|
237
|
+
constructor(config = {}) {
|
|
238
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get current context window status.
|
|
242
|
+
*/
|
|
243
|
+
getContextWindow(messages) {
|
|
244
|
+
const totalTokens = estimateContextTokens(messages);
|
|
245
|
+
return {
|
|
246
|
+
messages,
|
|
247
|
+
totalTokens,
|
|
248
|
+
maxTokens: this.config.maxTokens,
|
|
249
|
+
usagePercent: totalTokens / this.config.maxTokens,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if compaction is needed.
|
|
254
|
+
*/
|
|
255
|
+
needsCompaction(messages) {
|
|
256
|
+
const window = this.getContextWindow(messages);
|
|
257
|
+
return window.usagePercent >= this.config.compactionThreshold;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Perform context compaction.
|
|
261
|
+
*/
|
|
262
|
+
compact(messages) {
|
|
263
|
+
const window = this.getContextWindow(messages);
|
|
264
|
+
// No compaction needed
|
|
265
|
+
if (window.usagePercent < this.config.compactionThreshold) {
|
|
266
|
+
return {
|
|
267
|
+
messages,
|
|
268
|
+
messagesRemoved: 0,
|
|
269
|
+
tokensSaved: 0,
|
|
270
|
+
strategy: 'none',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const targetTokens = Math.floor(this.config.maxTokens * this.config.targetUsage);
|
|
274
|
+
let result = [...messages];
|
|
275
|
+
let strategy = 'none';
|
|
276
|
+
const originalTokens = window.totalTokens;
|
|
277
|
+
// Calculate importance for all messages
|
|
278
|
+
const importanceMap = new Map();
|
|
279
|
+
for (let i = 0; i < result.length; i++) {
|
|
280
|
+
importanceMap.set(result[i].id, calculateImportance(result[i], i, result.length));
|
|
281
|
+
}
|
|
282
|
+
// Strategy 1: Deduplicate similar messages
|
|
283
|
+
if (this.config.enableDeduplication) {
|
|
284
|
+
const duplicates = findDuplicates(result, this.config.deduplicationThreshold);
|
|
285
|
+
if (duplicates.size > 0) {
|
|
286
|
+
const toRemove = new Set();
|
|
287
|
+
for (const [, dups] of duplicates) {
|
|
288
|
+
for (const id of dups) {
|
|
289
|
+
toRemove.add(id);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
result = result.filter(m => !toRemove.has(m.id));
|
|
293
|
+
if (toRemove.size > 0) {
|
|
294
|
+
strategy = 'deduplicate';
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Check if we've met target
|
|
299
|
+
if (estimateContextTokens(result) <= targetTokens) {
|
|
300
|
+
return {
|
|
301
|
+
messages: result,
|
|
302
|
+
messagesRemoved: messages.length - result.length,
|
|
303
|
+
tokensSaved: originalTokens - estimateContextTokens(result),
|
|
304
|
+
strategy,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// Strategy 2: Remove low-importance messages (keep recent)
|
|
308
|
+
const recentIds = new Set(result.slice(-this.config.keepRecentCount).map(m => m.id));
|
|
309
|
+
result = result.filter(m => {
|
|
310
|
+
if (recentIds.has(m.id))
|
|
311
|
+
return true;
|
|
312
|
+
if (m.isSummary)
|
|
313
|
+
return true;
|
|
314
|
+
if (m.role === 'system')
|
|
315
|
+
return true;
|
|
316
|
+
const importance = importanceMap.get(m.id) ?? 50;
|
|
317
|
+
return importance >= this.config.minImportanceRetain;
|
|
318
|
+
});
|
|
319
|
+
if (result.length < messages.length) {
|
|
320
|
+
strategy = 'trim_low_importance';
|
|
321
|
+
}
|
|
322
|
+
// Check if we've met target
|
|
323
|
+
if (estimateContextTokens(result) <= targetTokens) {
|
|
324
|
+
return {
|
|
325
|
+
messages: result,
|
|
326
|
+
messagesRemoved: messages.length - result.length,
|
|
327
|
+
tokensSaved: originalTokens - estimateContextTokens(result),
|
|
328
|
+
strategy,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
// Strategy 3: Summarize old messages
|
|
332
|
+
if (this.config.enableSummarization) {
|
|
333
|
+
const messagesToSummarize = result.slice(0, -this.config.keepRecentCount)
|
|
334
|
+
.filter(m => !m.isSummary && m.role !== 'system');
|
|
335
|
+
if (messagesToSummarize.length >= 3) {
|
|
336
|
+
const summary = createSummary(messagesToSummarize);
|
|
337
|
+
const summaryIds = new Set(messagesToSummarize.map(m => m.id));
|
|
338
|
+
result = [
|
|
339
|
+
summary,
|
|
340
|
+
...result.filter(m => !summaryIds.has(m.id)),
|
|
341
|
+
];
|
|
342
|
+
strategy = 'summarize';
|
|
343
|
+
return {
|
|
344
|
+
messages: result,
|
|
345
|
+
messagesRemoved: messages.length - result.length,
|
|
346
|
+
tokensSaved: originalTokens - estimateContextTokens(result),
|
|
347
|
+
summaryAdded: summary,
|
|
348
|
+
strategy,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Strategy 4: Aggressive trim (last resort)
|
|
353
|
+
while (estimateContextTokens(result) > targetTokens && result.length > this.config.keepRecentCount + 1) {
|
|
354
|
+
// Remove oldest non-system, non-summary message
|
|
355
|
+
const removeIndex = result.findIndex(m => !m.isSummary && m.role !== 'system');
|
|
356
|
+
if (removeIndex === -1)
|
|
357
|
+
break;
|
|
358
|
+
result.splice(removeIndex, 1);
|
|
359
|
+
}
|
|
360
|
+
strategy = 'aggressive';
|
|
361
|
+
return {
|
|
362
|
+
messages: result,
|
|
363
|
+
messagesRemoved: messages.length - result.length,
|
|
364
|
+
tokensSaved: originalTokens - estimateContextTokens(result),
|
|
365
|
+
strategy,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Add a message to context with automatic compaction if needed.
|
|
370
|
+
*/
|
|
371
|
+
addMessage(messages, newMessage) {
|
|
372
|
+
const updated = [...messages, newMessage];
|
|
373
|
+
if (this.needsCompaction(updated)) {
|
|
374
|
+
const result = this.compact(updated);
|
|
375
|
+
return {
|
|
376
|
+
messages: result.messages,
|
|
377
|
+
compacted: true,
|
|
378
|
+
result,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
messages: updated,
|
|
383
|
+
compacted: false,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get token budget remaining.
|
|
388
|
+
*/
|
|
389
|
+
getTokenBudget(messages) {
|
|
390
|
+
const used = estimateContextTokens(messages);
|
|
391
|
+
return {
|
|
392
|
+
used,
|
|
393
|
+
remaining: this.config.maxTokens - used,
|
|
394
|
+
percentUsed: (used / this.config.maxTokens) * 100,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// =============================================================================
|
|
399
|
+
// Factory Function
|
|
400
|
+
// =============================================================================
|
|
401
|
+
/**
|
|
402
|
+
* Create a context compactor with the given configuration.
|
|
403
|
+
*/
|
|
404
|
+
export function createContextCompactor(config) {
|
|
405
|
+
return new ContextCompactor(config);
|
|
406
|
+
}
|
|
407
|
+
// =============================================================================
|
|
408
|
+
// Utilities
|
|
409
|
+
// =============================================================================
|
|
410
|
+
/**
|
|
411
|
+
* Format token count for display.
|
|
412
|
+
*/
|
|
413
|
+
export function formatTokenCount(tokens) {
|
|
414
|
+
if (tokens >= 1000000) {
|
|
415
|
+
return `${(tokens / 1000000).toFixed(1)}M`;
|
|
416
|
+
}
|
|
417
|
+
if (tokens >= 1000) {
|
|
418
|
+
return `${(tokens / 1000).toFixed(1)}k`;
|
|
419
|
+
}
|
|
420
|
+
return tokens.toString();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Benchmark token estimation performance.
|
|
424
|
+
*/
|
|
425
|
+
export function benchmarkTokenEstimation(iterations = 10000) {
|
|
426
|
+
const testTexts = [
|
|
427
|
+
'Hello world',
|
|
428
|
+
'This is a longer piece of text that contains multiple sentences and should take more time to process.',
|
|
429
|
+
'```typescript\nfunction hello() {\n console.log("Hello");\n}\n```',
|
|
430
|
+
'A'.repeat(10000), // 10k chars
|
|
431
|
+
];
|
|
432
|
+
let maxNs = 0;
|
|
433
|
+
let totalTokens = 0;
|
|
434
|
+
const start = process.hrtime.bigint();
|
|
435
|
+
for (let i = 0; i < iterations; i++) {
|
|
436
|
+
for (const text of testTexts) {
|
|
437
|
+
const s = process.hrtime.bigint();
|
|
438
|
+
const tokens = estimateTokens(text);
|
|
439
|
+
totalTokens += tokens;
|
|
440
|
+
const elapsed = Number(process.hrtime.bigint() - s);
|
|
441
|
+
if (elapsed > maxNs)
|
|
442
|
+
maxNs = elapsed;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const totalNs = Number(process.hrtime.bigint() - start);
|
|
446
|
+
const totalMs = totalNs / 1_000_000;
|
|
447
|
+
return {
|
|
448
|
+
avgNs: totalNs / (iterations * testTexts.length),
|
|
449
|
+
maxNs,
|
|
450
|
+
tokensPerMs: totalTokens / totalMs,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=context-compaction.js.map
|
package/dist/memory/index.d.ts
CHANGED
|
@@ -29,4 +29,5 @@ export * from './adapters/index.js';
|
|
|
29
29
|
export { createMemoryAdapter, getMemoryConfigFromEnv } from './factory.js';
|
|
30
30
|
export { createMemoryService } from './service.js';
|
|
31
31
|
export { createMemoryHooks, getMemoryHooks } from './memory-hooks.js';
|
|
32
|
+
export * from './context-compaction.js';
|
|
32
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/memory/index.js
CHANGED
|
@@ -29,4 +29,5 @@ export * from './adapters/index.js';
|
|
|
29
29
|
export { createMemoryAdapter, getMemoryConfigFromEnv } from './factory.js';
|
|
30
30
|
export { createMemoryService } from './service.js';
|
|
31
31
|
export { createMemoryHooks, getMemoryHooks } from './memory-hooks.js';
|
|
32
|
+
export * from './context-compaction.js';
|
|
32
33
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Channels for group communication
|
|
7
7
|
* - Direct messaging between any combination of users and agents
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { generateId } from '../utils/id-generator.js';
|
|
10
10
|
import { PROTOCOL_VERSION } from './types.js';
|
|
11
11
|
// Re-export PROTOCOL_VERSION for convenience
|
|
12
12
|
export { PROTOCOL_VERSION };
|
|
@@ -48,7 +48,7 @@ export function createChannelJoinEnvelope(from, channel, options) {
|
|
|
48
48
|
return {
|
|
49
49
|
v: PROTOCOL_VERSION,
|
|
50
50
|
type: 'CHANNEL_JOIN',
|
|
51
|
-
id:
|
|
51
|
+
id: generateId(),
|
|
52
52
|
ts: Date.now(),
|
|
53
53
|
from,
|
|
54
54
|
payload: {
|
|
@@ -65,7 +65,7 @@ export function createChannelLeaveEnvelope(from, channel, reason) {
|
|
|
65
65
|
return {
|
|
66
66
|
v: PROTOCOL_VERSION,
|
|
67
67
|
type: 'CHANNEL_LEAVE',
|
|
68
|
-
id:
|
|
68
|
+
id: generateId(),
|
|
69
69
|
ts: Date.now(),
|
|
70
70
|
from,
|
|
71
71
|
payload: {
|
|
@@ -81,7 +81,7 @@ export function createChannelMessageEnvelope(from, channel, body, options) {
|
|
|
81
81
|
return {
|
|
82
82
|
v: PROTOCOL_VERSION,
|
|
83
83
|
type: 'CHANNEL_MESSAGE',
|
|
84
|
-
id:
|
|
84
|
+
id: generateId(),
|
|
85
85
|
ts: Date.now(),
|
|
86
86
|
from,
|
|
87
87
|
payload: {
|
|
@@ -1,32 +1,94 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Frame encoding/decoding for the agent relay protocol.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Wire format:
|
|
5
|
+
* - 1 byte: format indicator (0 = JSON, 1 = MessagePack)
|
|
6
|
+
* - 4 bytes: big-endian payload length
|
|
7
|
+
* - N bytes: payload (JSON or MessagePack encoded)
|
|
8
|
+
*
|
|
9
|
+
* Optimizations:
|
|
10
|
+
* - Ring buffer to avoid Buffer.concat allocations
|
|
11
|
+
* - MessagePack support for faster serialization (optional)
|
|
12
|
+
* - Zero-copy frame extraction where possible
|
|
4
13
|
*/
|
|
5
14
|
import type { Envelope } from './types.js';
|
|
6
15
|
export declare const MAX_FRAME_BYTES: number;
|
|
7
|
-
export declare const HEADER_SIZE =
|
|
16
|
+
export declare const HEADER_SIZE = 5;
|
|
17
|
+
export declare const LEGACY_HEADER_SIZE = 4;
|
|
18
|
+
export type WireFormat = 'json' | 'msgpack';
|
|
19
|
+
/**
|
|
20
|
+
* Initialize MessagePack support. Call this at startup if you want msgpack.
|
|
21
|
+
* Install @msgpack/msgpack to enable: npm install @msgpack/msgpack
|
|
22
|
+
*/
|
|
23
|
+
export declare function initMessagePack(): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if MessagePack is available.
|
|
26
|
+
*/
|
|
27
|
+
export declare function hasMessagePack(): boolean;
|
|
8
28
|
/**
|
|
9
29
|
* Encode a message envelope into a framed buffer.
|
|
30
|
+
*
|
|
31
|
+
* @param envelope - The envelope to encode
|
|
32
|
+
* @param format - Wire format to use (default: 'json')
|
|
33
|
+
* @returns Framed buffer ready for socket write
|
|
34
|
+
*/
|
|
35
|
+
export declare function encodeFrame(envelope: Envelope, format?: WireFormat): Buffer;
|
|
36
|
+
/**
|
|
37
|
+
* Encode a frame in legacy format (no format byte, JSON only).
|
|
38
|
+
* Used for backwards compatibility with older clients.
|
|
10
39
|
*/
|
|
11
|
-
export declare function
|
|
40
|
+
export declare function encodeFrameLegacy(envelope: Envelope): Buffer;
|
|
12
41
|
/**
|
|
13
|
-
*
|
|
42
|
+
* Ring buffer-based frame parser for streaming data.
|
|
43
|
+
*
|
|
44
|
+
* Optimizations:
|
|
45
|
+
* - Pre-allocated buffer avoids GC pressure from Buffer.concat
|
|
46
|
+
* - Compaction only when necessary (wrap-around)
|
|
47
|
+
* - Direct parsing from buffer offsets
|
|
14
48
|
*/
|
|
15
49
|
export declare class FrameParser {
|
|
16
|
-
private
|
|
17
|
-
private
|
|
50
|
+
private ring;
|
|
51
|
+
private head;
|
|
52
|
+
private tail;
|
|
53
|
+
private readonly capacity;
|
|
54
|
+
private readonly maxFrameBytes;
|
|
55
|
+
private format;
|
|
56
|
+
private legacyMode;
|
|
18
57
|
constructor(maxFrameBytes?: number);
|
|
58
|
+
/**
|
|
59
|
+
* Set the expected wire format for parsing.
|
|
60
|
+
*/
|
|
61
|
+
setFormat(format: WireFormat): void;
|
|
62
|
+
/**
|
|
63
|
+
* Enable legacy mode (4-byte header, JSON only).
|
|
64
|
+
*/
|
|
65
|
+
setLegacyMode(legacy: boolean): void;
|
|
66
|
+
/**
|
|
67
|
+
* Get current unread bytes in buffer.
|
|
68
|
+
*/
|
|
69
|
+
get pendingBytes(): number;
|
|
19
70
|
/**
|
|
20
71
|
* Push data into the parser and extract complete frames.
|
|
72
|
+
*
|
|
73
|
+
* @param data - Incoming data buffer
|
|
74
|
+
* @returns Array of parsed envelope frames
|
|
21
75
|
*/
|
|
22
76
|
push(data: Buffer): Envelope[];
|
|
23
77
|
/**
|
|
24
|
-
*
|
|
78
|
+
* Extract all complete frames from the buffer.
|
|
25
79
|
*/
|
|
26
|
-
|
|
80
|
+
private extractFrames;
|
|
27
81
|
/**
|
|
28
|
-
*
|
|
82
|
+
* Decode payload based on format byte.
|
|
29
83
|
*/
|
|
30
|
-
|
|
84
|
+
private decodePayload;
|
|
85
|
+
/**
|
|
86
|
+
* Compact the buffer by shifting unread data to the start.
|
|
87
|
+
*/
|
|
88
|
+
private compact;
|
|
89
|
+
/**
|
|
90
|
+
* Reset parser state (e.g., on connection reset).
|
|
91
|
+
*/
|
|
92
|
+
reset(): void;
|
|
31
93
|
}
|
|
32
94
|
//# sourceMappingURL=framing.d.ts.map
|