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.
Files changed (181) hide show
  1. package/README.md +6 -81
  2. package/dist/agent/core/domain/llm/index.d.ts +1 -1
  3. package/dist/agent/core/domain/llm/index.js +1 -1
  4. package/dist/agent/core/domain/llm/registry.d.ts +8 -0
  5. package/dist/agent/core/domain/llm/registry.js +34 -0
  6. package/dist/agent/core/domain/sandbox/types.d.ts +2 -0
  7. package/dist/agent/core/domain/tools/constants.d.ts +3 -0
  8. package/dist/agent/core/domain/tools/constants.js +3 -0
  9. package/dist/agent/core/interfaces/cipher-services.d.ts +2 -4
  10. package/dist/agent/core/interfaces/i-cipher-agent.d.ts +9 -1
  11. package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
  12. package/dist/agent/core/interfaces/i-tool-provider.d.ts +10 -0
  13. package/dist/agent/core/interfaces/i-tool-scheduler.d.ts +9 -0
  14. package/dist/agent/infra/agent/agent-schemas.d.ts +0 -9
  15. package/dist/agent/infra/agent/agent-schemas.js +0 -3
  16. package/dist/agent/infra/agent/cipher-agent.d.ts +25 -1
  17. package/dist/agent/infra/agent/cipher-agent.js +138 -11
  18. package/dist/agent/infra/agent/provider-update-config.d.ts +0 -2
  19. package/dist/agent/infra/agent/service-initializer.d.ts +2 -6
  20. package/dist/agent/infra/agent/service-initializer.js +45 -38
  21. package/dist/agent/infra/blob/blob-storage-factory.d.ts +2 -2
  22. package/dist/agent/infra/blob/blob-storage-factory.js +4 -4
  23. package/dist/agent/infra/blob/file-blob-storage.d.ts +96 -0
  24. package/dist/agent/infra/blob/file-blob-storage.js +454 -0
  25. package/dist/agent/infra/blob/index.d.ts +2 -3
  26. package/dist/agent/infra/blob/index.js +4 -6
  27. package/dist/agent/infra/llm/agent-llm-service.d.ts +3 -0
  28. package/dist/agent/infra/llm/agent-llm-service.js +34 -52
  29. package/dist/agent/infra/llm/context/compression/compression-helpers.d.ts +35 -0
  30. package/dist/agent/infra/llm/context/compression/compression-helpers.js +124 -0
  31. package/dist/agent/infra/llm/context/compression/escalated-compression.d.ts +62 -0
  32. package/dist/agent/infra/llm/context/compression/escalated-compression.js +144 -0
  33. package/dist/agent/infra/llm/context/compression/index.d.ts +3 -0
  34. package/dist/agent/infra/llm/context/compression/index.js +3 -0
  35. package/dist/agent/infra/llm/context/compression/reactive-overflow.d.ts +0 -27
  36. package/dist/agent/infra/llm/context/compression/reactive-overflow.js +5 -122
  37. package/dist/agent/infra/llm/context/context-manager.d.ts +20 -1
  38. package/dist/agent/infra/llm/context/context-manager.js +37 -7
  39. package/dist/agent/infra/llm/providers/index.js +0 -2
  40. package/dist/agent/infra/llm/providers/types.d.ts +1 -5
  41. package/dist/agent/infra/map/agentic-map-service.d.ts +97 -0
  42. package/dist/agent/infra/map/agentic-map-service.js +309 -0
  43. package/dist/agent/infra/map/context-tree-store.d.ts +94 -0
  44. package/dist/agent/infra/map/context-tree-store.js +278 -0
  45. package/dist/agent/infra/map/index.d.ts +4 -0
  46. package/dist/agent/infra/map/index.js +4 -0
  47. package/dist/agent/infra/map/llm-map-memory.d.ts +59 -0
  48. package/dist/agent/infra/map/llm-map-memory.js +187 -0
  49. package/dist/agent/infra/map/llm-map-service.d.ts +36 -0
  50. package/dist/agent/infra/map/llm-map-service.js +118 -0
  51. package/dist/agent/infra/map/map-shared.d.ts +140 -0
  52. package/dist/agent/infra/map/map-shared.js +325 -0
  53. package/dist/agent/infra/map/worker-pool.d.ts +45 -0
  54. package/dist/agent/infra/map/worker-pool.js +73 -0
  55. package/dist/agent/infra/sandbox/curation-helpers.d.ts +62 -0
  56. package/dist/agent/infra/sandbox/curation-helpers.js +219 -0
  57. package/dist/agent/infra/sandbox/sandbox-service.d.ts +12 -0
  58. package/dist/agent/infra/sandbox/sandbox-service.js +39 -7
  59. package/dist/agent/infra/sandbox/tools-sdk.d.ts +48 -1
  60. package/dist/agent/infra/sandbox/tools-sdk.js +52 -1
  61. package/dist/agent/infra/session/session-manager.d.ts +8 -1
  62. package/dist/agent/infra/session/session-manager.js +24 -4
  63. package/dist/agent/infra/storage/file-key-storage.d.ts +142 -0
  64. package/dist/agent/infra/storage/file-key-storage.js +572 -0
  65. package/dist/agent/infra/storage/granular-history-storage.d.ts +1 -1
  66. package/dist/agent/infra/storage/granular-history-storage.js +1 -1
  67. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.d.ts +4 -0
  68. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.js +42 -14
  69. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.d.ts +16 -0
  70. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.js +47 -0
  71. package/dist/agent/infra/tools/core-tool-scheduler.js +3 -1
  72. package/dist/agent/infra/tools/implementations/agentic-map-tool.d.ts +35 -0
  73. package/dist/agent/infra/tools/implementations/agentic-map-tool.js +156 -0
  74. package/dist/agent/infra/tools/implementations/code-exec-tool.js +1 -0
  75. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +9 -9
  76. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +18 -0
  77. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +43 -0
  78. package/dist/agent/infra/tools/implementations/llm-map-tool.d.ts +24 -0
  79. package/dist/agent/infra/tools/implementations/llm-map-tool.js +87 -0
  80. package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +28 -1
  81. package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +27 -3
  82. package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -0
  83. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +83 -12
  84. package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
  85. package/dist/agent/infra/tools/tool-manager.js +6 -0
  86. package/dist/agent/infra/tools/tool-provider.d.ts +12 -0
  87. package/dist/agent/infra/tools/tool-provider.js +78 -0
  88. package/dist/agent/infra/tools/tool-registry.d.ts +14 -0
  89. package/dist/agent/infra/tools/tool-registry.js +32 -0
  90. package/dist/agent/resources/prompts/system-prompt.yml +48 -74
  91. package/dist/agent/resources/tools/expand_knowledge.txt +20 -0
  92. package/dist/oclif/commands/curate/index.js +1 -2
  93. package/dist/oclif/commands/main.js +1 -0
  94. package/dist/oclif/commands/providers/connect.d.ts +1 -3
  95. package/dist/oclif/commands/providers/connect.js +7 -29
  96. package/dist/oclif/commands/query.js +1 -2
  97. package/dist/server/constants.d.ts +7 -0
  98. package/dist/server/constants.js +8 -0
  99. package/dist/server/core/domain/entities/provider-registry.js +1 -15
  100. package/dist/server/core/domain/knowledge/memory-scoring.js +1 -1
  101. package/dist/server/core/domain/knowledge/summary-types.d.ts +126 -0
  102. package/dist/server/core/domain/knowledge/summary-types.js +7 -0
  103. package/dist/server/core/domain/transport/schemas.d.ts +0 -4
  104. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.d.ts +30 -0
  105. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.js +1 -0
  106. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.d.ts +30 -0
  107. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.js +1 -0
  108. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.d.ts +29 -0
  109. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.js +1 -0
  110. package/dist/server/infra/cogit/context-tree-to-push-context-mapper.js +10 -3
  111. package/dist/server/infra/connectors/skill/skill-connector.d.ts +4 -0
  112. package/dist/server/infra/connectors/skill/skill-connector.js +4 -0
  113. package/dist/server/infra/context-tree/children-hash.d.ts +20 -0
  114. package/dist/server/infra/context-tree/children-hash.js +22 -0
  115. package/dist/server/infra/context-tree/derived-artifact.d.ts +28 -0
  116. package/dist/server/infra/context-tree/derived-artifact.js +48 -0
  117. package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +37 -0
  118. package/dist/server/infra/context-tree/file-context-tree-archive-service.js +219 -0
  119. package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +50 -0
  120. package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +278 -0
  121. package/dist/server/infra/context-tree/file-context-tree-merger.js +4 -0
  122. package/dist/server/infra/context-tree/file-context-tree-snapshot-service.js +12 -4
  123. package/dist/server/infra/context-tree/file-context-tree-summary-service.d.ts +44 -0
  124. package/dist/server/infra/context-tree/file-context-tree-summary-service.js +313 -0
  125. package/dist/server/infra/context-tree/file-context-tree-writer-service.js +5 -0
  126. package/dist/server/infra/context-tree/prompts/summary-generation.d.ts +22 -0
  127. package/dist/server/infra/context-tree/prompts/summary-generation.js +45 -0
  128. package/dist/server/infra/context-tree/snapshot-diff.d.ts +19 -0
  129. package/dist/server/infra/context-tree/snapshot-diff.js +39 -0
  130. package/dist/server/infra/context-tree/summary-frontmatter.d.ts +24 -0
  131. package/dist/server/infra/context-tree/summary-frontmatter.js +111 -0
  132. package/dist/server/infra/daemon/agent-process.js +2 -14
  133. package/dist/server/infra/executor/curate-executor.d.ts +1 -0
  134. package/dist/server/infra/executor/curate-executor.js +82 -34
  135. package/dist/server/infra/executor/folder-pack-executor.js +1 -1
  136. package/dist/server/infra/executor/pre-compaction/compaction-escalation.d.ts +6 -0
  137. package/dist/server/infra/executor/pre-compaction/compaction-escalation.js +6 -0
  138. package/dist/server/infra/executor/pre-compaction/index.d.ts +3 -0
  139. package/dist/server/infra/executor/pre-compaction/index.js +1 -0
  140. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.d.ts +59 -0
  141. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.js +124 -0
  142. package/dist/server/infra/executor/pre-compaction/prompts.d.ts +24 -0
  143. package/dist/server/infra/executor/pre-compaction/prompts.js +47 -0
  144. package/dist/server/infra/executor/query-executor.d.ts +3 -0
  145. package/dist/server/infra/executor/query-executor.js +39 -4
  146. package/dist/server/infra/http/authenticated-http-client.js +4 -0
  147. package/dist/server/infra/http/provider-model-fetcher-registry.js +1 -5
  148. package/dist/server/infra/http/provider-model-fetchers.d.ts +0 -14
  149. package/dist/server/infra/http/provider-model-fetchers.js +0 -132
  150. package/dist/server/infra/provider/provider-config-resolver.js +0 -55
  151. package/dist/server/utils/curate-result-parser.d.ts +4 -4
  152. package/dist/shared/constants/curation.d.ts +6 -0
  153. package/dist/shared/constants/curation.js +6 -0
  154. package/dist/shared/utils/escalation-utils.d.ts +59 -0
  155. package/dist/shared/utils/escalation-utils.js +141 -0
  156. package/dist/tui/components/command-input.js +1 -1
  157. package/dist/tui/components/inline-prompts/inline-confirm.js +6 -1
  158. package/dist/tui/features/commands/definitions/exit.d.ts +2 -0
  159. package/dist/tui/features/commands/definitions/exit.js +9 -0
  160. package/dist/tui/features/commands/definitions/index.js +3 -0
  161. package/dist/tui/features/exit/components/exit-flow.d.ts +10 -0
  162. package/dist/tui/features/exit/components/exit-flow.js +19 -0
  163. package/dist/tui/features/provider/components/provider-flow.js +1 -21
  164. package/oclif.manifest.json +100 -109
  165. package/package.json +11 -4
  166. package/dist/agent/infra/blob/migrations.d.ts +0 -63
  167. package/dist/agent/infra/blob/migrations.js +0 -148
  168. package/dist/agent/infra/blob/sqlite-blob-storage.d.ts +0 -82
  169. package/dist/agent/infra/blob/sqlite-blob-storage.js +0 -307
  170. package/dist/agent/infra/llm/providers/google-vertex.d.ts +0 -15
  171. package/dist/agent/infra/llm/providers/google-vertex.js +0 -36
  172. package/dist/agent/infra/storage/blob-history-storage.d.ts +0 -81
  173. package/dist/agent/infra/storage/blob-history-storage.js +0 -193
  174. package/dist/agent/infra/storage/dual-format-history-storage.d.ts +0 -83
  175. package/dist/agent/infra/storage/dual-format-history-storage.js +0 -165
  176. package/dist/agent/infra/storage/sqlite-key-storage.d.ts +0 -113
  177. package/dist/agent/infra/storage/sqlite-key-storage.js +0 -438
  178. package/dist/server/infra/provider/vertex-ai-utils.d.ts +0 -10
  179. package/dist/server/infra/provider/vertex-ai-utils.js +0 -28
  180. package/dist/tui/features/provider/components/credential-path-dialog.d.ts +0 -30
  181. 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 (maxInputTokens - systemPromptTokens).
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 <= this.maxInputTokens) {
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 = this.maxInputTokens - systemPromptTokens;
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 <= this.maxInputTokens) {
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 > this.maxInputTokens) {
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' | 'service-account';
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
+ }