byterover-cli 3.10.1 → 3.10.2

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 (28) hide show
  1. package/dist/agent/infra/agent/service-initializer.js +8 -2
  2. package/dist/agent/infra/llm/agent-llm-service.d.ts +9 -9
  3. package/dist/agent/infra/llm/agent-llm-service.js +28 -18
  4. package/dist/agent/infra/llm/generators/ai-sdk-content-generator.d.ts +10 -1
  5. package/dist/agent/infra/llm/generators/ai-sdk-content-generator.js +21 -4
  6. package/dist/agent/infra/llm/generators/ai-sdk-message-converter.d.ts +4 -0
  7. package/dist/agent/infra/llm/generators/ai-sdk-message-converter.js +8 -1
  8. package/dist/agent/infra/map/abstract-generator.d.ts +29 -0
  9. package/dist/agent/infra/map/abstract-generator.js +161 -0
  10. package/dist/agent/infra/map/abstract-queue.d.ts +7 -0
  11. package/dist/agent/infra/map/abstract-queue.js +100 -26
  12. package/dist/agent/infra/system-prompt/contributors/file-contributor.js +6 -2
  13. package/dist/agent/infra/tools/tool-manager.d.ts +10 -1
  14. package/dist/agent/infra/tools/tool-manager.js +10 -1
  15. package/dist/server/infra/dream/dream-state-schema.d.ts +35 -0
  16. package/dist/server/infra/dream/dream-state-schema.js +15 -0
  17. package/dist/server/infra/dream/dream-state-service.d.ts +22 -0
  18. package/dist/server/infra/dream/dream-state-service.js +62 -3
  19. package/dist/server/infra/dream/dream-trigger.js +6 -2
  20. package/dist/server/infra/executor/curate-executor.d.ts +16 -0
  21. package/dist/server/infra/executor/curate-executor.js +76 -5
  22. package/dist/server/infra/executor/dream-executor.d.ts +16 -0
  23. package/dist/server/infra/executor/dream-executor.js +44 -7
  24. package/dist/server/infra/transport/handlers/provider-handler.js +20 -3
  25. package/dist/tui/features/auth/api/get-auth-state.js +6 -3
  26. package/dist/tui/features/auth/components/auth-initializer.js +4 -2
  27. package/oclif.manifest.json +413 -413
  28. package/package.json +1 -1
@@ -48,6 +48,8 @@ export type DreamExecutorDeps = {
48
48
  save(entry: DreamLogEntry): Promise<void>;
49
49
  };
50
50
  dreamStateService: {
51
+ drainStaleSummaryPaths(): Promise<string[]>;
52
+ enqueueStaleSummaryPaths(paths: string[]): Promise<void>;
51
53
  read(): Promise<import('../dream/dream-state-schema.js').DreamState>;
52
54
  update(updater: (state: import('../dream/dream-state-schema.js').DreamState) => import('../dream/dream-state-schema.js').DreamState): Promise<import('../dream/dream-state-schema.js').DreamState>;
53
55
  write(state: import('../dream/dream-state-schema.js').DreamState): Promise<void>;
@@ -106,6 +108,20 @@ export declare class DreamExecutor {
106
108
  signal: AbortSignal;
107
109
  taskId: string;
108
110
  }): Promise<void>;
111
+ /**
112
+ * Regenerate parent `_index.md` files for the given paths and rebuild the
113
+ * manifest. Extracted as a seam so tests can override and assert which
114
+ * paths were passed (the A ∪ B merge in step 5 is the central correctness
115
+ * invariant of the deferral). Production constructs the services here so
116
+ * the dependency surface of {@link DreamExecutorDeps} stays narrow.
117
+ */
118
+ protected runStaleSummaryPropagation(args: {
119
+ agent: ICipherAgent;
120
+ parentTaskId?: string;
121
+ paths: string[];
122
+ projectRoot: string;
123
+ }): Promise<void>;
124
+ /** Errors are tracked at the log level (status='error'), not per-operation — always 0 here. */
109
125
  private computeSummary;
