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
@@ -14,9 +14,7 @@ import Anthropic from '@anthropic-ai/sdk';
14
14
  import { GoogleGenAI } from '@google/genai';
15
15
  import { APICallError, generateText } from 'ai';
16
16
  import axios, { isAxiosError } from 'axios';
17
- import { existsSync, readFileSync } from 'node:fs';
18
17
  import OpenAI from 'openai';
19
- import { resolveVertexAiProject } from '../provider/vertex-ai-utils.js';
20
18
  const DEFAULT_CACHE_TTL = 60 * 60 * 1000; // 1 hour
21
19
  const ANTHROPIC_KNOWN_MODELS = {
22
20
  'claude-3-5-haiku-20241022': { contextLength: 200_000, inputPerM: 0.8, outputPerM: 4 },
@@ -251,136 +249,6 @@ export class GoogleModelFetcher {
251
249
  }
252
250
  }
253
251
  // ============================================================================
254
- // Google Vertex AI Model Fetcher
255
- // ============================================================================
256
- /**
257
- * Known context window sizes for Google models.
258
- * The Vertex AI list endpoint does not populate inputTokenLimit,
259
- * so we use a static lookup with prefix matching.
260
- */
261
- const GOOGLE_CONTEXT_LENGTHS = [
262
- ['gemini-2.5', 1_048_576],
263
- ['gemini-2.0', 1_048_576],
264
- ['gemini-1.5', 1_048_576],
265
- ['gemini-1.0', 32_768],
266
- ['gemma', 8192],
267
- ];
268
- function getGoogleContextLength(modelId) {
269
- for (const [prefix, contextLength] of GOOGLE_CONTEXT_LENGTHS) {
270
- if (modelId.startsWith(prefix))
271
- return contextLength;
272
- }
273
- return 1_048_576;
274
- }
275
- /**
276
- * Fetches models from Google Vertex AI using the @google/genai SDK with vertexai mode.
277
- * Uses Application Default Credentials (ADC) instead of API keys.
278
- */
279
- export class GoogleVertexModelFetcher {
280
- cache;
281
- cacheTtlMs;
282
- constructor(cacheTtlMs = DEFAULT_CACHE_TTL) {
283
- this.cacheTtlMs = cacheTtlMs;
284
- }
285
- async fetchModels(apiKey, forceRefresh = false) {
286
- if (!forceRefresh && this.cache && Date.now() - this.cache.timestamp < this.cacheTtlMs) {
287
- return this.cache.models;
288
- }
289
- // apiKey holds the credential file path for Vertex AI.
290
- // Set env var for the GoogleGenAI SDK which reads it internally.
291
- if (apiKey) {
292
- process.env.GOOGLE_APPLICATION_CREDENTIALS = apiKey;
293
- }
294
- const project = resolveVertexAiProject(apiKey);
295
- const location = process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
296
- const client = new GoogleGenAI({ location, project, vertexai: true });
297
- const models = [];
298
- try {
299
- const pager = await client.models.list();
300
- for (const model of pager.page) {
301
- // Vertex AI uses "publishers/google/models/<id>" format
302
- const rawName = model.name ?? '';
303
- const id = rawName.replace(/^publishers\/google\/models\//, '').replace(/^models\//, '');
304
- // Only include text generation models.
305
- // Vertex AI doesn't populate supportedActions, so use prefix matching as primary filter.
306
- // When supportedActions IS available, use it as authoritative.
307
- if (model.supportedActions && !model.supportedActions.includes('generateContent'))
308
- continue;
309
- if (!id.startsWith('gemini') && !id.startsWith('gemma'))
310
- continue;
311
- if (id.includes('embedding') || id.includes('image'))
312
- continue;
313
- models.push({
314
- contextLength: model.inputTokenLimit ?? getGoogleContextLength(id),
315
- description: model.description ?? undefined,
316
- id,
317
- isFree: false,
318
- name: model.displayName ?? id,
319
- pricing: { inputPerM: 0, outputPerM: 0 },
320
- provider: 'Google Vertex AI',
321
- });
322
- }
323
- }
324
- catch (error) {
325
- throw new Error(formatVertexAiError(error));
326
- }
327
- this.cache = { models, timestamp: Date.now() };
328
- return models;
329
- }
330
- async validateApiKey(apiKey) {
331
- try {
332
- // apiKey holds the credential file path for Vertex AI
333
- if (apiKey) {
334
- if (!existsSync(apiKey)) {
335
- return { error: `File not found: ${apiKey}`, isValid: false };
336
- }
337
- try {
338
- JSON.parse(readFileSync(apiKey, 'utf8'));
339
- }
340
- catch {
341
- return { error: `Invalid JSON in file: ${apiKey}`, isValid: false };
342
- }
343
- // Set temporarily for the GoogleGenAI SDK to pick up during validation
344
- process.env.GOOGLE_APPLICATION_CREDENTIALS = apiKey;
345
- }
346
- const project = resolveVertexAiProject(apiKey);
347
- const location = process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
348
- const client = new GoogleGenAI({ location, project, vertexai: true });
349
- await client.models.list();
350
- return { isValid: true };
351
- }
352
- catch (error) {
353
- return { error: formatVertexAiError(error), isValid: false };
354
- }
355
- }
356
- }
357
- /**
358
- * Format Vertex AI errors with actionable ADC setup instructions.
359
- */
360
- function formatVertexAiError(error) {
361
- const message = error instanceof Error ? error.message : String(error);
362
- const lowerMessage = message.toLowerCase();
363
- // Invalid credential file (wrong key material, corrupt file, non-service-account JSON)
364
- if (lowerMessage.includes('decoder routines') || lowerMessage.includes('unsupported') || lowerMessage.includes('invalid_grant')) {
365
- return 'Invalid credential file. Ensure the file is a valid Google Cloud service account JSON key.';
366
- }
367
- if (lowerMessage.includes('could not load the default credentials') ||
368
- lowerMessage.includes('default credentials') ||
369
- lowerMessage.includes('application_default_credentials')) {
370
- return 'Google Cloud credentials not found. Run `gcloud auth application-default login` or set the GOOGLE_APPLICATION_CREDENTIALS environment variable.';
371
- }
372
- if (lowerMessage.includes('unable to detect a default project')) {
373
- return `Google Cloud project not set. Set GOOGLE_CLOUD_PROJECT environment variable or use a service account JSON that contains a project_id.`;
374
- }
375
- if (lowerMessage.includes('401') || lowerMessage.includes('unauthorized') || lowerMessage.includes('authentication')) {
376
- return `Google Cloud authentication failed. Run \`gcloud auth application-default login\` or check your GOOGLE_APPLICATION_CREDENTIALS.\n${message}`;
377
- }
378
- if (lowerMessage.includes('403') || lowerMessage.includes('forbidden') || lowerMessage.includes('permission')) {
379
- return `Google Cloud permission denied. Ensure your account has Vertex AI API access enabled.\n${message}`;
380
- }
381
- return `Google Vertex AI error: ${message}`;
382
- }
383
- // ============================================================================
384
252
  // OpenAI-Compatible Model Fetcher (xAI, Groq, Mistral)
385
253
  // ============================================================================
386
254
  /**
@@ -5,19 +5,9 @@
5
5
  * for the currently active provider. Used by the daemon state server to serve
6
6
  * agent child processes on startup and after provider hot-swap.
7
7
  */
8
- import { existsSync } from 'node:fs';
9
- import { homedir } from 'node:os';
10
- import { join } from 'node:path';
11
8
  import { getProviderById, providerRequiresApiKey } from '../../core/domain/entities/provider-registry.js';
12
9
  import { getProviderApiKeyFromEnv } from './env-provider-detector.js';
13
- import { resolveVertexAiProject } from './vertex-ai-utils.js';
14
10
  async function isProviderCredentialAccessible(providerId, providerKeychainStore) {
15
- if (providerId === 'google-vertex') {
16
- const storedPath = await providerKeychainStore.getApiKey(providerId);
17
- if (storedPath)
18
- return existsSync(storedPath);
19
- return !getVertexAiCredentialError(process.env.GOOGLE_APPLICATION_CREDENTIALS);
20
- }
21
11
  if (!providerRequiresApiKey(providerId))
22
12
  return true;
23
13
  return Boolean((await providerKeychainStore.getApiKey(providerId)) || getProviderApiKeyFromEnv(providerId));
@@ -77,9 +67,6 @@ export async function resolveProviderConfig(providerConfigStore, providerKeychai
77
67
  if (!activeProvider || activeProvider === 'byterover') {
78
68
  return { activeModel, activeProvider, maxInputTokens };
79
69
  }
80
- if (activeProvider === 'google-vertex') {
81
- return resolveVertexAiConfig(activeModel, activeProvider, maxInputTokens, providerKeychainStore);
82
- }
83
70
  // Resolve API key: keychain first, then environment variable
84
71
  let apiKey = await providerKeychainStore.getApiKey(activeProvider);
85
72
  if (!apiKey) {
@@ -123,45 +110,3 @@ export async function resolveProviderConfig(providerConfigStore, providerKeychai
123
110
  }
124
111
  }
125
112
  }
126
- /**
127
- * Resolve Google Vertex AI provider configuration.
128
- * Uses service account JSON from keychain or Application Default Credentials.
129
- */
130
- async function resolveVertexAiConfig(activeModel, activeProvider, maxInputTokens, providerKeychainStore) {
131
- const storedCredentialPath = await providerKeychainStore.getApiKey(activeProvider);
132
- const effectiveCredentialPath = storedCredentialPath || process.env.GOOGLE_APPLICATION_CREDENTIALS || undefined;
133
- const credentialError = getVertexAiCredentialError(effectiveCredentialPath);
134
- return {
135
- activeModel,
136
- activeProvider,
137
- maxInputTokens,
138
- provider: activeProvider,
139
- providerCredentialError: credentialError ?? undefined,
140
- providerCredentialPath: effectiveCredentialPath,
141
- providerKeyMissing: Boolean(credentialError),
142
- providerLocation: process.env.GOOGLE_CLOUD_LOCATION || 'us-central1',
143
- providerProject: resolveVertexAiProject(storedCredentialPath),
144
- };
145
- }
146
- /**
147
- * Lightweight check for Google Vertex AI credential availability.
148
- * Checks the given credential path and the well-known ADC file
149
- * without making network calls.
150
- *
151
- * @param credentialPath - Explicit credential file path (from keychain or env var).
152
- * @returns Error message if credentials are not found, null if accessible.
153
- */
154
- function getVertexAiCredentialError(credentialPath) {
155
- if (credentialPath) {
156
- if (!existsSync(credentialPath)) {
157
- return `Credential file not found: ${credentialPath}. Verify the path or run \`gcloud auth application-default login\`.`;
158
- }
159
- return null;
160
- }
161
- // Check well-known ADC location (created by `gcloud auth application-default login`)
162
- const adcPath = join(homedir(), '.config', 'gcloud', 'application_default_credentials.json');
163
- if (existsSync(adcPath)) {
164
- return null;
165
- }
166
- return 'Google Cloud credentials not found. Run `gcloud auth application-default login` or set the GOOGLE_APPLICATION_CREDENTIALS environment variable.';
167
- }
@@ -48,22 +48,22 @@ export declare const CurateResultSchema: z.ZodObject<{
48
48
  updated: z.ZodOptional<z.ZodNumber>;
49
49
  }, "strip", z.ZodTypeAny, {
50
50
  failed?: number | undefined;
51
- updated?: number | undefined;
52
51
  deleted?: number | undefined;
52
+ updated?: number | undefined;
53
53
  added?: number | undefined;
54
54
  merged?: number | undefined;
55
55
  }, {
56
56
  failed?: number | undefined;
57
- updated?: number | undefined;
58
57
  deleted?: number | undefined;
58
+ updated?: number | undefined;
59
59
  added?: number | undefined;
60
60
  merged?: number | undefined;
61
61
  }>>;
62
62
  }, "strip", z.ZodTypeAny, {
63
63
  summary?: {
64
64
  failed?: number | undefined;
65
- updated?: number | undefined;
66
65
  deleted?: number | undefined;
66
+ updated?: number | undefined;
67
67
  added?: number | undefined;
68
68
  merged?: number | undefined;
69
69
  } | undefined;
@@ -77,8 +77,8 @@ export declare const CurateResultSchema: z.ZodObject<{
77
77
  }, {
78
78
  summary?: {
79
79
  failed?: number | undefined;
80
- updated?: number | undefined;
81
80
  deleted?: number | undefined;
81
+ updated?: number | undefined;
82
82
  added?: number | undefined;
83
83
  merged?: number | undefined;
84
84
  } | undefined;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared curation threshold — single source of truth.
3
+ * Used by both pre-compaction (server layer) and curation helpers (agent layer).
4
+ * Contexts below this threshold skip compaction and use single-pass curation.
5
+ */
6
+ export declare const CURATION_CHAR_THRESHOLD = 20000;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared curation threshold — single source of truth.
3
+ * Used by both pre-compaction (server layer) and curation helpers (agent layer).
4
+ * Contexts below this threshold skip compaction and use single-pass curation.
5
+ */
6
+ export const CURATION_CHAR_THRESHOLD = 20_000;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Compaction escalation utilities for context compression.
3
+ *
4
+ * Ported from VoltCode's compaction-escalation.ts with additions:
5
+ * - isCompactionOutputValid() quality gate for rejecting LLM refusals
6
+ * - Optional tokenizer param for buildDeterministicFallbackCompaction()
7
+ *
8
+ * Pure utility functions — no external dependencies.
9
+ * Lives in src/shared/ so both server/ and agent/ can import without cross-layer coupling.
10
+ */
11
+ /** Structural type — no import dependency on agent layer */
12
+ type TokenCounter = {
13
+ countTokens(text: string): number;
14
+ };
15
+ /**
16
+ * Escalation tiers for compaction-generation passes.
17
+ */
18
+ export type CompactionEscalationTier = 'aggressive' | 'fallback' | 'normal';
19
+ /**
20
+ * Heuristic token estimation: round(length / 4).
21
+ * Same as VoltCode's Token.estimate().
22
+ */
23
+ export declare function estimateTokens(text: string): number;
24
+ /**
25
+ * Check whether a compaction output is acceptable for convergence.
26
+ * Requires non-empty content and strict token reduction versus the source.
27
+ */
28
+ export declare function shouldAcceptCompactionOutput(output: string, inputTokens: number): boolean;
29
+ /**
30
+ * Quality gate: reject LLM refusals and trivially short output.
31
+ *
32
+ * - Rejects output < 50 chars (likely a disclaimer)
33
+ * - Rejects common LLM refusal/disclaimer patterns
34
+ * - Accepts >= 200 chars unconditionally (valid prose without markdown is fine)
35
+ * - For 50-199 chars, requires at least one structural signal
36
+ */
37
+ export declare function isCompactionOutputValid(output: string): boolean;
38
+ /**
39
+ * Append aggressive compression directive to a prompt.
40
+ * Idempotent — does not double-append if already present.
41
+ */
42
+ export declare function withAggressiveCompactionDirective(promptTemplate: string): string;
43
+ /**
44
+ * Build deterministic fallback output (pass 3) by truncating source text until
45
+ * it is strictly smaller than the source token count.
46
+ *
47
+ * Uses binary search to find the largest prefix that satisfies the strict
48
+ * token-reduction goal.
49
+ *
50
+ * When `tokenizer` is provided, uses `tokenizer.countTokens()` for precise
51
+ * binary search convergence. When absent, uses `estimateTokens()` (backward compat).
52
+ */
53
+ export declare function buildDeterministicFallbackCompaction(input: {
54
+ inputTokens: number;
55
+ sourceText: string;
56
+ suffixLabel: string;
57
+ tokenizer?: TokenCounter;
58
+ }): string;
59
+ export {};
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Compaction escalation utilities for context compression.
3
+ *
4
+ * Ported from VoltCode's compaction-escalation.ts with additions:
5
+ * - isCompactionOutputValid() quality gate for rejecting LLM refusals
6
+ * - Optional tokenizer param for buildDeterministicFallbackCompaction()
7
+ *
8
+ * Pure utility functions — no external dependencies.
9
+ * Lives in src/shared/ so both server/ and agent/ can import without cross-layer coupling.
10
+ */
11
+ const CHARS_PER_TOKEN = 4;
12
+ /**
13
+ * Marker header appended to prompts in aggressive mode.
14
+ */
15
+ const AGGRESSIVE_DIRECTIVE_HEADER = '## Aggressive Compression Override';
16
+ /**
17
+ * Heuristic token estimation: round(length / 4).
18
+ * Same as VoltCode's Token.estimate().
19
+ */
20
+ export function estimateTokens(text) {
21
+ return Math.max(0, Math.round((text || '').length / CHARS_PER_TOKEN));
22
+ }
23
+ /**
24
+ * Check whether a compaction output is acceptable for convergence.
25
+ * Requires non-empty content and strict token reduction versus the source.
26
+ */
27
+ export function shouldAcceptCompactionOutput(output, inputTokens) {
28
+ const trimmed = output.trim();
29
+ if (!trimmed)
30
+ return false;
31
+ if (!Number.isFinite(inputTokens) || inputTokens <= 1)
32
+ return false;
33
+ return estimateTokens(trimmed) < inputTokens;
34
+ }
35
+ /**
36
+ * Quality gate: reject LLM refusals and trivially short output.
37
+ *
38
+ * - Rejects output < 50 chars (likely a disclaimer)
39
+ * - Rejects common LLM refusal/disclaimer patterns
40
+ * - Accepts >= 200 chars unconditionally (valid prose without markdown is fine)
41
+ * - For 50-199 chars, requires at least one structural signal
42
+ */
43
+ export function isCompactionOutputValid(output) {
44
+ const trimmed = output.trim();
45
+ if (trimmed.length < 50)
46
+ return false;
47
+ const REFUSAL_PATTERNS = [
48
+ /^I (?:cannot|can't|am unable|don't have)/i,
49
+ /^(?:not found|no (?:information|context|data))/i,
50
+ /^(?:sorry|unfortunately|I apologize)/i,
51
+ /^(?:as an AI|I'm an AI)/i,
52
+ /^(?:based on (?:the|my) (?:knowledge|training))/i,
53
+ ];
54
+ if (REFUSAL_PATTERNS.some((p) => p.test(trimmed)))
55
+ return false;
56
+ if (trimmed.length >= 200)
57
+ return true;
58
+ return (trimmed.includes('```') ||
59
+ trimmed.includes('| ') ||
60
+ /^[-*]\s/m.test(trimmed) ||
61
+ /^#{1,4}\s/m.test(trimmed) ||
62
+ trimmed.split('\n').length >= 3);
63
+ }
64
+ /**
65
+ * Append aggressive compression directive to a prompt.
66
+ * Idempotent — does not double-append if already present.
67
+ */
68
+ export function withAggressiveCompactionDirective(promptTemplate) {
69
+ if (promptTemplate.includes(AGGRESSIVE_DIRECTIVE_HEADER)) {
70
+ return promptTemplate;
71
+ }
72
+ return `${promptTemplate.trim()}\n\n${AGGRESSIVE_DIRECTIVE_HEADER}
73
+ - You are in escalation pass 2 because pass 1 was not shorter than input.
74
+ - Compress more aggressively than normal while preserving task-critical facts.
75
+ - Remove repetition, low-value narrative, and secondary detail.
76
+ - Output must still be coherent and safe for continuation.`;
77
+ }
78
+ /**
79
+ * Build deterministic fallback output (pass 3) by truncating source text until
80
+ * it is strictly smaller than the source token count.
81
+ *
82
+ * Uses binary search to find the largest prefix that satisfies the strict
83
+ * token-reduction goal.
84
+ *
85
+ * When `tokenizer` is provided, uses `tokenizer.countTokens()` for precise
86
+ * binary search convergence. When absent, uses `estimateTokens()` (backward compat).
87
+ */
88
+ export function buildDeterministicFallbackCompaction(input) {
89
+ const source = input.sourceText.trim();
90
+ if (!source)
91
+ return '';
92
+ const targetTokens = Number.isFinite(input.inputTokens) ? Math.floor(input.inputTokens) : Infinity;
93
+ if (!Number.isFinite(targetTokens)) {
94
+ return source;
95
+ }
96
+ // Strict reduction is impossible for non-empty strings when target <= 0.
97
+ // Return empty string to preserve convergence under any tokenizer.
98
+ if (targetTokens <= 0) {
99
+ return '';
100
+ }
101
+ // For target=1, many tokenizers cannot represent any non-empty string with 0 tokens.
102
+ // Returning empty string is the only tokenizer-agnostic strict reduction.
103
+ if (targetTokens === 1) {
104
+ return '';
105
+ }
106
+ const countTokens = input.tokenizer
107
+ ? (text) => input.tokenizer.countTokens(text)
108
+ : estimateTokens;
109
+ const suffix = `\n[${input.suffixLabel}; truncated from ${targetTokens} tokens]`;
110
+ const sourceLen = source.length;
111
+ const fitPrefix = (extra) => {
112
+ let lo = 1;
113
+ let hi = sourceLen;
114
+ let best = '';
115
+ while (lo <= hi) {
116
+ const mid = Math.floor((lo + hi) / 2);
117
+ const prefix = source.slice(0, mid).trimEnd();
118
+ const candidate = `${prefix}${extra}`.trim();
119
+ if (!candidate) {
120
+ hi = mid - 1;
121
+ continue;
122
+ }
123
+ const candidateTokens = countTokens(candidate);
124
+ if (candidateTokens < targetTokens) {
125
+ best = candidate;
126
+ lo = mid + 1;
127
+ }
128
+ else {
129
+ hi = mid - 1;
130
+ }
131
+ }
132
+ return best;
133
+ };
134
+ const withSuffix = fitPrefix(suffix);
135
+ if (withSuffix)
136
+ return withSuffix;
137
+ const plainPrefix = fitPrefix('');
138
+ if (plainPrefix)
139
+ return plainPrefix;
140
+ return source.slice(0, 1);
141
+ }
@@ -72,7 +72,7 @@ export const CommandInput = () => {
72
72
  setIsStreaming(false);
73
73
  setHasActiveDialog(false);
74
74
  }
75
- }, { isActive: isStreaming });
75
+ }, { isActive: isStreaming && !activeDialog });
76
76
  const executeCommand = useCallback(async (value) => {
77
77
  const trimmed = value.trim();
78
78
  if (!trimmed)
@@ -6,13 +6,18 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
6
6
  * Shows (Y/n) or (N/y) based on default value.
7
7
  * User types y/n and presses Enter to submit.
8
8
  */
9
- import { Box, Text } from 'ink';
9
+ import { Box, Text, useInput } from 'ink';
10
10
  import TextInput from 'ink-text-input';
11
11
  import { useState } from 'react';
12
12
  import { useTheme } from '../../hooks/index.js';
13
13
  export function InlineConfirm({ default: defaultValue = true, message, onConfirm, }) {
14
14
  const { theme: { colors }, } = useTheme();
15
15
  const [input, setInput] = useState('');
16
+ useInput((_input, key) => {
17
+ if (key.escape) {
18
+ onConfirm(false);
19
+ }
20
+ });
16
21
  // Format hint based on default: (Y/n) or (N/y)
17
22
  const hint = defaultValue ? '(Y/n)' : '(N/y)';
18
23
  const handleSubmit = (value) => {
@@ -0,0 +1,2 @@
1
+ import type { SlashCommand } from '../../../types/commands.js';
2
+ export declare const exitCommand: SlashCommand;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { ExitFlow } from '../../exit/components/exit-flow.js';
3
+ export const exitCommand = {
4
+ action: () => ({
5
+ render: ({ onComplete }) => React.createElement(ExitFlow, { onComplete }),
6
+ }),
7
+ description: 'Exit the ByteRover REPL',
8
+ name: 'exit',
9
+ };
@@ -1,5 +1,6 @@
1
1
  import { connectorsCommand } from './connectors.js';
2
2
  import { curateCommand } from './curate.js';
3
+ import { exitCommand } from './exit.js';
3
4
  import { hubCommand } from './hub.js';
4
5
  import { loginCommand } from './login.js';
5
6
  import { logoutCommand } from './logout.js';
@@ -42,4 +43,6 @@ export const load = () => [
42
43
  // Auth
43
44
  loginCommand,
44
45
  logoutCommand,
46
+ // Exit
47
+ exitCommand,
45
48
  ];
@@ -0,0 +1,10 @@
1
+ /**
2
+ * ExitFlow Component
3
+ *
4
+ * Confirms, then gracefully exits the Ink application.
5
+ */
6
+ import React from 'react';
7
+ import type { CustomDialogCallbacks } from '../../../types/commands.js';
8
+ type ExitFlowProps = Pick<CustomDialogCallbacks, 'onComplete'>;
9
+ export declare function ExitFlow({ onComplete }: ExitFlowProps): React.ReactNode;
10
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ExitFlow Component
4
+ *
5
+ * Confirms, then gracefully exits the Ink application.
6
+ */
7
+ import { useApp } from 'ink';
8
+ import { InlineConfirm } from '../../../components/inline-prompts/inline-confirm.js';
9
+ export function ExitFlow({ onComplete }) {
10
+ const { exit } = useApp();
11
+ return (_jsx(InlineConfirm, { default: true, message: "Exit ByteRover REPL", onConfirm: (confirmed) => {
12
+ if (confirmed) {
13
+ exit();
14
+ }
15
+ else {
16
+ onComplete('Exit cancelled.');
17
+ }
18
+ } }));
19
+ }
@@ -21,7 +21,6 @@ import { useSetActiveProvider } from '../api/set-active-provider.js';
21
21
  import { useValidateApiKey } from '../api/validate-api-key.js';
22
22
  import { ApiKeyDialog } from './api-key-dialog.js';
23
23
  import { BaseUrlDialog } from './base-url-dialog.js';
24
- import { CredentialPathDialog } from './credential-path-dialog.js';
25
24
  import { ModelSelectStep } from './model-select-step.js';
26
25
  import { ProviderDialog } from './provider-dialog.js';
27
26
  export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCancel, onComplete, providerDialogTitle, }) => {
@@ -65,17 +64,6 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
65
64
  name: 'Disconnect',
66
65
  });
67
66
  }
