byterover-cli 2.0.0 → 2.1.0
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 +6 -81
- package/dist/agent/core/domain/llm/index.d.ts +1 -1
- package/dist/agent/core/domain/llm/index.js +1 -1
- package/dist/agent/core/domain/llm/registry.d.ts +8 -0
- package/dist/agent/core/domain/llm/registry.js +34 -0
- package/dist/agent/core/domain/sandbox/types.d.ts +2 -0
- package/dist/agent/core/domain/tools/constants.d.ts +3 -0
- package/dist/agent/core/domain/tools/constants.js +3 -0
- package/dist/agent/core/interfaces/cipher-services.d.ts +2 -4
- package/dist/agent/core/interfaces/i-cipher-agent.d.ts +9 -1
- package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
- package/dist/agent/core/interfaces/i-tool-provider.d.ts +10 -0
- package/dist/agent/core/interfaces/i-tool-scheduler.d.ts +9 -0
- package/dist/agent/infra/agent/agent-schemas.d.ts +0 -9
- package/dist/agent/infra/agent/agent-schemas.js +0 -3
- package/dist/agent/infra/agent/cipher-agent.d.ts +25 -1
- package/dist/agent/infra/agent/cipher-agent.js +138 -11
- package/dist/agent/infra/agent/provider-update-config.d.ts +0 -2
- package/dist/agent/infra/agent/service-initializer.d.ts +2 -6
- package/dist/agent/infra/agent/service-initializer.js +45 -38
- package/dist/agent/infra/blob/blob-storage-factory.d.ts +2 -2
- package/dist/agent/infra/blob/blob-storage-factory.js +4 -4
- package/dist/agent/infra/blob/file-blob-storage.d.ts +96 -0
- package/dist/agent/infra/blob/file-blob-storage.js +454 -0
- package/dist/agent/infra/blob/index.d.ts +2 -3
- package/dist/agent/infra/blob/index.js +4 -6
- package/dist/agent/infra/llm/agent-llm-service.d.ts +3 -0
- package/dist/agent/infra/llm/agent-llm-service.js +34 -52
- package/dist/agent/infra/llm/context/compression/compression-helpers.d.ts +35 -0
- package/dist/agent/infra/llm/context/compression/compression-helpers.js +124 -0
- package/dist/agent/infra/llm/context/compression/escalated-compression.d.ts +62 -0
- package/dist/agent/infra/llm/context/compression/escalated-compression.js +144 -0
- package/dist/agent/infra/llm/context/compression/index.d.ts +3 -0
- package/dist/agent/infra/llm/context/compression/index.js +3 -0
- package/dist/agent/infra/llm/context/compression/reactive-overflow.d.ts +0 -27
- package/dist/agent/infra/llm/context/compression/reactive-overflow.js +5 -122
- package/dist/agent/infra/llm/context/context-manager.d.ts +20 -1
- package/dist/agent/infra/llm/context/context-manager.js +37 -7
- package/dist/agent/infra/llm/providers/index.js +0 -2
- package/dist/agent/infra/llm/providers/types.d.ts +1 -5
- package/dist/agent/infra/map/agentic-map-service.d.ts +97 -0
- package/dist/agent/infra/map/agentic-map-service.js +309 -0
- package/dist/agent/infra/map/context-tree-store.d.ts +94 -0
- package/dist/agent/infra/map/context-tree-store.js +278 -0
- package/dist/agent/infra/map/index.d.ts +4 -0
- package/dist/agent/infra/map/index.js +4 -0
- package/dist/agent/infra/map/llm-map-memory.d.ts +59 -0
- package/dist/agent/infra/map/llm-map-memory.js +187 -0
- package/dist/agent/infra/map/llm-map-service.d.ts +36 -0
- package/dist/agent/infra/map/llm-map-service.js +118 -0
- package/dist/agent/infra/map/map-shared.d.ts +140 -0
- package/dist/agent/infra/map/map-shared.js +325 -0
- package/dist/agent/infra/map/worker-pool.d.ts +45 -0
- package/dist/agent/infra/map/worker-pool.js +73 -0
- package/dist/agent/infra/sandbox/curation-helpers.d.ts +62 -0
- package/dist/agent/infra/sandbox/curation-helpers.js +219 -0
- package/dist/agent/infra/sandbox/sandbox-service.d.ts +12 -0
- package/dist/agent/infra/sandbox/sandbox-service.js +39 -7
- package/dist/agent/infra/sandbox/tools-sdk.d.ts +48 -1
- package/dist/agent/infra/sandbox/tools-sdk.js +52 -1
- package/dist/agent/infra/session/session-manager.d.ts +8 -1
- package/dist/agent/infra/session/session-manager.js +24 -4
- package/dist/agent/infra/storage/file-key-storage.d.ts +142 -0
- package/dist/agent/infra/storage/file-key-storage.js +572 -0
- package/dist/agent/infra/storage/granular-history-storage.d.ts +1 -1
- package/dist/agent/infra/storage/granular-history-storage.js +1 -1
- package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.d.ts +4 -0
- package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.js +42 -14
- package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.d.ts +16 -0
- package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.js +47 -0
- package/dist/agent/infra/tools/core-tool-scheduler.js +3 -1
- package/dist/agent/infra/tools/implementations/agentic-map-tool.d.ts +35 -0
- package/dist/agent/infra/tools/implementations/agentic-map-tool.js +156 -0
- package/dist/agent/infra/tools/implementations/code-exec-tool.js +1 -0
- package/dist/agent/infra/tools/implementations/curate-tool.d.ts +9 -9
- package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +18 -0
- package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +43 -0
- package/dist/agent/infra/tools/implementations/llm-map-tool.d.ts +24 -0
- package/dist/agent/infra/tools/implementations/llm-map-tool.js +87 -0
- package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +28 -1
- package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +27 -3
- package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -0
- package/dist/agent/infra/tools/implementations/search-knowledge-service.js +83 -12
- package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
- package/dist/agent/infra/tools/tool-manager.js +6 -0
- package/dist/agent/infra/tools/tool-provider.d.ts +12 -0
- package/dist/agent/infra/tools/tool-provider.js +78 -0
- package/dist/agent/infra/tools/tool-registry.d.ts +14 -0
- package/dist/agent/infra/tools/tool-registry.js +32 -0
- package/dist/agent/resources/prompts/system-prompt.yml +48 -74
- package/dist/agent/resources/tools/expand_knowledge.txt +20 -0
- package/dist/oclif/commands/curate/index.js +1 -2
- package/dist/oclif/commands/main.js +1 -0
- package/dist/oclif/commands/providers/connect.d.ts +1 -3
- package/dist/oclif/commands/providers/connect.js +7 -29
- package/dist/oclif/commands/query.js +1 -2
- package/dist/server/constants.d.ts +7 -0
- package/dist/server/constants.js +8 -0
- package/dist/server/core/domain/entities/provider-registry.js +1 -15
- package/dist/server/core/domain/knowledge/memory-scoring.js +1 -1
- package/dist/server/core/domain/knowledge/summary-types.d.ts +126 -0
- package/dist/server/core/domain/knowledge/summary-types.js +7 -0
- package/dist/server/core/domain/transport/schemas.d.ts +0 -4
- package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.d.ts +30 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.js +1 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.d.ts +30 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.js +1 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.d.ts +29 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.js +1 -0
- package/dist/server/infra/cogit/context-tree-to-push-context-mapper.js +10 -3
- package/dist/server/infra/connectors/skill/skill-connector.d.ts +4 -0
- package/dist/server/infra/connectors/skill/skill-connector.js +4 -0
- package/dist/server/infra/context-tree/children-hash.d.ts +20 -0
- package/dist/server/infra/context-tree/children-hash.js +22 -0
- package/dist/server/infra/context-tree/derived-artifact.d.ts +28 -0
- package/dist/server/infra/context-tree/derived-artifact.js +48 -0
- package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +37 -0
- package/dist/server/infra/context-tree/file-context-tree-archive-service.js +219 -0
- package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +50 -0
- package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +278 -0
- package/dist/server/infra/context-tree/file-context-tree-merger.js +4 -0
- package/dist/server/infra/context-tree/file-context-tree-snapshot-service.js +12 -4
- package/dist/server/infra/context-tree/file-context-tree-summary-service.d.ts +44 -0
- package/dist/server/infra/context-tree/file-context-tree-summary-service.js +313 -0
- package/dist/server/infra/context-tree/file-context-tree-writer-service.js +5 -0
- package/dist/server/infra/context-tree/prompts/summary-generation.d.ts +22 -0
- package/dist/server/infra/context-tree/prompts/summary-generation.js +45 -0
- package/dist/server/infra/context-tree/snapshot-diff.d.ts +19 -0
- package/dist/server/infra/context-tree/snapshot-diff.js +39 -0
- package/dist/server/infra/context-tree/summary-frontmatter.d.ts +24 -0
- package/dist/server/infra/context-tree/summary-frontmatter.js +111 -0
- package/dist/server/infra/daemon/agent-process.js +2 -14
- package/dist/server/infra/executor/curate-executor.d.ts +1 -0
- package/dist/server/infra/executor/curate-executor.js +82 -34
- package/dist/server/infra/executor/folder-pack-executor.js +1 -1
- package/dist/server/infra/executor/pre-compaction/compaction-escalation.d.ts +6 -0
- package/dist/server/infra/executor/pre-compaction/compaction-escalation.js +6 -0
- package/dist/server/infra/executor/pre-compaction/index.d.ts +3 -0
- package/dist/server/infra/executor/pre-compaction/index.js +1 -0
- package/dist/server/infra/executor/pre-compaction/pre-compaction-service.d.ts +59 -0
- package/dist/server/infra/executor/pre-compaction/pre-compaction-service.js +124 -0
- package/dist/server/infra/executor/pre-compaction/prompts.d.ts +24 -0
- package/dist/server/infra/executor/pre-compaction/prompts.js +47 -0
- package/dist/server/infra/executor/query-executor.d.ts +3 -0
- package/dist/server/infra/executor/query-executor.js +39 -4
- package/dist/server/infra/http/authenticated-http-client.js +4 -0
- package/dist/server/infra/http/provider-model-fetcher-registry.js +1 -5
- package/dist/server/infra/http/provider-model-fetchers.d.ts +0 -14
- package/dist/server/infra/http/provider-model-fetchers.js +0 -132
- package/dist/server/infra/provider/provider-config-resolver.js +0 -55
- package/dist/server/utils/curate-result-parser.d.ts +4 -4
- package/dist/shared/constants/curation.d.ts +6 -0
- package/dist/shared/constants/curation.js +6 -0
- package/dist/shared/utils/escalation-utils.d.ts +59 -0
- package/dist/shared/utils/escalation-utils.js +141 -0
- package/dist/tui/components/command-input.js +1 -1
- package/dist/tui/components/inline-prompts/inline-confirm.js +6 -1
- package/dist/tui/features/commands/definitions/exit.d.ts +2 -0
- package/dist/tui/features/commands/definitions/exit.js +9 -0
- package/dist/tui/features/commands/definitions/index.js +3 -0
- package/dist/tui/features/exit/components/exit-flow.d.ts +10 -0
- package/dist/tui/features/exit/components/exit-flow.js +19 -0
- package/dist/tui/features/provider/components/provider-flow.js +1 -21
- package/oclif.manifest.json +100 -109
- package/package.json +11 -4
- package/dist/agent/infra/blob/migrations.d.ts +0 -63
- package/dist/agent/infra/blob/migrations.js +0 -148
- package/dist/agent/infra/blob/sqlite-blob-storage.d.ts +0 -82
- package/dist/agent/infra/blob/sqlite-blob-storage.js +0 -307
- package/dist/agent/infra/llm/providers/google-vertex.d.ts +0 -15
- package/dist/agent/infra/llm/providers/google-vertex.js +0 -36
- package/dist/agent/infra/storage/blob-history-storage.d.ts +0 -81
- package/dist/agent/infra/storage/blob-history-storage.js +0 -193
- package/dist/agent/infra/storage/dual-format-history-storage.d.ts +0 -83
- package/dist/agent/infra/storage/dual-format-history-storage.js +0 -165
- package/dist/agent/infra/storage/sqlite-key-storage.d.ts +0 -113
- package/dist/agent/infra/storage/sqlite-key-storage.js +0 -438
- package/dist/server/infra/provider/vertex-ai-utils.d.ts +0 -10
- package/dist/server/infra/provider/vertex-ai-utils.js +0 -28
- package/dist/tui/features/provider/components/credential-path-dialog.d.ts +0 -30
- package/dist/tui/features/provider/components/credential-path-dialog.js +0 -85
|
@@ -243,6 +243,32 @@ export class ContextManager {
|
|
|
243
243
|
}
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Compress messages using the strategy chain and replace in-memory state.
|
|
248
|
+
* Called by AgentLLMService when context exceeds the threshold.
|
|
249
|
+
*
|
|
250
|
+
* Delegates to compressHistoryIfNeeded() which iterates compressionStrategies
|
|
251
|
+
* (EscalatedCompression → MiddleRemoval → OldestRemoval) until the history
|
|
252
|
+
* fits within the token budget.
|
|
253
|
+
*
|
|
254
|
+
* @param systemPromptTokens - Tokens reserved for the system prompt
|
|
255
|
+
* @param targetHistoryBudget - Target token budget for message history.
|
|
256
|
+
* When provided, overrides maxInputTokens for threshold/budget calculations
|
|
257
|
+
* so the strategy chain compresses to the caller's target (e.g. 70% utilization)
|
|
258
|
+
* rather than the full context window.
|
|
259
|
+
* @returns The compressed message array (same reference as this.messages)
|
|
260
|
+
*/
|
|
261
|
+
async compressAndReplace(systemPromptTokens, targetHistoryBudget) {
|
|
262
|
+
const targetMaxTokens = targetHistoryBudget
|
|
263
|
+
? targetHistoryBudget + systemPromptTokens
|
|
264
|
+
: undefined;
|
|
265
|
+
const compressed = await this.compressHistoryIfNeeded(systemPromptTokens, undefined, targetMaxTokens);
|
|
266
|
+
if (compressed !== this.messages) {
|
|
267
|
+
this.messages = compressed;
|
|
268
|
+
this.persistDirty = true;
|
|
269
|
+
}
|
|
270
|
+
return this.messages;
|
|
271
|
+
}
|
|
246
272
|
/**
|
|
247
273
|
* Compress messages by removing oldest messages until total tokens fit within the budget.
|
|
248
274
|
* This directly modifies the internal messages array by slicing from the beginning.
|
|
@@ -514,26 +540,30 @@ export class ContextManager {
|
|
|
514
540
|
* Compress conversation history if needed to fit within token limits.
|
|
515
541
|
*
|
|
516
542
|
* This method applies compression strategies sequentially until the history
|
|
517
|
-
* fits within the available token budget
|
|
543
|
+
* fits within the available token budget.
|
|
518
544
|
*
|
|
519
545
|
* @param systemPromptTokens - Tokens used by system prompt (reserved, not compressible)
|
|
520
546
|
* @param messagesToCompress - Messages to compress (defaults to all messages)
|
|
547
|
+
* @param targetMaxTokens - Override for maxInputTokens. When provided, the method
|
|
548
|
+
* uses this as the total token ceiling (system + history) instead of this.maxInputTokens.
|
|
549
|
+
* This allows the caller to target a lower utilization (e.g. 70%) rather than 100%.
|
|
521
550
|
* @returns Compressed message history
|
|
522
551
|
*/
|
|
523
|
-
async compressHistoryIfNeeded(systemPromptTokens, messagesToCompress) {
|
|
552
|
+
async compressHistoryIfNeeded(systemPromptTokens, messagesToCompress, targetMaxTokens) {
|
|
553
|
+
const effectiveMaxTokens = targetMaxTokens ?? this.maxInputTokens;
|
|
524
554
|
const messages = messagesToCompress ?? this.messages;
|
|
525
555
|
// Calculate current token usage
|
|
526
556
|
const currentHistoryTokens = countMessagesTokens(messages, this.tokenizer);
|
|
527
557
|
const totalTokens = systemPromptTokens + currentHistoryTokens;
|
|
528
558
|
// No compression needed
|
|
529
|
-
if (totalTokens <=
|
|
559
|
+
if (totalTokens <= effectiveMaxTokens) {
|
|
530
560
|
// Debug logging removed for cleaner user experience
|
|
531
561
|
return messages;
|
|
532
562
|
}
|
|
533
563
|
// Debug logging removed for cleaner user experience
|
|
534
564
|
// Calculate target token budget for history
|
|
535
565
|
// Reserve space for system prompt
|
|
536
|
-
const maxHistoryTokens =
|
|
566
|
+
const maxHistoryTokens = effectiveMaxTokens - systemPromptTokens;
|
|
537
567
|
// Apply compression strategies sequentially
|
|
538
568
|
let compressedHistory = messages;
|
|
539
569
|
for (const strategy of this.compressionStrategies) {
|
|
@@ -543,7 +573,7 @@ export class ContextManager {
|
|
|
543
573
|
// Check if we've met the token limit
|
|
544
574
|
const compressedTokens = countMessagesTokens(compressedHistory, this.tokenizer);
|
|
545
575
|
const newTotal = systemPromptTokens + compressedTokens;
|
|
546
|
-
if (newTotal <=
|
|
576
|
+
if (newTotal <= effectiveMaxTokens) {
|
|
547
577
|
// Debug logging removed for cleaner user experience
|
|
548
578
|
break;
|
|
549
579
|
}
|
|
@@ -551,12 +581,12 @@ export class ContextManager {
|
|
|
551
581
|
// Final token count
|
|
552
582
|
const finalTokens = countMessagesTokens(compressedHistory, this.tokenizer);
|
|
553
583
|
const finalTotal = systemPromptTokens + finalTokens;
|
|
554
|
-
if (finalTotal >
|
|
584
|
+
if (finalTotal > effectiveMaxTokens) {
|
|
555
585
|
// Keep warning as it's important for users to know
|
|
556
586
|
this.logger.warn('Unable to compress below token limit', {
|
|
587
|
+
effectiveMaxTokens,
|
|
557
588
|
finalTokens,
|
|
558
589
|
finalTotal,
|
|
559
|
-
maxInputTokens: this.maxInputTokens,
|
|
560
590
|
sessionId: this.sessionId,
|
|
561
591
|
systemPromptTokens,
|
|
562
592
|
});
|
|
@@ -11,7 +11,6 @@ import { cerebrasProvider } from './cerebras.js';
|
|
|
11
11
|
import { cohereProvider } from './cohere.js';
|
|
12
12
|
import { deepinfraProvider } from './deepinfra.js';
|
|
13
13
|
import { glmProvider } from './glm.js';
|
|
14
|
-
import { googleVertexProvider } from './google-vertex.js';
|
|
15
14
|
import { googleProvider } from './google.js';
|
|
16
15
|
import { groqProvider } from './groq.js';
|
|
17
16
|
import { minimaxProvider } from './minimax.js';
|
|
@@ -36,7 +35,6 @@ const PROVIDER_MODULES = {
|
|
|
36
35
|
deepinfra: deepinfraProvider,
|
|
37
36
|
glm: glmProvider,
|
|
38
37
|
google: googleProvider,
|
|
39
|
-
'google-vertex': googleVertexProvider,
|
|
40
38
|
groq: groqProvider,
|
|
41
39
|
minimax: minimaxProvider,
|
|
42
40
|
mistral: mistralProvider,
|
|
@@ -8,7 +8,7 @@ import type { IContentGenerator } from '../../../core/interfaces/i-content-gener
|
|
|
8
8
|
/**
|
|
9
9
|
* Provider authentication type.
|
|
10
10
|
*/
|
|
11
|
-
export type ProviderAuthType = 'api-key' | 'internal'
|
|
11
|
+
export type ProviderAuthType = 'api-key' | 'internal';
|
|
12
12
|
/**
|
|
13
13
|
* Internal provider type determines formatter/tokenizer selection.
|
|
14
14
|
* - 'claude': Uses ClaudeMessageFormatter + ClaudeTokenizer
|
|
@@ -31,14 +31,10 @@ export interface GeneratorFactoryConfig {
|
|
|
31
31
|
readonly httpConfig?: Record<string, unknown>;
|
|
32
32
|
/** HTTP Referer header (for OpenRouter) */
|
|
33
33
|
readonly httpReferer?: string;
|
|
34
|
-
/** GCP location for Vertex AI */
|
|
35
|
-
readonly location?: string;
|
|
36
34
|
/** Maximum tokens in the response */
|
|
37
35
|
readonly maxTokens: number;
|
|
38
36
|
/** Model identifier */
|
|
39
37
|
readonly model: string;
|
|
40
|
-
/** GCP project ID for Vertex AI */
|
|
41
|
-
readonly project?: string;
|
|
42
38
|
/** Site name (for OpenRouter) */
|
|
43
39
|
readonly siteName?: string;
|
|
44
40
|
/** Temperature for randomness */
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ICipherAgent } from '../../core/interfaces/i-cipher-agent.js';
|
|
2
|
+
import type { ILogger } from '../../core/interfaces/i-logger.js';
|
|
3
|
+
import type { ContextTreeStore } from './context-tree-store.js';
|
|
4
|
+
import { type AgenticMapParameters } from './map-shared.js';
|
|
5
|
+
import { type MapProgress, type MapRunResult } from './worker-pool.js';
|
|
6
|
+
/** Absolute depth ceiling — cannot be exceeded regardless of LLM-supplied max_depth */
|
|
7
|
+
export declare const HARD_MAX_DEPTH = 3;
|
|
8
|
+
interface NestingRecord {
|
|
9
|
+
absoluteMaxDepth: number;
|
|
10
|
+
ancestorInputPaths: ReadonlySet<string>;
|
|
11
|
+
isRootCaller: boolean;
|
|
12
|
+
mapRunId: string;
|
|
13
|
+
nestingDepth: number;
|
|
14
|
+
/** Agent instance that registered this root-eligible session (undefined for sub-sessions) */
|
|
15
|
+
ownerId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Read-only accessor — raw map is not exported */
|
|
18
|
+
export declare function getNestingRecord(sessionId: string): Readonly<NestingRecord> | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Register a session as root-eligible for write-enabled agentic_map calls.
|
|
21
|
+
* Must be called at session creation time (e.g., CipherAgent.start/createSession).
|
|
22
|
+
* Idempotent for already-registered root callers with the same ownerId.
|
|
23
|
+
* Throws if the session is already registered as a sub-session (isRootCaller: false) —
|
|
24
|
+
* this indicates a bug (task-session ID passed to a root-eligible creation path).
|
|
25
|
+
* Throws if the session is already registered to a DIFFERENT ownerId — this
|
|
26
|
+
* indicates a cross-agent session ID collision and must fail fast.
|
|
27
|
+
*
|
|
28
|
+
* @param sessionId - Session to register
|
|
29
|
+
* @param ownerId - Unique identifier for the agent instance that owns this registration.
|
|
30
|
+
* Used to scope deregistration: only the owning agent can remove the record.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerRootEligibleSession(sessionId: string, ownerId: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Remove a root-eligible session registration.
|
|
35
|
+
* Call when a session registered via registerRootEligibleSession() is deleted.
|
|
36
|
+
* Only deletes if the record's ownerId matches the caller — prevents one agent
|
|
37
|
+
* instance from removing another agent's live record.
|
|
38
|
+
* Safe to call for unregistered sessions (no-op).
|
|
39
|
+
*
|
|
40
|
+
* @param sessionId - Session to deregister
|
|
41
|
+
* @param ownerId - Must match the ownerId used at registration time
|
|
42
|
+
*/
|
|
43
|
+
export declare function deregisterRootEligibleSession(sessionId: string, ownerId: string): void;
|
|
44
|
+
/** FOR TEST USE ONLY — do not call in production code */
|
|
45
|
+
export declare function _resetNestingRegistryForTests(): void;
|
|
46
|
+
/** FOR TEST USE ONLY — inject a sub-session record into the nesting registry */
|
|
47
|
+
export declare function _setNestingRecordForTests(sessionId: string, record: {
|
|
48
|
+
absoluteMaxDepth: number;
|
|
49
|
+
ancestorInputPaths: ReadonlySet<string>;
|
|
50
|
+
isRootCaller: boolean;
|
|
51
|
+
mapRunId: string;
|
|
52
|
+
nestingDepth: number;
|
|
53
|
+
}): void;
|
|
54
|
+
/** FOR TEST USE ONLY — inject entries into the runSessionIndex */
|
|
55
|
+
export declare function _setRunSessionIndexForTests(mapRunId: string, sessionIds: Set<string>): void;
|
|
56
|
+
/** FOR TEST USE ONLY — expose cleanupMapRun for registry lifecycle tests */
|
|
57
|
+
export declare function _cleanupMapRunForTests(mapRunId: string): void;
|
|
58
|
+
export interface AgenticMapServiceOptions {
|
|
59
|
+
/** Abort signal for cancellation */
|
|
60
|
+
abortSignal?: AbortSignal;
|
|
61
|
+
/** The cipher agent instance for creating sub-sessions */
|
|
62
|
+
agent: ICipherAgent;
|
|
63
|
+
/** Internal: canonical input paths of all ancestor calls (anti-cycle) */
|
|
64
|
+
ancestorInputPaths?: ReadonlySet<string>;
|
|
65
|
+
/** Optional context tree store for result aggregation */
|
|
66
|
+
contextTreeStore?: ContextTreeStore;
|
|
67
|
+
/** Internal: root's clamped max depth, inherited by descendants */
|
|
68
|
+
effectiveMaxDepth?: number;
|
|
69
|
+
/** Optional logger for fail-open warnings */
|
|
70
|
+
logger?: ILogger;
|
|
71
|
+
/** Internal: root run ID for bulk sub-session cleanup */
|
|
72
|
+
mapRunId?: string;
|
|
73
|
+
/** Internal: nesting depth of this call (0 = root) */
|
|
74
|
+
nestingDepth?: number;
|
|
75
|
+
/** Progress callback */
|
|
76
|
+
onProgress?: (progress: MapProgress) => void;
|
|
77
|
+
/** Tool parameters from the LLM */
|
|
78
|
+
params: AgenticMapParameters;
|
|
79
|
+
/** Task ID for event routing */
|
|
80
|
+
taskId?: string;
|
|
81
|
+
/** Working directory (project root) */
|
|
82
|
+
workingDirectory: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Execute an Agentic-Map: parallel sub-agent sessions over a JSONL file.
|
|
86
|
+
*
|
|
87
|
+
* For each item (line), spawns a full agent session with tool access.
|
|
88
|
+
* The sub-agent must output a JSON value that validates against the provided
|
|
89
|
+
* output schema.
|
|
90
|
+
*
|
|
91
|
+
* Ported from VoltCode's agentic-map.ts, adapted for byterover-cli:
|
|
92
|
+
* - Uses agent.createTaskSession() / agent.executeOnSession() instead of Session.create()
|
|
93
|
+
* - Uses in-memory worker pool (no FileMapStore / PostgreSQL)
|
|
94
|
+
* - Concurrency capped at 4 (CLI runs on user machines)
|
|
95
|
+
*/
|
|
96
|
+
export declare function executeAgenticMap(options: AgenticMapServiceOptions): Promise<MapRunResult>;
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { buildRecursiveCompositionGuidance, buildUserMessage, canonicalizePath, parseJsonlFile, resolveAndValidatePath, validateAgainstSchema, withTimeout, } from './map-shared.js';
|
|
4
|
+
import { runMapWorkerPool } from './worker-pool.js';
|
|
5
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
6
|
+
/** Absolute depth ceiling — cannot be exceeded regardless of LLM-supplied max_depth */
|
|
7
|
+
export const HARD_MAX_DEPTH = 3;
|
|
8
|
+
/** Max parallel sub-agent sessions (lower than VoltCode's 16 for CLI machines) */
|
|
9
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
10
|
+
const nestingRegistry = new Map();
|
|
11
|
+
const runSessionIndex = new Map();
|
|
12
|
+
/** Read-only accessor — raw map is not exported */
|
|
13
|
+
export function getNestingRecord(sessionId) {
|
|
14
|
+
return nestingRegistry.get(sessionId);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register a session as root-eligible for write-enabled agentic_map calls.
|
|
18
|
+
* Must be called at session creation time (e.g., CipherAgent.start/createSession).
|
|
19
|
+
* Idempotent for already-registered root callers with the same ownerId.
|
|
20
|
+
* Throws if the session is already registered as a sub-session (isRootCaller: false) —
|
|
21
|
+
* this indicates a bug (task-session ID passed to a root-eligible creation path).
|
|
22
|
+
* Throws if the session is already registered to a DIFFERENT ownerId — this
|
|
23
|
+
* indicates a cross-agent session ID collision and must fail fast.
|
|
24
|
+
*
|
|
25
|
+
* @param sessionId - Session to register
|
|
26
|
+
* @param ownerId - Unique identifier for the agent instance that owns this registration.
|
|
27
|
+
* Used to scope deregistration: only the owning agent can remove the record.
|
|
28
|
+
*/
|
|
29
|
+
export function registerRootEligibleSession(sessionId, ownerId) {
|
|
30
|
+
const existing = nestingRegistry.get(sessionId);
|
|
31
|
+
if (existing !== undefined) {
|
|
32
|
+
if (!existing.isRootCaller) {
|
|
33
|
+
throw new Error(`registerRootEligibleSession: session "${sessionId}" is already registered as a ` +
|
|
34
|
+
`sub-session. Cannot promote to root-eligible. This is a bug.`);
|
|
35
|
+
}
|
|
36
|
+
if (existing.ownerId !== ownerId) {
|
|
37
|
+
throw new Error(`registerRootEligibleSession: session "${sessionId}" is already owned by a different ` +
|
|
38
|
+
`agent instance. Refusing to share root-eligible registration across owners.`);
|
|
39
|
+
}
|
|
40
|
+
return; // already root-eligible for same owner — idempotent no-op
|
|
41
|
+
}
|
|
42
|
+
nestingRegistry.set(sessionId, {
|
|
43
|
+
absoluteMaxDepth: 0, // not used for root callers
|
|
44
|
+
ancestorInputPaths: new Set(),
|
|
45
|
+
isRootCaller: true,
|
|
46
|
+
mapRunId: '', // not used; re-derived per invocation
|
|
47
|
+
nestingDepth: 0,
|
|
48
|
+
ownerId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove a root-eligible session registration.
|
|
53
|
+
* Call when a session registered via registerRootEligibleSession() is deleted.
|
|
54
|
+
* Only deletes if the record's ownerId matches the caller — prevents one agent
|
|
55
|
+
* instance from removing another agent's live record.
|
|
56
|
+
* Safe to call for unregistered sessions (no-op).
|
|
57
|
+
*
|
|
58
|
+
* @param sessionId - Session to deregister
|
|
59
|
+
* @param ownerId - Must match the ownerId used at registration time
|
|
60
|
+
*/
|
|
61
|
+
export function deregisterRootEligibleSession(sessionId, ownerId) {
|
|
62
|
+
const record = nestingRegistry.get(sessionId);
|
|
63
|
+
if (record?.isRootCaller && record.ownerId === ownerId) {
|
|
64
|
+
nestingRegistry.delete(sessionId);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** FOR TEST USE ONLY — do not call in production code */
|
|
68
|
+
export function _resetNestingRegistryForTests() {
|
|
69
|
+
nestingRegistry.clear();
|
|
70
|
+
runSessionIndex.clear();
|
|
71
|
+
}
|
|
72
|
+
/** FOR TEST USE ONLY — inject a sub-session record into the nesting registry */
|
|
73
|
+
export function _setNestingRecordForTests(sessionId, record) {
|
|
74
|
+
nestingRegistry.set(sessionId, record);
|
|
75
|
+
}
|
|
76
|
+
/** FOR TEST USE ONLY — inject entries into the runSessionIndex */
|
|
77
|
+
export function _setRunSessionIndexForTests(mapRunId, sessionIds) {
|
|
78
|
+
runSessionIndex.set(mapRunId, sessionIds);
|
|
79
|
+
}
|
|
80
|
+
/** FOR TEST USE ONLY — expose cleanupMapRun for registry lifecycle tests */
|
|
81
|
+
export function _cleanupMapRunForTests(mapRunId) {
|
|
82
|
+
cleanupMapRun(mapRunId);
|
|
83
|
+
}
|
|
84
|
+
/** Bulk cleanup for a completed root run. Called ONLY at nestingDepth === 0. */
|
|
85
|
+
function cleanupMapRun(mapRunId) {
|
|
86
|
+
const sids = runSessionIndex.get(mapRunId);
|
|
87
|
+
if (sids) {
|
|
88
|
+
for (const sid of sids) {
|
|
89
|
+
nestingRegistry.delete(sid); // all entries here are isRootCaller: false
|
|
90
|
+
}
|
|
91
|
+
runSessionIndex.delete(mapRunId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ── Agentic-Map Service ──────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Execute an Agentic-Map: parallel sub-agent sessions over a JSONL file.
|
|
97
|
+
*
|
|
98
|
+
* For each item (line), spawns a full agent session with tool access.
|
|
99
|
+
* The sub-agent must output a JSON value that validates against the provided
|
|
100
|
+
* output schema.
|
|
101
|
+
*
|
|
102
|
+
* Ported from VoltCode's agentic-map.ts, adapted for byterover-cli:
|
|
103
|
+
* - Uses agent.createTaskSession() / agent.executeOnSession() instead of Session.create()
|
|
104
|
+
* - Uses in-memory worker pool (no FileMapStore / PostgreSQL)
|
|
105
|
+
* - Concurrency capped at 4 (CLI runs on user machines)
|
|
106
|
+
*/
|
|
107
|
+
export async function executeAgenticMap(options) {
|
|
108
|
+
const { abortSignal, agent, onProgress, params, taskId, workingDirectory, } = options;
|
|
109
|
+
const { input_path: inputPath, max_attempts: maxAttempts = 3, output_path: outputPath, output_schema: outputSchema, prompt, read_only: readOnly = true, timeout_seconds: timeoutSeconds = 300, } = params;
|
|
110
|
+
// Destructure internal nesting options
|
|
111
|
+
const nestingDepth = options.nestingDepth ?? 0;
|
|
112
|
+
// Always clamp to HARD_MAX_DEPTH — defense-in-depth even if caller supplies a higher value
|
|
113
|
+
const effectiveMaxDepth = Math.min(options.effectiveMaxDepth ?? Math.min(params.max_depth ?? 1, HARD_MAX_DEPTH), HARD_MAX_DEPTH);
|
|
114
|
+
const ancestorInputPaths = options.ancestorInputPaths ?? new Set();
|
|
115
|
+
const mapRunId = options.mapRunId ?? randomUUID();
|
|
116
|
+
// Resolve paths relative to working directory and validate they don't escape it
|
|
117
|
+
const resolvedInputPath = resolveAndValidatePath(workingDirectory, inputPath);
|
|
118
|
+
const resolvedOutputPath = resolveAndValidatePath(workingDirectory, outputPath);
|
|
119
|
+
const canonicalInputPath = canonicalizePath(resolvedInputPath);
|
|
120
|
+
// Anti-cycle — unconditional (defense-in-depth regardless of read_only and allowlist state)
|
|
121
|
+
if (ancestorInputPaths.has(canonicalInputPath)) {
|
|
122
|
+
throw new Error(`agentic_map: cycle — input_path "${inputPath}" is already being processed ` +
|
|
123
|
+
`by an ancestor call. Use a distinct JSONL file at each nesting level.`);
|
|
124
|
+
}
|
|
125
|
+
// Depth — unconditional
|
|
126
|
+
if (nestingDepth >= effectiveMaxDepth) {
|
|
127
|
+
throw new Error(`agentic_map: nesting depth ${nestingDepth} has reached max_depth ${effectiveMaxDepth} ` +
|
|
128
|
+
`(hard ceiling: ${HARD_MAX_DEPTH}).`);
|
|
129
|
+
}
|
|
130
|
+
// Depth-aware concurrency
|
|
131
|
+
const concurrency = Math.max(1, Math.floor(DEFAULT_CONCURRENCY / (nestingDepth + 1)));
|
|
132
|
+
// Always include current input path in child ancestor set (covers read_only branches too)
|
|
133
|
+
const childAncestorPaths = new Set([canonicalInputPath, ...ancestorInputPaths]);
|
|
134
|
+
// 1. Parse input JSONL
|
|
135
|
+
const items = await parseJsonlFile(resolvedInputPath);
|
|
136
|
+
if (items.length === 0) {
|
|
137
|
+
await writeFile(resolvedOutputPath, '', 'utf8');
|
|
138
|
+
return { failed: 0, mapId: 'empty', succeeded: 0, total: 0 };
|
|
139
|
+
}
|
|
140
|
+
// 2. Prepare run metadata
|
|
141
|
+
const runStartedAt = new Date().toISOString();
|
|
142
|
+
// Track created sessions for cleanup
|
|
143
|
+
const sessionIds = [];
|
|
144
|
+
try {
|
|
145
|
+
// 3. Define per-item processing function
|
|
146
|
+
async function processItem(itemIndex, item) {
|
|
147
|
+
// Create a per-item task session
|
|
148
|
+
// When read_only, use 'query' command type to restrict sub-agent to read-only tools
|
|
149
|
+
const itemTaskId = `map-item-${itemIndex}-${randomUUID().slice(0, 8)}`;
|
|
150
|
+
const sessionCommandType = readOnly ? 'query' : 'curate';
|
|
151
|
+
// mapRootEligible intentionally omitted — sub-sessions are NOT root-eligible.
|
|
152
|
+
// The isRootCaller: false record is set directly below.
|
|
153
|
+
const sessionId = await agent.createTaskSession(itemTaskId, sessionCommandType);
|
|
154
|
+
sessionIds.push(sessionId);
|
|
155
|
+
// Register ALL sub-sessions for depth tracking (defense against future allowlist changes)
|
|
156
|
+
nestingRegistry.set(sessionId, {
|
|
157
|
+
absoluteMaxDepth: effectiveMaxDepth,
|
|
158
|
+
ancestorInputPaths: childAncestorPaths,
|
|
159
|
+
isRootCaller: false,
|
|
160
|
+
mapRunId,
|
|
161
|
+
nestingDepth: nestingDepth + 1,
|
|
162
|
+
});
|
|
163
|
+
if (!runSessionIndex.has(mapRunId))
|
|
164
|
+
runSessionIndex.set(mapRunId, new Set());
|
|
165
|
+
runSessionIndex.get(mapRunId).add(sessionId);
|
|
166
|
+
// Build user message
|
|
167
|
+
let userMessageText = buildUserMessage(prompt, 'pending', runStartedAt, itemIndex, item, outputSchema);
|
|
168
|
+
// Append guidance when further nesting is possible (write-enabled only)
|
|
169
|
+
const canNest = !readOnly && (nestingDepth + 1) < effectiveMaxDepth;
|
|
170
|
+
if (canNest) {
|
|
171
|
+
userMessageText += '\n\n' + buildRecursiveCompositionGuidance(nestingDepth + 1, effectiveMaxDepth);
|
|
172
|
+
}
|
|
173
|
+
// Per-item timeout
|
|
174
|
+
const timeoutController = new AbortController();
|
|
175
|
+
const timeoutHandle = setTimeout(() => {
|
|
176
|
+
timeoutController.abort();
|
|
177
|
+
}, timeoutSeconds * 1000);
|
|
178
|
+
try {
|
|
179
|
+
let attemptsUsed = 1;
|
|
180
|
+
// Full agentic prompt (with tool access, multi-step reasoning)
|
|
181
|
+
let result = await withTimeout(agent.executeOnSession(sessionId, userMessageText, {
|
|
182
|
+
executionContext: {
|
|
183
|
+
clearHistory: true,
|
|
184
|
+
commandType: sessionCommandType,
|
|
185
|
+
maxIterations: readOnly ? 10 : 20,
|
|
186
|
+
},
|
|
187
|
+
taskId: taskId ?? randomUUID(),
|
|
188
|
+
}), timeoutController.signal);
|
|
189
|
+
// Validation loop with retry
|
|
190
|
+
while (true) {
|
|
191
|
+
// Extract JSON from the response
|
|
192
|
+
let parsed;
|
|
193
|
+
let lastError = '';
|
|
194
|
+
// Try to parse the entire response as JSON first
|
|
195
|
+
try {
|
|
196
|
+
parsed = JSON.parse(result);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Try to extract JSON block from response
|
|
200
|
+
const jsonMatch = result.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
201
|
+
if (jsonMatch) {
|
|
202
|
+
try {
|
|
203
|
+
parsed = JSON.parse(jsonMatch[1]);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
lastError = `JSON parse error from code block: ${error instanceof Error ? error.message : String(error)}`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// Try to find JSON object/array in the response
|
|
211
|
+
const jsonObjMatch = result.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
|
|
212
|
+
if (jsonObjMatch) {
|
|
213
|
+
try {
|
|
214
|
+
parsed = JSON.parse(jsonObjMatch[1]);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
lastError = `JSON parse error from extraction: ${error instanceof Error ? error.message : String(error)}`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
lastError = 'No JSON found in response';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Validate against schema
|
|
226
|
+
if (parsed !== undefined) {
|
|
227
|
+
const validation = validateAgainstSchema(parsed, outputSchema);
|
|
228
|
+
if (validation.valid) {
|
|
229
|
+
// Fail-open: store result in context tree if available
|
|
230
|
+
if (options.contextTreeStore) {
|
|
231
|
+
try {
|
|
232
|
+
options.contextTreeStore.store(itemIndex, JSON.stringify(parsed));
|
|
233
|
+
}
|
|
234
|
+
catch (storeError) {
|
|
235
|
+
options.logger?.warn('Context tree store failed', { error: String(storeError), itemIndex });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
lastError = `Schema validation failed: ${validation.error}`;
|
|
241
|
+
}
|
|
242
|
+
// Check retry budget
|
|
243
|
+
if (attemptsUsed >= maxAttempts) {
|
|
244
|
+
throw new Error(`Failed after ${attemptsUsed} attempts. Last error: ${lastError}`);
|
|
245
|
+
}
|
|
246
|
+
// Check abort
|
|
247
|
+
if (abortSignal?.aborted || timeoutController.signal.aborted) {
|
|
248
|
+
throw new Error('Aborted or timed out');
|
|
249
|
+
}
|
|
250
|
+
// Retry by sending validation error back to the SAME session
|
|
251
|
+
attemptsUsed++;
|
|
252
|
+
const retryPrompt = [
|
|
253
|
+
`Validation failed: ${lastError}`,
|
|
254
|
+
'',
|
|
255
|
+
'Respond with corrected JSON only. No explanations, no markdown fences.',
|
|
256
|
+
].join('\n');
|
|
257
|
+
// eslint-disable-next-line no-await-in-loop
|
|
258
|
+
result = await withTimeout(agent.executeOnSession(sessionId, retryPrompt, {
|
|
259
|
+
executionContext: { commandType: sessionCommandType, maxIterations: 5 },
|
|
260
|
+
taskId: taskId ?? randomUUID(),
|
|
261
|
+
}), timeoutController.signal);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
clearTimeout(timeoutHandle);
|
|
266
|
+
// Do NOT delete sessions or registry here.
|
|
267
|
+
// Session deletion is handled by the outer finally (Promise.allSettled)
|
|
268
|
+
// to avoid a double-dispose race with concurrent deleteTaskSession calls.
|
|
269
|
+
// Registry cleanup is done exclusively by cleanupMapRun at root,
|
|
270
|
+
// ensuring orphaned sessions (withTimeout race) retain their depth records
|
|
271
|
+
// until the root run has fully completed.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// 4. Run in-memory worker pool
|
|
275
|
+
const result = await runMapWorkerPool({
|
|
276
|
+
abortSignal,
|
|
277
|
+
concurrency,
|
|
278
|
+
items,
|
|
279
|
+
onProgress,
|
|
280
|
+
processItem,
|
|
281
|
+
});
|
|
282
|
+
// 5. Compact context tree and attach summaryHandle (fail-open)
|
|
283
|
+
if (options.contextTreeStore) {
|
|
284
|
+
try {
|
|
285
|
+
await options.contextTreeStore.compact();
|
|
286
|
+
result.summaryHandle = options.contextTreeStore.getSummaryHandle();
|
|
287
|
+
}
|
|
288
|
+
catch (compactError) {
|
|
289
|
+
options.logger?.warn('Context tree compaction failed', { error: String(compactError) });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// 6. Write output JSONL from in-memory results (sorted by index)
|
|
293
|
+
const sorted = [...result.results.entries()].sort(([a], [b]) => a - b);
|
|
294
|
+
const outputContent = sorted.map(([, r]) => JSON.stringify(r)).join('\n');
|
|
295
|
+
await writeFile(resolvedOutputPath, outputContent, 'utf8');
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
// Await all session deletions before cleaning registry.
|
|
300
|
+
// Reduces the race window where orphaned sessions could call agentic_map
|
|
301
|
+
// after their records are wiped.
|
|
302
|
+
await Promise.allSettled(sessionIds.map((sid) => agent.deleteTaskSession(sid)));
|
|
303
|
+
// Bulk cleanup only at root — nested levels must NOT call this,
|
|
304
|
+
// as it would wipe sibling sessions still running at the same depth.
|
|
305
|
+
if (nestingDepth === 0) {
|
|
306
|
+
cleanupMapRun(mapRunId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Tree Store.
|
|
3
|
+
*
|
|
4
|
+
* In-memory store implementing bounded buffer with automatic compaction.
|
|
5
|
+
* Two-tier architecture: synchronous buffering with bounded eviction + async final compaction.
|
|
6
|
+
*
|
|
7
|
+
* Hot path (store): synchronous, no LLM calls. If buffer exceeds τ_hard,
|
|
8
|
+
* entries are evicted, summaries consolidated, and single summaries truncated
|
|
9
|
+
* via deterministic truncation in a loop until totalTokens ≤ τ_hard or no
|
|
10
|
+
* further reduction is possible (single entry with no summary).
|
|
11
|
+
*
|
|
12
|
+
* Cold path (compact): called ONCE after worker pool completes.
|
|
13
|
+
* Runs full 3-level escalation to produce high-quality summary.
|
|
14
|
+
*
|
|
15
|
+
* Memory bound: after store() completes, totalTokens ≤ τ_hard + maxSingleLabeledEntrySize
|
|
16
|
+
* where labeled entry size = countTokens("[Item N]: content").
|
|
17
|
+
* The single-entry overshoot covers the item that triggered eviction.
|
|
18
|
+
*/
|
|
19
|
+
import type { IContentGenerator } from '../../core/interfaces/i-content-generator.js';
|
|
20
|
+
import type { ITokenizer } from '../../core/interfaces/i-tokenizer.js';
|
|
21
|
+
/**
|
|
22
|
+
* Options for ContextTreeStore.
|
|
23
|
+
*/
|
|
24
|
+
export interface ContextTreeStoreOptions {
|
|
25
|
+
/** IContentGenerator for LLM summarization in compact() */
|
|
26
|
+
generator: IContentGenerator;
|
|
27
|
+
/** Maximum compaction rounds to prevent infinite loops (default: 10) */
|
|
28
|
+
maxCompactionRounds?: number;
|
|
29
|
+
/** Maximum tokens for the final summaryHandle (default: 2000) */
|
|
30
|
+
summaryBudget?: number;
|
|
31
|
+
/** Hard limit triggering synchronous eviction */
|
|
32
|
+
tauHard: number;
|
|
33
|
+
/** Tokenizer for token counting */
|
|
34
|
+
tokenizer: ITokenizer;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Context Tree Store with bounded buffer and 3-level compaction.
|
|
38
|
+
*/
|
|
39
|
+
export declare class ContextTreeStore {
|
|
40
|
+
private readonly entries;
|
|
41
|
+
private readonly generator;
|
|
42
|
+
private readonly maxCompactionRounds;
|
|
43
|
+
private readonly summaries;
|
|
44
|
+
private readonly summaryBudget;
|
|
45
|
+
private summaryHandle;
|
|
46
|
+
private summaryTokens;
|
|
47
|
+
private readonly tauHard;
|
|
48
|
+
private readonly tokenizer;
|
|
49
|
+
private totalTokens;
|
|
50
|
+
constructor(options: ContextTreeStoreOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Canonical entry format — single source of truth for labeled entries.
|
|
53
|
+
* Used in store(), evictOldest(), and compact().
|
|
54
|
+
*/
|
|
55
|
+
private static formatEntry;
|
|
56
|
+
/**
|
|
57
|
+
* Cold path — called ONCE after worker pool completes.
|
|
58
|
+
* Runs full 3-level escalation (may involve LLM calls) to produce
|
|
59
|
+
* high-quality summary from remaining entries + prior summaries.
|
|
60
|
+
*/
|
|
61
|
+
compact(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Returns compact summary text, bounded to summaryBudget tokens.
|
|
64
|
+
* Must call compact() first for LLM-quality output.
|
|
65
|
+
*/
|
|
66
|
+
getSummaryHandle(): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Hot path — called from processItem(). Synchronous, no LLM calls.
|
|
69
|
+
* If buffer exceeds τ_hard, evicts entries and consolidates summaries
|
|
70
|
+
* until totalTokens ≤ τ_hard or no further reduction is possible.
|
|
71
|
+
*/
|
|
72
|
+
store(index: number, content: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Consolidate all summaries into a single truncated summary.
|
|
75
|
+
* Called when entries alone can't bring totalTokens below τ_hard.
|
|
76
|
+
* Synchronous — uses deterministic truncation only.
|
|
77
|
+
*/
|
|
78
|
+
private consolidateSummaries;
|
|
79
|
+
/**
|
|
80
|
+
* Synchronous eviction of oldest entries via deterministic truncation.
|
|
81
|
+
* Truncates oldest half of entries into a compact summary chunk.
|
|
82
|
+
*/
|
|
83
|
+
private evictOldest;
|
|
84
|
+
/**
|
|
85
|
+
* Truncate a single remaining summary to fit within budget.
|
|
86
|
+
* Called when entries.size <= 1 and summaries.length === 1 but still over τ_hard.
|
|
87
|
+
* Halves the summary via deterministic truncation each round.
|
|
88
|
+
*/
|
|
89
|
+
private truncateSingleSummary;
|
|
90
|
+
/**
|
|
91
|
+
* Attempt LLM summarization for compact().
|
|
92
|
+
*/
|
|
93
|
+
private tryLlmSummarization;
|
|
94
|
+
}
|