110
126
  /**
111
127
  * Dual-write: create curate log entries for dream operations that need human review.
@@ -74,7 +74,12 @@ export class DreamExecutor {
74
74
  preState = await snapshotService.getCurrentState(projectRoot);
75
75
  }
76
76
  catch {
77
- // Fail-open: if snapshot fails, skip propagation
77
+ // Fail-open: leaving preState undefined skips the entire step 5 block
78
+ // (queue drain + propagation), so the stale-summary queue is left
79
+ // intact for the next successful dream cycle. Skipping drain here is
80
+ // safer than drain-then-fail: the atomic-drain design clears entries
81
+ // synchronously inside the RMW, so if we drained and then threw
82
+ // before reaching the catch's re-enqueue, the snapshot would be lost.
78
83
  }
79
84
  // Step 2: Load dream state
80
85
  const dreamState = await this.deps.dreamStateService.read();
@@ -94,19 +99,37 @@ export class DreamExecutor {
94
99
  taskId: options.taskId,
95
100
  });
96
101
  // Step 5: Post-dream propagation (fail-open)
102
+ // Two sources of stale summary paths:
103
+ // A. The stale-summary queue, drained from dream state — paths from
104
+ // curate operations that ran since the last dream cycle (the LLM
105
+ // cascade work was deferred from curate's hot path to here).
106
+ // B. Dream's own snapshot diff — paths changed by this dream's
107
+ // consolidate/synthesize/prune operations.
108
+ // Merging A ∪ B before calling propagateStaleness lets a path touched
109
+ // by both sources regenerate exactly once. The queue is drained
110
+ // atomically (cleared in the same RMW that captures the snapshot) so
111
+ // any concurrent curate enqueueing during propagation appends a fresh
112
+ // entry to the now-empty queue and the next dream picks it up.
97
113
  if (preState) {
114
+ let drainedSnapshot = [];
98
115
  try {
116
+ drainedSnapshot = await this.deps.dreamStateService.drainStaleSummaryPaths();
99
117
  const postState = await snapshotService.getCurrentState(projectRoot);
100
118
  const changedPaths = diffStates(preState, postState);
101
- if (changedPaths.length > 0) {
102
- const summaryService = new FileContextTreeSummaryService();
103
- await summaryService.propagateStaleness(changedPaths, agent, projectRoot, options.taskId);
104
- const manifestService = new FileContextTreeManifestService({ baseDirectory: projectRoot });
105
- await manifestService.buildManifest(projectRoot);
119
+ const merged = [...new Set([...changedPaths, ...drainedSnapshot])];
120
+ if (merged.length > 0) {
121
+ await this.runStaleSummaryPropagation({ agent, parentTaskId: options.taskId, paths: merged, projectRoot });
106
122
  }
107
123
  }
108
124
  catch {
109
- // Fail-open: propagation errors never block dream
125
+ // Fail-open: propagation errors never block dream. Re-enqueue the
126
+ // drained snapshot so the next dream cycle retries — atomic drain
127
+ // already removed them, so without this they would be lost.
128
+ if (drainedSnapshot.length > 0) {
129
+ await this.deps.dreamStateService.enqueueStaleSummaryPaths(drainedSnapshot).catch(() => {
130
+ // If the re-enqueue itself fails, there is nothing more to do here.
131
+ });
132
+ }
110
133
  }
111
134
  }
112
135
  // Step 6: Write dream log
@@ -237,6 +260,20 @@ export class DreamExecutor {
237
260
  taskId,
238
261
  })));
239
262
  }
263
+ /**
264
+ * Regenerate parent `_index.md` files for the given paths and rebuild the
265
+ * manifest. Extracted as a seam so tests can override and assert which
266
+ * paths were passed (the A ∪ B merge in step 5 is the central correctness
267
+ * invariant of the deferral). Production constructs the services here so
268
+ * the dependency surface of {@link DreamExecutorDeps} stays narrow.
269
+ */
270
+ async runStaleSummaryPropagation(args) {
271
+ const summaryService = new FileContextTreeSummaryService();
272
+ await summaryService.propagateStaleness(args.paths, args.agent, args.projectRoot, args.parentTaskId);
273
+ const manifestService = new FileContextTreeManifestService({ baseDirectory: args.projectRoot });
274
+ await manifestService.buildManifest(args.projectRoot);
275
+ }
276
+ /** Errors are tracked at the log level (status='error'), not per-operation — always 0 here. */
240
277
  computeSummary(operations) {
241
278
  const summary = { consolidated: 0, errors: 0, flaggedForReview: 0, pruned: 0, synthesized: 0 };
242
279
  for (const op of operations) {
@@ -6,6 +6,16 @@ import { processLog } from '../../../utils/process-logger.js';
6
6
  import { validateApiKey as validateApiKeyViaFetcher } from '../../http/provider-model-fetcher-registry.js';
7
7
  import { OpenAICompatibleModelFetcher } from '../../http/provider-model-fetchers.js';
8
8
  import { computeExpiresAt, exchangeCodeForTokens as defaultExchangeCodeForTokens, generatePkce as defaultGeneratePkce, parseAccountIdFromIdToken, ProviderCallbackServer, ProviderCallbackTimeoutError, } from '../../provider-oauth/index.js';
9
+ const BYTEROVER_AUTH_REQUIRED_MESSAGE = [
10
+ 'ByteRover Provider requires a ByteRover account.',
11
+ '',
12
+ ' • Interactive shell: brv login',
13
+ ' • Headless / SSH / CI: create an account at https://app.byterover.dev,',
14
+ ' generate an API key at https://app.byterover.dev/settings/keys, then:',
15
+ ' brv login --api-key <key>',
16
+ '',
17
+ 'Once signed in, retry: brv providers connect byterover',
18
+ ].join('\n');
9
19
  async function defaultValidateOpenAICompatibleEndpoint(params) {
10
20
  const fetcher = new OpenAICompatibleModelFetcher(params.baseUrl, 'OpenAI Compatible');
11
21
  return fetcher.validateApiKey(params.apiKey);
@@ -148,7 +158,7 @@ export class ProviderHandler {
148
158
  this.transport.onRequest(ProviderEvents.CONNECT, async (data) => {
149
159
  const { apiKey, baseUrl, providerId } = data;
150
160
  if (providerId === 'byterover' && !this.isByteRoverAuthSatisfied()) {
151
- return { error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in', success: false };
161
+ return { error: BYTEROVER_AUTH_REQUIRED_MESSAGE, success: false };
152
162
  }
153
163
  // Verify openai-compatible endpoint is reachable before persisting anything —
154
164
  // a failed setup must not leave a placeholder config that masquerades as
@@ -187,7 +197,14 @@ export class ProviderHandler {
187
197
  // "needs setup" and unmounts any in-flight setup flow on the home
188
198
  // page. The model:setActive handler activates the provider when the
189
199
  // user picks a model, which is the right moment.
190
- const willHaveActiveModel = Boolean(provider?.defaultModel)
200
+ //
201
+ // byterover bypasses this gate: it has no model fetcher and no
202
+ // `brv model switch` recovery path, so deferring would strand it as
203
+ // connected-but-never-active. Its model is resolved at runtime via
204
+ // DEFAULT_LLM_MODEL in agent-process.ts rather than persisted here,
205
+ // so future default changes roll out without a per-user migration.
206
+ const willHaveActiveModel = providerId === 'byterover'
207
+ || Boolean(provider?.defaultModel)
191
208
  || Boolean(await this.providerConfigStore.getActiveModel(providerId));
192
209
  await this.providerConfigStore.connectProvider(providerId, {
193
210
  activeModel: provider?.defaultModel,
@@ -253,7 +270,7 @@ export class ProviderHandler {
253
270
  setupSetActive() {
254
271
  this.transport.onRequest(ProviderEvents.SET_ACTIVE, async (data) => {
255
272
  if (data.providerId === 'byterover' && !this.isByteRoverAuthSatisfied()) {
256
- return { error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in', success: false };
273
+ return { error: BYTEROVER_AUTH_REQUIRED_MESSAGE, success: false };
257
274
  }
258
275
  await this.providerConfigStore.setActiveProvider(data.providerId);
259
276
  this.transport.broadcast(TransportDaemonEventNames.PROVIDER_UPDATED, {});
@@ -5,9 +5,12 @@ export const getAuthState = () => {
5
5
  const { apiClient } = useTransportStore.getState();
6
6
  if (!apiClient)
7
7
  return Promise.reject(new Error('Not connected'));
8
- // Use 500ms timeout to fail fast if handler not ready yet
9
- // React Query will retry automatically with exponential backoff
10
- return apiClient.request(AuthEvents.GET_STATE, undefined, { timeout: 500 });
8
+ // The daemon-side handler does a network round-trip to /user/me. Measured
9
+ // p99 across multiple networks ranges 1.2-3.1s with occasional outliers,
10
+ // so 4000ms gives ~1.3x headroom over the worst observed sample with
11
+ // margin left for slower connections (mobile, international, VPN).
12
+ // React Query retries once on failure for transient blips.
13
+ return apiClient.request(AuthEvents.GET_STATE, undefined, { timeout: 4000 });
11
14
  };
12
15
  export const getAuthStateQueryOptions = () => queryOptions({
13
16
  gcTime: 5 * 60 * 1000,
@@ -23,8 +23,10 @@ export function AuthInitializer({ children }) {
23
23
  const { data: authState, isFetched, isLoading, } = useGetAuthState({
24
24
  queryConfig: {
25
25
  enabled: apiClient !== null,
26
- retry: 5,
27
- retryDelay: (attemptIndex) => Math.min(500 * 2 ** attemptIndex, 2000),
26
+ // One retry covers transient blips; the per-attempt timeout is now generous
27
+ // (3s) so we don't need 5+ retries that would block startup for ~17s when offline.
28
+ retry: 1,
29
+ retryDelay: 500,
28
30
  staleTime: 2 * 60 * 1000,
29
31
  },
30
32
  });