byterover-cli 3.10.2 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/agent/core/domain/llm/registry.d.ts +12 -0
- package/dist/agent/core/domain/llm/registry.js +49 -0
- package/dist/agent/core/domain/llm/types.d.ts +6 -0
- package/dist/agent/core/interfaces/i-content-generator.d.ts +8 -0
- package/dist/agent/infra/llm/agent-llm-service.js +18 -6
- package/dist/agent/infra/llm/context/context-manager.d.ts +4 -1
- package/dist/agent/infra/llm/context/context-manager.js +5 -1
- package/dist/agent/infra/llm/generators/ai-sdk-content-generator.d.ts +13 -0
- package/dist/agent/infra/llm/generators/ai-sdk-content-generator.js +19 -6
- package/dist/agent/infra/llm/generators/ai-sdk-message-converter.js +16 -4
- package/dist/agent/infra/llm/generators/byterover-content-generator.d.ts +1 -0
- package/dist/agent/infra/llm/generators/byterover-content-generator.js +4 -1
- package/dist/agent/infra/llm/model-capabilities.d.ts +2 -1
- package/dist/agent/infra/llm/model-capabilities.js +6 -4
- package/dist/agent/infra/llm/providers/anthropic.js +2 -0
- package/dist/agent/infra/llm/providers/deepseek.d.ts +10 -0
- package/dist/agent/infra/llm/providers/deepseek.js +33 -0
- package/dist/agent/infra/llm/providers/glm-coding-plan.d.ts +9 -0
- package/dist/agent/infra/llm/providers/glm-coding-plan.js +32 -0
- package/dist/agent/infra/llm/providers/index.js +4 -0
- package/dist/agent/infra/llm/providers/openrouter.js +2 -0
- package/dist/oclif/commands/query.js +7 -1
- package/dist/oclif/lib/task-client.d.ts +9 -0
- package/dist/oclif/lib/task-client.js +11 -1
- package/dist/server/core/domain/entities/provider-registry.js +26 -0
- package/dist/server/infra/daemon/brv-server.js +4 -0
- package/dist/server/infra/http/provider-model-fetcher-registry.js +5 -0
- package/dist/server/infra/http/provider-model-fetchers.js +54 -27
- package/dist/server/infra/mcp/mcp-server.d.ts +6 -0
- package/dist/server/infra/mcp/mcp-server.js +15 -3
- package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +1 -1
- package/dist/server/infra/mcp/tools/brv-curate-tool.js +4 -2
- package/dist/server/infra/mcp/tools/brv-query-tool.d.ts +1 -1
- package/dist/server/infra/mcp/tools/brv-query-tool.js +3 -2
- package/dist/server/infra/mcp/tools/drift-footer.d.ts +8 -0
- package/dist/server/infra/mcp/tools/drift-footer.js +16 -0
- package/dist/server/infra/process/connection-coordinator.d.ts +7 -0
- package/dist/server/infra/process/connection-coordinator.js +5 -0
- package/dist/server/infra/process/query-log-handler.d.ts +6 -0
- package/dist/server/infra/process/query-log-handler.js +23 -0
- package/dist/server/infra/process/transport-handlers.d.ts +5 -0
- package/dist/server/infra/process/transport-handlers.js +1 -0
- package/dist/tui/components/header.js +7 -1
- package/dist/tui/components/logo.d.ts +6 -0
- package/dist/tui/components/logo.js +18 -5
- package/dist/tui/features/transport/components/transport-initializer.js +8 -2
- package/dist/tui/stores/transport-store.d.ts +8 -0
- package/dist/tui/stores/transport-store.js +2 -0
- package/dist/webui/assets/index--sXE__bc.css +1 -0
- package/dist/webui/assets/{index-thSZZahh.js → index-Bkkx961b.js} +63 -63
- package/dist/webui/index.html +2 -2
- package/dist/webui/sw.js +1 -1
- package/dist/webui/workbox-9c191d2f.js +1 -0
- package/node_modules/@campfirein/brv-transport-client/dist/core/interfaces/i-client.d.ts +14 -0
- package/node_modules/@campfirein/brv-transport-client/dist/core/interfaces/i-client.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/index.d.ts +1 -0
- package/node_modules/@campfirein/brv-transport-client/dist/index.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/index.js +2 -0
- package/node_modules/@campfirein/brv-transport-client/dist/index.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/client-factory.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/client-factory.js +5 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/client-factory.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.d.ts +9 -7
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.js +11 -9
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-discovery-sync.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.d.ts +23 -6
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.js +11 -5
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-health.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-spawner.js +7 -7
- package/node_modules/@campfirein/brv-transport-client/dist/infra/daemon-spawner.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.d.ts +7 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.js +5 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/schemas/schemas.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.d.ts +8 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.d.ts.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.js +15 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/socket-io-client.js.map +1 -1
- package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.d.ts +35 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.d.ts.map +1 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.js +59 -0
- package/node_modules/@campfirein/brv-transport-client/dist/infra/version-utils.js.map +1 -0
- package/node_modules/@campfirein/brv-transport-client/package.json +1 -1
- package/oclif.manifest.json +206 -206
- package/package.json +4 -4
- package/dist/webui/assets/index-CvcqpMYn.css +0 -1
- package/dist/webui/workbox-8c29f6e4.js +0 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Used by: curate, query commands.
|
|
8
8
|
*/
|
|
9
9
|
import type { ITransportClient } from '@campfirein/brv-transport-client';
|
|
10
|
+
import type { QueryLogMatchedDoc, QueryLogTier } from '../../server/core/domain/entities/query-log-entry.js';
|
|
10
11
|
/** Collected tool call with result (mirrors TUI ToolCallEvent) */
|
|
11
12
|
export interface ToolCallRecord {
|
|
12
13
|
args: Record<string, unknown>;
|
|
@@ -19,7 +20,11 @@ export interface ToolCallRecord {
|
|
|
19
20
|
}
|
|
20
21
|
/** Completion result passed to onCompleted callback */
|
|
21
22
|
export interface TaskCompletionResult {
|
|
23
|
+
/** Wall-clock execution time for query tasks, in milliseconds. Absent for non-query tasks. */
|
|
24
|
+
durationMs?: number;
|
|
22
25
|
logId?: string;
|
|
26
|
+
/** Documents matched by the query. Empty array on cache hits; absent for non-query tasks. */
|
|
27
|
+
matchedDocs?: QueryLogMatchedDoc[];
|
|
23
28
|
/** Pending review notification from the server, present when review is required after task completion. */
|
|
24
29
|
pendingReview?: {
|
|
25
30
|
pendingCount: number;
|
|
@@ -27,7 +32,11 @@ export interface TaskCompletionResult {
|
|
|
27
32
|
};
|
|
28
33
|
result?: string;
|
|
29
34
|
taskId: string;
|
|
35
|
+
/** Resolution tier for query tasks. Absent for non-query tasks. */
|
|
36
|
+
tier?: QueryLogTier;
|
|
30
37
|
toolCalls: ToolCallRecord[];
|
|
38
|
+
/** Top compound score across matchedDocs. Absent for cache hits and non-query tasks. */
|
|
39
|
+
topScore?: number;
|
|
31
40
|
}
|
|
32
41
|
/** Error result passed to onError callback */
|
|
33
42
|
export interface TaskErrorResult {
|
|
@@ -195,7 +195,17 @@ export function waitForTaskCompletion(options, log) {
|
|
|
195
195
|
const resolvedPendingReview = payload.pendingReviewCount !== undefined && payload.pendingReviewCount > 0
|
|
196
196
|
? { pendingCount: payload.pendingReviewCount, reviewUrl: pendingReview?.reviewUrl ?? '' }
|
|
197
197
|
: pendingReview;
|
|
198
|
-
onCompleted({
|
|
198
|
+
onCompleted({
|
|
199
|
+
durationMs: payload.durationMs,
|
|
200
|
+
logId: payload.logId,
|
|
201
|
+
matchedDocs: payload.matchedDocs,
|
|
202
|
+
pendingReview: resolvedPendingReview,
|
|
203
|
+
result: payload.result,
|
|
204
|
+
taskId,
|
|
205
|
+
tier: payload.tier,
|
|
206
|
+
toolCalls,
|
|
207
|
+
topScore: payload.topScore,
|
|
208
|
+
});
|
|
199
209
|
resolve();
|
|
200
210
|
}),
|
|
201
211
|
// Task error
|
|
@@ -72,6 +72,19 @@ export const PROVIDER_REGISTRY = {
|
|
|
72
72
|
name: 'DeepInfra',
|
|
73
73
|
priority: 10,
|
|
74
74
|
},
|
|
75
|
+
deepseek: {
|
|
76
|
+
apiKeyUrl: 'https://platform.deepseek.com/api_keys',
|
|
77
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
78
|
+
category: 'other',
|
|
79
|
+
defaultModel: 'deepseek-chat',
|
|
80
|
+
description: 'DeepSeek V3 and R1 reasoning models',
|
|
81
|
+
envVars: ['DEEPSEEK_API_KEY'],
|
|
82
|
+
headers: {},
|
|
83
|
+
id: 'deepseek',
|
|
84
|
+
modelsEndpoint: '/models',
|
|
85
|
+
name: 'DeepSeek',
|
|
86
|
+
priority: 19,
|
|
87
|
+
},
|
|
75
88
|
glm: {
|
|
76
89
|
apiKeyUrl: 'https://open.z.ai',
|
|
77
90
|
baseUrl: 'https://api.z.ai/api/paas/v4',
|
|
@@ -85,6 +98,19 @@ export const PROVIDER_REGISTRY = {
|
|
|
85
98
|
name: 'GLM (Z.AI)',
|
|
86
99
|
priority: 17,
|
|
87
100
|
},
|
|
101
|
+
'glm-coding-plan': {
|
|
102
|
+
apiKeyUrl: 'https://z.ai/manage-apikey/apikey-list',
|
|
103
|
+
baseUrl: 'https://api.z.ai/api/coding/paas/v4',
|
|
104
|
+
category: 'other',
|
|
105
|
+
defaultModel: 'glm-4.7',
|
|
106
|
+
description: 'GLM models on the Z.AI Coding Plan subscription',
|
|
107
|
+
envVars: ['ZHIPU_API_KEY'],
|
|
108
|
+
headers: {},
|
|
109
|
+
id: 'glm-coding-plan',
|
|
110
|
+
modelsEndpoint: '',
|
|
111
|
+
name: 'GLM Coding Plan (Z.AI)',
|
|
112
|
+
priority: 17.5,
|
|
113
|
+
},
|
|
88
114
|
google: {
|
|
89
115
|
apiKeyUrl: 'https://aistudio.google.com/apikey',
|
|
90
116
|
baseUrl: '',
|
|
@@ -337,6 +337,10 @@ async function main() {
|
|
|
337
337
|
const transportHandlers = new TransportHandlers({
|
|
338
338
|
agentPool,
|
|
339
339
|
clientManager,
|
|
340
|
+
// The version we read at startup gets relayed in the client:register ack
|
|
341
|
+
// so peer clients (TUI / MCP) can render drift indicators without an
|
|
342
|
+
// extra round-trip.
|
|
343
|
+
daemonVersion: version,
|
|
340
344
|
// Resolves the project's review-disabled flag once at task-create. The result
|
|
341
345
|
// is stamped onto TaskInfo + TaskExecute so daemon hooks (CurateLogHandler) and
|
|
342
346
|
// the agent process (curate-tool backups, dream review entries) all observe a
|
|
@@ -45,6 +45,7 @@ export async function getModelFetcher(providerId) {
|
|
|
45
45
|
case 'cerebras': // falls through
|
|
46
46
|
case 'cohere': // falls through
|
|
47
47
|
case 'deepinfra': // falls through
|
|
48
|
+
case 'deepseek': // falls through
|
|
48
49
|
case 'groq': // falls through
|
|
49
50
|
case 'mistral': // falls through
|
|
50
51
|
case 'togetherai': // falls through
|
|
@@ -59,6 +60,10 @@ export async function getModelFetcher(providerId) {
|
|
|
59
60
|
fetcher = new ChatBasedModelFetcher('https://api.z.ai/api/paas/v4', 'GLM (Z.AI)', ['glm-4.7', 'glm-4.6', 'glm-4.5', 'glm-4.5-flash']);
|
|
60
61
|
break;
|
|
61
62
|
}
|
|
63
|
+
case 'glm-coding-plan': {
|
|
64
|
+
fetcher = new ChatBasedModelFetcher('https://api.z.ai/api/coding/paas/v4', 'GLM Coding Plan (Z.AI)', ['glm-4.7', 'glm-4.7-flash', 'glm-4.7-flashx', 'glm-5-turbo', 'glm-4.5', 'glm-4.5-flash']);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
62
67
|
case 'google': {
|
|
63
68
|
fetcher = new GoogleModelFetcher();
|
|
64
69
|
break;
|
|
@@ -429,36 +429,63 @@ export class ChatBasedModelFetcher {
|
|
|
429
429
|
return this.knownModels;
|
|
430
430
|
}
|
|
431
431
|
async validateApiKey(apiKey) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
// Other errors (429, 400, etc.) mean the key was accepted
|
|
432
|
+
// Iterate through known models so a single missing model on a tier (e.g.
|
|
433
|
+
// GLM Coding Plan doesn't yet serve the latest glm-4.7) doesn't
|
|
434
|
+
// misclassify a valid key as invalid. We accept the key as soon as ANY
|
|
435
|
+
// model responds successfully, OR returns a non-auth error like 429/5xx
|
|
436
|
+
// (which still proves the key passed auth).
|
|
437
|
+
const candidates = this.knownModels.length > 0 ? this.knownModels : [{ id: 'default' }];
|
|
438
|
+
let lastNonAuthError;
|
|
439
|
+
for (const candidate of candidates) {
|
|
440
|
+
try {
|
|
441
|
+
// eslint-disable-next-line no-await-in-loop
|
|
442
|
+
await axios.post(`${this.baseUrl}/chat/completions`, {
|
|
443
|
+
max_tokens: 1,
|
|
444
|
+
messages: [{ content: 'hi', role: 'user' }],
|
|
445
|
+
model: candidate.id,
|
|
446
|
+
}, {
|
|
447
|
+
headers: {
|
|
448
|
+
Authorization: `Bearer ${apiKey}`,
|
|
449
|
+
'Content-Type': 'application/json',
|
|
450
|
+
},
|
|
451
|
+
httpAgent: ProxyConfig.getProxyAgent(),
|
|
452
|
+
httpsAgent: ProxyConfig.getProxyAgent(),
|
|
453
|
+
proxy: false,
|
|
454
|
+
timeout: 15_000,
|
|
455
|
+
});
|
|
458
456
|
return { isValid: true };
|
|
459
457
|
}
|
|
460
|
-
|
|
458
|
+
catch (error) {
|
|
459
|
+
if (isAxiosError(error)) {
|
|
460
|
+
if (error.response?.status === 401) {
|
|
461
|
+
return { error: 'Invalid API key', isValid: false };
|
|
462
|
+
}
|
|
463
|
+
if (error.response?.status === 403) {
|
|
464
|
+
return { error: 'API key does not have required permissions', isValid: false };
|
|
465
|
+
}
|
|
466
|
+
// 400/404 may mean "model not available on this tier" — try next.
|
|
467
|
+
if (error.response?.status === 400 || error.response?.status === 404) {
|
|
468
|
+
lastNonAuthError = error;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
// Axios errors that are not 401/403/400/404 (e.g. 429, 5xx, or
|
|
472
|
+
// network-level errors with no response like ECONNREFUSED) are
|
|
473
|
+
// treated as "key accepted" — either auth was passed (429/5xx) or
|
|
474
|
+
// we can't determine otherwise (no response). Optimistic: prefer a
|
|
475
|
+
// false-positive valid over a false-negative invalid.
|
|
476
|
+
return { isValid: true };
|
|
477
|
+
}
|
|
478
|
+
lastNonAuthError = error;
|
|
479
|
+
}
|
|
461
480
|
}
|
|
481
|
+
// Every candidate model returned 400/404 or a non-axios error and none
|
|
482
|
+
// gave us a positive auth signal. Treat the key as inconclusive — but
|
|
483
|
+
// since 401/403 was never observed, surface the last error so the user
|
|
484
|
+
// can see the real cause (often a model-availability issue, not auth).
|
|
485
|
+
return {
|
|
486
|
+
error: lastNonAuthError instanceof Error ? lastNonAuthError.message : 'Validation failed for all known models',
|
|
487
|
+
isValid: false,
|
|
488
|
+
};
|
|
462
489
|
}
|
|
463
490
|
}
|
|
464
491
|
// ============================================================================
|
|
@@ -57,6 +57,12 @@ export declare class ByteRoverMcpServer {
|
|
|
57
57
|
* Log to stderr (stdout is reserved for MCP protocol).
|
|
58
58
|
*/
|
|
59
59
|
private log;
|
|
60
|
+
/**
|
|
61
|
+
* Logs a one-line drift notice when the running daemon's version differs
|
|
62
|
+
* from this MCP's. Helps users notice an out-of-sync IDE without forcing
|
|
63
|
+
* a reconnect — the protocol is backward-compatible across the gap.
|
|
64
|
+
*/
|
|
65
|
+
private logDaemonVersionDrift;
|
|
60
66
|
/**
|
|
61
67
|
* Sends the cached agent name to the daemon (fire-and-forget).
|
|
62
68
|
* Called after MCP initialize handshake and on Socket.IO reconnection.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { connectToDaemon, createDaemonReconnector, } from '@campfirein/brv-transport-client';
|
|
1
|
+
import { connectToDaemon, createDaemonReconnector, versionsAreEquivalent, } from '@campfirein/brv-transport-client';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { TransportClientEventNames } from '../../core/domain/transport/schemas.js';
|
|
@@ -55,8 +55,8 @@ export class ByteRoverMcpServer {
|
|
|
55
55
|
: undefined;
|
|
56
56
|
// Register tools with lazy client getter
|
|
57
57
|
// Client will be set when start() is called
|
|
58
|
-
registerBrvQueryTool(this.server, () => this.client, () => this.getWorkingDirectory(), getStartupProjectContext);
|
|
59
|
-
registerBrvCurateTool(this.server, () => this.client, () => this.getWorkingDirectory(), getStartupProjectContext);
|
|
58
|
+
registerBrvQueryTool(this.server, () => this.client, () => this.getWorkingDirectory(), getStartupProjectContext, config.version);
|
|
59
|
+
registerBrvCurateTool(this.server, () => this.client, () => this.getWorkingDirectory(), getStartupProjectContext, config.version);
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Starts the MCP server.
|
|
@@ -80,12 +80,14 @@ export class ByteRoverMcpServer {
|
|
|
80
80
|
this.log(`Connected to brv instance at ${result.projectRoot}`);
|
|
81
81
|
this.log(`Client ID: ${result.client.getClientId()}`);
|
|
82
82
|
this.log(`Initial connection state: ${result.client.getState()}`);
|
|
83
|
+
this.logDaemonVersionDrift(result.client.getDaemonVersion?.());
|
|
83
84
|
// Auto-reconnect on disconnect (shared logic from brv-transport-client)
|
|
84
85
|
this.reconnectorHandle = createDaemonReconnector(result.client, {
|
|
85
86
|
connectOptions: this.connectOptions,
|
|
86
87
|
onReconnected: (newClient) => {
|
|
87
88
|
this.client = newClient;
|
|
88
89
|
this.log(`Reconnected successfully! Client ID: ${newClient.getClientId()}`);
|
|
90
|
+
this.logDaemonVersionDrift(newClient.getDaemonVersion?.());
|
|
89
91
|
this.sendAgentName();
|
|
90
92
|
},
|
|
91
93
|
onStateChange: (state) => {
|
|
@@ -157,6 +159,16 @@ export class ByteRoverMcpServer {
|
|
|
157
159
|
log(msg) {
|
|
158
160
|
process.stderr.write(`[brv-mcp] ${msg}\n`);
|
|
159
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Logs a one-line drift notice when the running daemon's version differs
|
|
164
|
+
* from this MCP's. Helps users notice an out-of-sync IDE without forcing
|
|
165
|
+
* a reconnect — the protocol is backward-compatible across the gap.
|
|
166
|
+
*/
|
|
167
|
+
logDaemonVersionDrift(daemonVersion) {
|
|
168
|
+
if (daemonVersion && !versionsAreEquivalent(this.config.version, daemonVersion)) {
|
|
169
|
+
this.log(`connected to daemon ${daemonVersion}; this MCP is ${this.config.version} (backward-compatible protocol)`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
160
172
|
/**
|
|
161
173
|
* Sends the cached agent name to the daemon (fire-and-forget).
|
|
162
174
|
* Called after MCP initialize handshake and on Socket.IO reconnection.
|
|
@@ -27,4 +27,4 @@ export declare const BrvCurateInputSchema: z.ZodObject<{
|
|
|
27
27
|
* Uses fire-and-forget pattern: returns immediately after queueing the task.
|
|
28
28
|
* The curation is processed asynchronously by the ByteRover agent.
|
|
29
29
|
*/
|
|
30
|
-
export declare function registerBrvCurateTool(server: McpServer, getClient: () => ITransportClient | undefined, getWorkingDirectory: () => string | undefined, getStartupProjectContext: () => McpStartupProjectContext | undefined): void;
|
|
30
|
+
export declare function registerBrvCurateTool(server: McpServer, getClient: () => ITransportClient | undefined, getWorkingDirectory: () => string | undefined, getStartupProjectContext: () => McpStartupProjectContext | undefined, clientVersion: string): void;
|
|
@@ -2,6 +2,7 @@ import { waitForConnectedClient } from '@campfirein/brv-transport-client';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { TransportTaskEventNames } from '../../../core/domain/transport/schemas.js';
|
|
5
|
+
import { appendDriftFooter } from './drift-footer.js';
|
|
5
6
|
import { associateProjectWithRetry, resolveMcpTaskContext } from './mcp-project-context.js';
|
|
6
7
|
import { resolveClientCwd } from './resolve-client-cwd.js';
|
|
7
8
|
import { cwdField } from './shared-schema.js';
|
|
@@ -30,7 +31,7 @@ export const BrvCurateInputSchema = z.object({
|
|
|
30
31
|
* Uses fire-and-forget pattern: returns immediately after queueing the task.
|
|
31
32
|
* The curation is processed asynchronously by the ByteRover agent.
|
|
32
33
|
*/
|
|
33
|
-
export function registerBrvCurateTool(server, getClient, getWorkingDirectory, getStartupProjectContext) {
|
|
34
|
+
export function registerBrvCurateTool(server, getClient, getWorkingDirectory, getStartupProjectContext, clientVersion) {
|
|
34
35
|
server.registerTool('brv-curate', {
|
|
35
36
|
description: 'Store context to the ByteRover context tree. Save patterns, decisions, or insights. ' +
|
|
36
37
|
'Curation is processed asynchronously — the tool returns immediately after queueing.',
|
|
@@ -92,10 +93,11 @@ export function registerBrvCurateTool(server, getClient, getWorkingDirectory, ge
|
|
|
92
93
|
const logId = ack?.logId;
|
|
93
94
|
const modeDescription = hasFolder ? 'folder pack' : 'curation';
|
|
94
95
|
const logSuffix = logId ? `, logId: ${logId}` : '';
|
|
96
|
+
const queuedMessage = `✓ Context queued for ${modeDescription} (taskId: ${taskId}${logSuffix}). The curation will be processed asynchronously.`;
|
|
95
97
|
return {
|
|
96
98
|
content: [
|
|
97
99
|
{
|
|
98
|
-
text:
|
|
100
|
+
text: appendDriftFooter(queuedMessage, clientVersion, client.getDaemonVersion?.()),
|
|
99
101
|
type: 'text',
|
|
100
102
|
},
|
|
101
103
|
],
|
|
@@ -18,4 +18,4 @@ export declare const BrvQueryInputSchema: z.ZodObject<{
|
|
|
18
18
|
* This tool allows coding agents to query the ByteRover context tree
|
|
19
19
|
* for patterns, decisions, implementation details, or any stored knowledge.
|
|
20
20
|
*/
|
|
21
|
-
export declare function registerBrvQueryTool(server: McpServer, getClient: () => ITransportClient | undefined, getWorkingDirectory: () => string | undefined, getStartupProjectContext: () => McpStartupProjectContext | undefined): void;
|
|
21
|
+
export declare function registerBrvQueryTool(server: McpServer, getClient: () => ITransportClient | undefined, getWorkingDirectory: () => string | undefined, getStartupProjectContext: () => McpStartupProjectContext | undefined, clientVersion: string): void;
|
|
@@ -2,6 +2,7 @@ import { waitForConnectedClient } from '@campfirein/brv-transport-client';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { TransportTaskEventNames } from '../../../core/domain/transport/schemas.js';
|
|
5
|
+
import { appendDriftFooter } from './drift-footer.js';
|
|
5
6
|
import { associateProjectWithRetry, resolveMcpTaskContext } from './mcp-project-context.js';
|
|
6
7
|
import { resolveClientCwd } from './resolve-client-cwd.js';
|
|
7
8
|
import { cwdField } from './shared-schema.js';
|
|
@@ -16,7 +17,7 @@ export const BrvQueryInputSchema = z.object({
|
|
|
16
17
|
* This tool allows coding agents to query the ByteRover context tree
|
|
17
18
|
* for patterns, decisions, implementation details, or any stored knowledge.
|
|
18
19
|
*/
|
|
19
|
-
export function registerBrvQueryTool(server, getClient, getWorkingDirectory, getStartupProjectContext) {
|
|
20
|
+
export function registerBrvQueryTool(server, getClient, getWorkingDirectory, getStartupProjectContext, clientVersion) {
|
|
20
21
|
server.registerTool('brv-query', {
|
|
21
22
|
description: 'Query the ByteRover context tree for patterns, decisions, or implementation details.',
|
|
22
23
|
inputSchema: BrvQueryInputSchema,
|
|
@@ -64,7 +65,7 @@ export function registerBrvQueryTool(server, getClient, getWorkingDirectory, get
|
|
|
64
65
|
// Wait for the already-listening result promise
|
|
65
66
|
const result = await resultPromise;
|
|
66
67
|
return {
|
|
67
|
-
content: [{ text: result, type: 'text' }],
|
|
68
|
+
content: [{ text: appendDriftFooter(result, clientVersion, client.getDaemonVersion?.()), type: 'text' }],
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
catch (error) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appends a version-drift footer to MCP tool text responses when the MCP
|
|
3
|
+
* client and the running daemon are at different versions.
|
|
4
|
+
*
|
|
5
|
+
* Returns the body unchanged when versions match or when the daemon hasn't
|
|
6
|
+
* reported its version yet (pre-fix daemon during a rolling upgrade).
|
|
7
|
+
*/
|
|
8
|
+
export declare function appendDriftFooter(body: string, clientVersion: string, daemonVersion?: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { versionsAreEquivalent } from '@campfirein/brv-transport-client';
|
|
2
|
+
/**
|
|
3
|
+
* Appends a version-drift footer to MCP tool text responses when the MCP
|
|
4
|
+
* client and the running daemon are at different versions.
|
|
5
|
+
*
|
|
6
|
+
* Returns the body unchanged when versions match or when the daemon hasn't
|
|
7
|
+
* reported its version yet (pre-fix daemon during a rolling upgrade).
|
|
8
|
+
*/
|
|
9
|
+
export function appendDriftFooter(body, clientVersion, daemonVersion) {
|
|
10
|
+
if (!daemonVersion || versionsAreEquivalent(clientVersion, daemonVersion)) {
|
|
11
|
+
return body;
|
|
12
|
+
}
|
|
13
|
+
return (body +
|
|
14
|
+
`\n\n---\nNote: this brv MCP is at ${clientVersion} while the running daemon is at ${daemonVersion}. ` +
|
|
15
|
+
`The protocol is backward-compatible. Restart your IDE to align versions.`);
|
|
16
|
+
}
|
|
@@ -18,6 +18,12 @@ import type { TaskRouter } from './task-router.js';
|
|
|
18
18
|
type ConnectionCoordinatorOptions = {
|
|
19
19
|
agentPool?: IAgentPool;
|
|
20
20
|
clientManager?: IClientManager;
|
|
21
|
+
/**
|
|
22
|
+
* Daemon version surfaced in `client:register` ack. Lets clients render
|
|
23
|
+
* drift indicators without a separate round-trip. Optional so older
|
|
24
|
+
* deployments still build.
|
|
25
|
+
*/
|
|
26
|
+
daemonVersion?: string;
|
|
21
27
|
projectRegistry?: IProjectRegistry;
|
|
22
28
|
projectRouter?: IProjectRouter;
|
|
23
29
|
taskRouter: TaskRouter;
|
|
@@ -31,6 +37,7 @@ export declare class ConnectionCoordinator {
|
|
|
31
37
|
private agentClients;
|
|
32
38
|
private readonly agentPool;
|
|
33
39
|
private readonly clientManager;
|
|
40
|
+
private readonly daemonVersion;
|
|
34
41
|
private readonly projectRegistry;
|
|
35
42
|
private readonly projectRouter;
|
|
36
43
|
private readonly taskRouter;
|
|
@@ -24,6 +24,7 @@ export class ConnectionCoordinator {
|
|
|
24
24
|
agentClients = new Map();
|
|
25
25
|
agentPool;
|
|
26
26
|
clientManager;
|
|
27
|
+
daemonVersion;
|
|
27
28
|
projectRegistry;
|
|
28
29
|
projectRouter;
|
|
29
30
|
taskRouter;
|
|
@@ -32,6 +33,7 @@ export class ConnectionCoordinator {
|
|
|
32
33
|
this.transport = options.transport;
|
|
33
34
|
this.agentPool = options.agentPool;
|
|
34
35
|
this.clientManager = options.clientManager;
|
|
36
|
+
this.daemonVersion = options.daemonVersion;
|
|
35
37
|
this.projectRouter = options.projectRouter;
|
|
36
38
|
this.projectRegistry = options.projectRegistry;
|
|
37
39
|
this.taskRouter = options.taskRouter;
|
|
@@ -189,6 +191,9 @@ export class ConnectionCoordinator {
|
|
|
189
191
|
if (data.projectPath) {
|
|
190
192
|
this.addToProjectRoom(clientId, data.projectPath);
|
|
191
193
|
}
|
|
194
|
+
if (this.daemonVersion) {
|
|
195
|
+
return { daemonVersion: this.daemonVersion, success: true };
|
|
196
|
+
}
|
|
192
197
|
return { success: true };
|
|
193
198
|
}
|
|
194
199
|
handleClientUpdateAgentName(clientId, data) {
|
|
@@ -25,6 +25,12 @@ export declare class QueryLogHandler implements ITaskLifecycleHook {
|
|
|
25
25
|
private readonly tasks;
|
|
26
26
|
constructor(createStore?: ((projectPath: string) => IQueryLogStore) | undefined);
|
|
27
27
|
cleanup(taskId: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Expose query metadata via the lifecycle-hook contract so TaskRouter can merge it into
|
|
30
|
+
* the task:completed payload sent to the originating client. Returning {} when no metadata
|
|
31
|
+
* is available keeps the merge a no-op and lets the daemon emit task:completed unchanged.
|
|
32
|
+
*/
|
|
33
|
+
getTaskCompletionData(taskId: string): Record<string, unknown>;
|
|
28
34
|
onTaskCancelled(taskId: string, _task: TaskInfo): Promise<void>;
|
|
29
35
|
onTaskCompleted(taskId: string, result: string, _task: TaskInfo): Promise<void>;
|
|
30
36
|
onTaskCreate(task: TaskInfo): Promise<void | {
|
|
@@ -39,6 +39,29 @@ export class QueryLogHandler {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Expose query metadata via the lifecycle-hook contract so TaskRouter can merge it into
|
|
44
|
+
* the task:completed payload sent to the originating client. Returning {} when no metadata
|
|
45
|
+
* is available keeps the merge a no-op and lets the daemon emit task:completed unchanged.
|
|
46
|
+
*/
|
|
47
|
+
getTaskCompletionData(taskId) {
|
|
48
|
+
const state = this.tasks.get(taskId);
|
|
49
|
+
if (!state?.queryResult)
|
|
50
|
+
return {};
|
|
51
|
+
// Flatten the QueryExecutorResult's nested shape onto the task:completed payload so
|
|
52
|
+
// it matches the public RecallResult contract (flat `durationMs` / `topScore`).
|
|
53
|
+
// `timing` is always populated by every QueryExecutor branch, so no guard.
|
|
54
|
+
// `searchMetadata` is omitted on cache hits (Tier 0/1), so guard before extracting.
|
|
55
|
+
const out = {
|
|
56
|
+
durationMs: state.queryResult.timing.durationMs,
|
|
57
|
+
matchedDocs: state.queryResult.matchedDocs,
|
|
58
|
+
tier: state.queryResult.tier,
|
|
59
|
+
};
|
|
60
|
+
if (state.queryResult.searchMetadata !== undefined) {
|
|
61
|
+
out.topScore = state.queryResult.searchMetadata.topScore;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
42
65
|
async onTaskCancelled(taskId, _task) {
|
|
43
66
|
const state = this.tasks.get(taskId);
|
|
44
67
|
if (!state)
|
|
@@ -38,6 +38,11 @@ export type { TaskInfo } from './types.js';
|
|
|
38
38
|
type TransportHandlersOptions = {
|
|
39
39
|
agentPool?: IAgentPool;
|
|
40
40
|
clientManager?: IClientManager;
|
|
41
|
+
/**
|
|
42
|
+
* Daemon's CLI version (read from package.json at startup). Surfaced in the
|
|
43
|
+
* `client:register` ack so clients can render version-drift indicators.
|
|
44
|
+
*/
|
|
45
|
+
daemonVersion?: string;
|
|
41
46
|
/** Resolves project's review-disabled flag at task-create. Snapshotted once into TaskInfo + TaskExecute. */
|
|
42
47
|
isReviewDisabled?: IsReviewDisabledResolver;
|
|
43
48
|
/** Lifecycle hooks for task events (e.g. CurateLogHandler). */
|
|
@@ -52,6 +52,7 @@ export class TransportHandlers {
|
|
|
52
52
|
this.connectionCoordinator = new ConnectionCoordinator({
|
|
53
53
|
agentPool: options.agentPool,
|
|
54
54
|
clientManager: options.clientManager,
|
|
55
|
+
daemonVersion: options.daemonVersion,
|
|
55
56
|
projectRegistry: options.projectRegistry,
|
|
56
57
|
projectRouter: options.projectRouter,
|
|
57
58
|
taskRouter: this.taskRouter,
|
|
@@ -7,10 +7,16 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
7
7
|
* - Connected agent status
|
|
8
8
|
* - Queue stats (pending/processing)
|
|
9
9
|
*/
|
|
10
|
+
import { versionsAreEquivalent } from '@campfirein/brv-transport-client';
|
|
10
11
|
import { Box } from 'ink';
|
|
11
12
|
import { useTransportStore } from '../stores/transport-store.js';
|
|
12
13
|
import { Logo } from './logo.js';
|
|
13
14
|
export const Header = ({ compact }) => {
|
|
14
15
|
const version = useTransportStore((s) => s.version);
|
|
15
|
-
|
|
16
|
+
const daemonVersion = useTransportStore((s) => s.daemonVersion);
|
|
17
|
+
// Drift indicator surfaces when this brv build connects to a daemon spawned
|
|
18
|
+
// by a different build. Rendered inline by Logo so the banner stays a single
|
|
19
|
+
// line; hidden when versions match or the daemon is too old to advertise.
|
|
20
|
+
const isOutdated = daemonVersion !== undefined && !versionsAreEquivalent(version, daemonVersion);
|
|
21
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, width: "100%", children: _jsx(Logo, { compact: compact, driftDaemonVersion: isOutdated ? daemonVersion : undefined, version: version }) }));
|
|
16
22
|
};
|
|
@@ -25,6 +25,12 @@ interface LogoProps {
|
|
|
25
25
|
* Compact mode, only show text logo
|
|
26
26
|
*/
|
|
27
27
|
compact?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Daemon version to surface inline as a drift indicator. Pass undefined when
|
|
30
|
+
* the local CLI and the running daemon agree on version (or the daemon is
|
|
31
|
+
* too old to advertise its version) so the indicator stays hidden.
|
|
32
|
+
*/
|
|
33
|
+
driftDaemonVersion?: string;
|
|
28
34
|
/**
|
|
29
35
|
* Optional version to display
|
|
30
36
|
*/
|
|
@@ -80,7 +80,7 @@ function getLogoLines(variant, terminalWidth) {
|
|
|
80
80
|
*
|
|
81
81
|
* Automatically selects the best logo variant based on terminal dimensions.
|
|
82
82
|
*/
|
|
83
|
-
export const Logo = ({ compact, version }) => {
|
|
83
|
+
export const Logo = ({ compact, driftDaemonVersion, version }) => {
|
|
84
84
|
const { stdout } = useStdout();
|
|
85
85
|
const { theme: { colors }, } = useTheme();
|
|
86
86
|
const isLatestVersion = useIsLatestVersion(version ?? '');
|
|
@@ -88,15 +88,28 @@ export const Logo = ({ compact, version }) => {
|
|
|
88
88
|
const terminalHeight = stdout?.rows ?? 24;
|
|
89
89
|
const variant = useMemo(() => (compact ? 'text' : selectLogoVariant(terminalWidth, terminalHeight)), [compact, terminalWidth, terminalHeight]);
|
|
90
90
|
const logoLines = useMemo(() => getLogoLines(variant, terminalWidth), [variant, terminalWidth]);
|
|
91
|
-
|
|
91
|
+
// Inline drift token, e.g. " [outdated, daemon v3.99.0]". Empty when the
|
|
92
|
+
// header has no daemon-version drift to surface — keeps the banner length
|
|
93
|
+
// calculation symmetric with the no-drift case.
|
|
94
|
+
const driftText = driftDaemonVersion ? ` [outdated, daemon v${driftDaemonVersion}]` : '';
|
|
95
|
+
const headerLine = useMemo(() => {
|
|
96
|
+
if (variant !== 'full' || !LOGO_FULL[0])
|
|
97
|
+
return null;
|
|
98
|
+
const base = getHeaderLine(LOGO_FULL[0], version ?? '', terminalWidth);
|
|
99
|
+
if (!driftText)
|
|
100
|
+
return base;
|
|
101
|
+
// Re-pad so the trailing `/////` fills the row after the drift token.
|
|
102
|
+
const contentLength = base.brv.length + base.spaces.length + base.version.length + driftText.length;
|
|
103
|
+
return { ...base, padEnd: calculatePadEnd(contentLength, terminalWidth) };
|
|
104
|
+
}, [variant, version, terminalWidth, driftText]);
|
|
92
105
|
// Text-only logo for minimal terminals
|
|
93
106
|
if (variant === 'text') {
|
|
94
|
-
const textContent = MINI_LOGO + (version ? ` v${version}` : '') + (isLatestVersion ? ' (latest)' : '');
|
|
107
|
+
const textContent = MINI_LOGO + (version ? ` v${version}` : '') + (isLatestVersion ? ' (latest)' : '') + driftText;
|
|
95
108
|
const padEnd = calculatePadEnd(textContent.length, terminalWidth);
|
|
96
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: PAD_START }), _jsxs(Text, { children: [_jsx(Text, { bold: true, color: colors.primary, children: MINI_LOGO }), version && _jsxs(Text, { color: colors.primary, children: [" v", version] }), isLatestVersion && _jsx(Text, { color: colors.primary, children: " (latest)" })] }), _jsx(Text, { color: colors.primary, children: padEnd })] }));
|
|
109
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: PAD_START }), _jsxs(Text, { children: [_jsx(Text, { bold: true, color: colors.primary, children: MINI_LOGO }), version && _jsxs(Text, { color: colors.primary, children: [" v", version] }), isLatestVersion && _jsx(Text, { color: colors.primary, children: " (latest)" }), driftText && _jsx(Text, { color: colors.warning, children: driftText })] }), _jsx(Text, { color: colors.primary, children: padEnd })] }));
|
|
97
110
|
}
|
|
98
111
|
// ASCII logo with header line and version
|
|
99
|
-
return (_jsxs(Box, { flexDirection: "column", children: [headerLine && (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: headerLine.padStart }), _jsxs(Text, { children: [_jsx(Text, { children: headerLine.brv }), _jsx(Text, { children: headerLine.spaces }), _jsx(Text, { color: colors.primary, children: headerLine.version })] }), _jsx(Text, { color: colors.primary, children: headerLine.padEnd })] })), logoLines.map((line, index) => (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: line.padStart }), _jsx(Text, { color: colors.primary, children: line.content }), _jsx(Text, { color: colors.primary, children: line.padEnd })] }, index)))] }));
|
|
112
|
+
return (_jsxs(Box, { flexDirection: "column", children: [headerLine && (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: headerLine.padStart }), _jsxs(Text, { children: [_jsx(Text, { children: headerLine.brv }), _jsx(Text, { children: headerLine.spaces }), _jsx(Text, { color: colors.primary, children: headerLine.version }), driftText && _jsx(Text, { color: colors.warning, children: driftText })] }), _jsx(Text, { color: colors.primary, children: headerLine.padEnd })] })), logoLines.map((line, index) => (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: line.padStart }), _jsx(Text, { color: colors.primary, children: line.content }), _jsx(Text, { color: colors.primary, children: line.padEnd })] }, index)))] }));
|
|
100
113
|
};
|
|
101
114
|
/**
|
|
102
115
|
* Export utilities for external use
|
|
@@ -14,7 +14,7 @@ import { getAllEventValues } from '../../../../shared/transport/events/index.js'
|
|
|
14
14
|
import { initTransportLog, logTransportEvent } from '../../../lib/transport-logger.js';
|
|
15
15
|
import { useTransportStore } from '../../../stores/transport-store.js';
|
|
16
16
|
export function TransportInitializer({ children }) {
|
|
17
|
-
const { incrementReconnectCount, setClient, setConnectionState, setError } = useTransportStore();
|
|
17
|
+
const { incrementReconnectCount, setClient, setConnectionState, setDaemonVersion, setError } = useTransportStore();
|
|
18
18
|
useEffect(() => {
|
|
19
19
|
let mounted = true;
|
|
20
20
|
let reconnectorHandle;
|
|
@@ -87,6 +87,9 @@ export function TransportInitializer({ children }) {
|
|
|
87
87
|
logTransportEvent('_connection', { clientId: newClient.getClientId(), state: 'initialized' });
|
|
88
88
|
// Set client in store (this also creates apiClient)
|
|
89
89
|
setClient(newClient);
|
|
90
|
+
// Capture daemon version from register ack so the header can render
|
|
91
|
+
// a drift indicator when the daemon was started by a different brv build.
|
|
92
|
+
setDaemonVersion(newClient.getDaemonVersion?.());
|
|
90
93
|
// Auto-reconnect on disconnect (shared logic from brv-transport-client)
|
|
91
94
|
reconnectorHandle = createDaemonReconnector(newClient, {
|
|
92
95
|
connectOptions,
|
|
@@ -95,6 +98,9 @@ export function TransportInitializer({ children }) {
|
|
|
95
98
|
return;
|
|
96
99
|
registerEventHandlers(reconnectedClient);
|
|
97
100
|
setClient(reconnectedClient);
|
|
101
|
+
// Refresh on reconnect — the daemon may have been replaced by a
|
|
102
|
+
// peer client at a different version.
|
|
103
|
+
setDaemonVersion(reconnectedClient.getDaemonVersion?.());
|
|
98
104
|
logTransportEvent('_reconnect', { clientId: reconnectedClient.getClientId(), state: 'success' });
|
|
99
105
|
},
|
|
100
106
|
onStateChange(state, client) {
|
|
@@ -137,6 +143,6 @@ export function TransportInitializer({ children }) {
|
|
|
137
143
|
});
|
|
138
144
|
}
|
|
139
145
|
};
|
|
140
|
-
}, [incrementReconnectCount, setClient, setConnectionState, setError]);
|
|
146
|
+
}, [incrementReconnectCount, setClient, setConnectionState, setDaemonVersion, setError]);
|
|
141
147
|
return _jsx(_Fragment, { children: children });
|
|
142
148
|
}
|
|
@@ -14,6 +14,12 @@ export interface TransportState {
|
|
|
14
14
|
client: ITransportClient | null;
|
|
15
15
|
/** Current connection state */
|
|
16
16
|
connectionState: ConnectionState;
|
|
17
|
+
/**
|
|
18
|
+
* Daemon version reported in the most recent client:register ack.
|
|
19
|
+
* Undefined when the daemon is too old to advertise its version.
|
|
20
|
+
* Drives the version-drift indicator in the TUI header.
|
|
21
|
+
*/
|
|
22
|
+
daemonVersion: string | undefined;
|
|
17
23
|
/** Connection error if any */
|
|
18
24
|
error: Error | null;
|
|
19
25
|
/** Whether the client is connected */
|
|
@@ -36,6 +42,8 @@ export interface TransportActions {
|
|
|
36
42
|
setClient: (client: ITransportClient) => void;
|
|
37
43
|
/** Update connection state */
|
|
38
44
|
setConnectionState: (state: ConnectionState) => void;
|
|
45
|
+
/** Set or clear the daemon version (called after every connect / reconnect) */
|
|
46
|
+
setDaemonVersion: (daemonVersion: string | undefined) => void;
|
|
39
47
|
/** Set connection error */
|
|
40
48
|
setError: (error: Error | null) => void;
|
|
41
49
|
/** Set resolved project info from oclif main */
|