68
- if (selectedProvider.id === 'google-vertex') {
69
- actions.push({
70
- description: 'Enter a new service account key file path',
71
- id: 'replace',
72
- name: 'Replace credential',
73
- }, {
74
- description: 'Remove credential and disconnect',
75
- id: 'disconnect',
76
- name: 'Disconnect',
77
- });
78
- }
79
67
  if (selectedProvider.id === 'openai-compatible') {
80
68
  actions.push({
81
69
  description: 'Change base URL and API key',
@@ -130,11 +118,6 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
130
118
  setStep('api_key');
131
119
  return;
132
120
  }
133
- // Google Vertex AI → credential file path step
134
- if (provider.id === 'google-vertex') {
135
- setStep('credential_path');
136
- return;
137
- }
138
121
  // No API key needed → connect directly → model select
139
122
  setStep('connecting');
140
123
  try {
@@ -184,7 +167,7 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
184
167
  break;
185
168
  }
186
169
  case 'replace': {
187
- setStep(selectedProvider.id === 'google-vertex' ? 'credential_path' : 'api_key');
170
+ setStep('api_key');
188
171
  break;
189
172
  }
190
173
  default: {
@@ -255,9 +238,6 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
255
238
  case 'connecting': {
256
239
  return (_jsx(Box, { children: _jsxs(Text, { color: colors.primary, children: ["Connecting to ", selectedProvider?.name, "..."] }) }));
257
240
  }
258
- case 'credential_path': {
259
- return selectedProvider ? (_jsx(CredentialPathDialog, { isActive: isActive, onCancel: handleApiKeyCancel, onSuccess: handleApiKeySuccess, provider: selectedProvider, validateCredential: handleValidateApiKey })) : null;
260
- }
261
241
  case 'model_select': {
262
242
  return selectedProvider ? (_jsx(ModelSelectStep, { isActive: isActive, onCancel: () => setStep('select'), onComplete: (modelName) => onComplete(`Connected to ${selectedProvider.name}, model set to ${modelName}`), providerId: selectedProvider.id, providerName: selectedProvider.name })) : null;
263
243
  }