byterover-cli 1.2.0 → 1.3.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 +87 -9
- package/dist/constants.d.ts +0 -5
- package/dist/constants.js +0 -5
- package/dist/core/domain/cipher/agent/agent-info.d.ts +17 -17
- package/dist/core/domain/cipher/llm/schemas.d.ts +14 -14
- package/dist/core/domain/cipher/session/session-metadata.d.ts +2 -2
- package/dist/core/domain/entities/agent.js +6 -6
- package/dist/core/domain/entities/connector-type.d.ts +2 -1
- package/dist/core/domain/entities/connector-type.js +2 -1
- package/dist/core/domain/transport/schemas.d.ts +66 -66
- package/dist/core/interfaces/cipher/i-chat-session.d.ts +3 -1
- package/dist/core/interfaces/connectors/i-connector.d.ts +2 -2
- package/dist/core/interfaces/i-file-service.d.ts +7 -0
- package/dist/infra/auth/oauth-service.d.ts +15 -0
- package/dist/infra/auth/oauth-service.js +38 -2
- package/dist/infra/cipher/agent/agent-schemas.d.ts +42 -42
- package/dist/infra/cipher/llm/context/context-manager.js +7 -9
- package/dist/infra/cipher/llm/internal-llm-service.d.ts +5 -1
- package/dist/infra/cipher/llm/internal-llm-service.js +57 -46
- package/dist/infra/cipher/session/chat-session.d.ts +3 -1
- package/dist/infra/cipher/session/chat-session.js +5 -3
- package/dist/infra/cipher/system-prompt/contributor-schemas.d.ts +8 -8
- package/dist/infra/cipher/system-prompt/schemas.d.ts +5 -5
- package/dist/infra/cipher/tools/implementations/task-tool.js +3 -3
- package/dist/infra/connectors/connector-manager.js +2 -0
- package/dist/infra/connectors/hook/hook-connector.d.ts +1 -1
- package/dist/infra/connectors/hook/hook-connector.js +3 -3
- package/dist/infra/connectors/mcp/mcp-connector.d.ts +1 -1
- package/dist/infra/connectors/mcp/mcp-connector.js +4 -4
- package/dist/infra/connectors/rules/rules-connector.d.ts +1 -1
- package/dist/infra/connectors/rules/rules-connector.js +4 -4
- package/dist/infra/connectors/shared/template-service.js +4 -0
- package/dist/infra/connectors/skill/index.d.ts +1 -0
- package/dist/infra/connectors/skill/index.js +1 -0
- package/dist/infra/connectors/skill/skill-connector-config.d.ts +45 -0
- package/dist/infra/connectors/skill/skill-connector-config.js +26 -0
- package/dist/infra/connectors/skill/skill-connector.d.ts +39 -0
- package/dist/infra/connectors/skill/skill-connector.js +160 -0
- package/dist/infra/connectors/skill/skill-content-loader.d.ts +18 -0
- package/dist/infra/connectors/skill/skill-content-loader.js +33 -0
- package/dist/infra/file/fs-file-service.d.ts +7 -0
- package/dist/infra/file/fs-file-service.js +15 -1
- package/dist/infra/mcp/tools/task-result-waiter.js +8 -0
- package/dist/infra/process/agent-worker.js +30 -14
- package/dist/infra/process/task-queue-manager.d.ts +23 -34
- package/dist/infra/process/task-queue-manager.js +57 -118
- package/dist/infra/process/transport-handlers.js +1 -7
- package/dist/infra/repl/commands/connectors-command.js +1 -1
- package/dist/infra/transport/socket-io-transport-client.d.ts +9 -0
- package/dist/infra/transport/socket-io-transport-client.js +21 -2
- package/dist/infra/usecase/connectors-use-case.js +8 -2
- package/dist/infra/usecase/init-use-case.js +1 -1
- package/dist/infra/usecase/reset-use-case.d.ts +1 -0
- package/dist/infra/usecase/reset-use-case.js +4 -1
- package/dist/{commands → oclif/commands}/curate.d.ts +1 -1
- package/dist/{commands → oclif/commands}/curate.js +6 -6
- package/dist/{commands → oclif/commands}/hook-prompt-submit.d.ts +1 -1
- package/dist/{commands → oclif/commands}/hook-prompt-submit.js +3 -3
- package/dist/{commands → oclif/commands}/main.js +10 -10
- package/dist/{commands → oclif/commands}/mcp.js +2 -2
- package/dist/{commands → oclif/commands}/query.d.ts +1 -1
- package/dist/{commands → oclif/commands}/query.js +6 -6
- package/dist/{commands → oclif/commands}/status.d.ts +1 -1
- package/dist/{commands → oclif/commands}/status.js +8 -8
- package/dist/{commands → oclif/commands}/watch.d.ts +3 -3
- package/dist/{commands → oclif/commands}/watch.js +6 -6
- package/dist/oclif/constants.d.ts +11 -0
- package/dist/oclif/constants.js +11 -0
- package/dist/{hooks → oclif/hooks}/prerun/validate-brv-config-version.d.ts +1 -1
- package/dist/{hooks → oclif/hooks}/prerun/validate-brv-config-version.js +2 -2
- package/dist/templates/sections/command-reference.md +5 -96
- package/dist/templates/sections/workflow.md +21 -16
- package/dist/templates/skill/SKILL.md +91 -0
- package/dist/templates/skill/TROUBLESHOOTING.md +50 -0
- package/dist/templates/skill/WORKFLOWS.md +229 -0
- package/dist/utils/type-guards.d.ts +11 -0
- package/dist/utils/type-guards.js +13 -0
- package/oclif.manifest.json +8 -1
- package/package.json +9 -9
- package/dist/infra/process/constants.d.ts +0 -1
- package/dist/infra/process/constants.js +0 -1
- /package/dist/{commands → oclif/commands}/main.d.ts +0 -0
- /package/dist/{commands → oclif/commands}/mcp.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/command_not_found/handle-invalid-commands.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/command_not_found/handle-invalid-commands.js +0 -0
- /package/dist/{hooks → oclif/hooks}/error/clean-errors.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/error/clean-errors.js +0 -0
- /package/dist/{hooks → oclif/hooks}/init/update-notifier.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/init/update-notifier.js +0 -0
- /package/dist/{hooks → oclif/hooks}/init/welcome.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/init/welcome.js +0 -0
|
@@ -19,7 +19,7 @@ import { TomlMcpConfigWriter } from './toml-mcp-config-writer.js';
|
|
|
19
19
|
* - Safe uninstall: Only removes ByteRover's MCP server entry and rule content
|
|
20
20
|
*/
|
|
21
21
|
export class McpConnector {
|
|
22
|
-
|
|
22
|
+
connectorType = 'mcp';
|
|
23
23
|
fileService;
|
|
24
24
|
projectRoot;
|
|
25
25
|
ruleFileManager;
|
|
@@ -34,7 +34,7 @@ export class McpConnector {
|
|
|
34
34
|
projectRoot: options.projectRoot,
|
|
35
35
|
});
|
|
36
36
|
this.supportedAgents = Object.entries(AGENT_CONNECTOR_CONFIG)
|
|
37
|
-
.filter(([_, config]) => config.supported.includes(this.
|
|
37
|
+
.filter(([_, config]) => config.supported.includes(this.connectorType))
|
|
38
38
|
.map(([agent]) => agent);
|
|
39
39
|
}
|
|
40
40
|
getConfigPath(agent) {
|
|
@@ -66,7 +66,7 @@ export class McpConnector {
|
|
|
66
66
|
return this.installAutomatic(agent, config);
|
|
67
67
|
}
|
|
68
68
|
isSupported(agent) {
|
|
69
|
-
return agent in MCP_CONNECTOR_CONFIGS && AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.
|
|
69
|
+
return agent in MCP_CONNECTOR_CONFIGS && AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.connectorType);
|
|
70
70
|
}
|
|
71
71
|
async status(agent) {
|
|
72
72
|
if (!this.isSupported(agent)) {
|
|
@@ -254,7 +254,7 @@ export class McpConnector {
|
|
|
254
254
|
if (!rulesConfig) {
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
|
-
const ruleContent = await this.templateService.generateRuleContent(agent, this.
|
|
257
|
+
const ruleContent = await this.templateService.generateRuleContent(agent, this.connectorType);
|
|
258
258
|
await this.ruleFileManager.install(rulesConfig.filePath, rulesConfig.writeMode, ruleContent);
|
|
259
259
|
}
|
|
260
260
|
/**
|
|
@@ -17,7 +17,7 @@ type RulesConnectorOptions = {
|
|
|
17
17
|
* Manages the installation, uninstallation, and status of rule files.
|
|
18
18
|
*/
|
|
19
19
|
export declare class RulesConnector implements IConnector {
|
|
20
|
-
readonly
|
|
20
|
+
readonly connectorType: ConnectorType;
|
|
21
21
|
private readonly fileService;
|
|
22
22
|
private readonly projectRoot;
|
|
23
23
|
private readonly ruleFileManager;
|
|
@@ -7,7 +7,7 @@ import { RULES_CONNECTOR_CONFIGS } from './rules-connector-config.js';
|
|
|
7
7
|
* Manages the installation, uninstallation, and status of rule files.
|
|
8
8
|
*/
|
|
9
9
|
export class RulesConnector {
|
|
10
|
-
|
|
10
|
+
connectorType = 'rules';
|
|
11
11
|
fileService;
|
|
12
12
|
projectRoot;
|
|
13
13
|
ruleFileManager;
|
|
@@ -22,7 +22,7 @@ export class RulesConnector {
|
|
|
22
22
|
projectRoot: options.projectRoot,
|
|
23
23
|
});
|
|
24
24
|
this.supportedAgents = Object.entries(AGENT_CONNECTOR_CONFIG)
|
|
25
|
-
.filter(([_, config]) => config.supported.includes(this.
|
|
25
|
+
.filter(([_, config]) => config.supported.includes(this.connectorType))
|
|
26
26
|
.map(([agent]) => agent);
|
|
27
27
|
}
|
|
28
28
|
getConfigPath(agent) {
|
|
@@ -34,7 +34,7 @@ export class RulesConnector {
|
|
|
34
34
|
async install(agent) {
|
|
35
35
|
const config = RULES_CONNECTOR_CONFIGS[agent];
|
|
36
36
|
try {
|
|
37
|
-
const ruleContent = await this.templateService.generateRuleContent(agent, this.
|
|
37
|
+
const ruleContent = await this.templateService.generateRuleContent(agent, this.connectorType);
|
|
38
38
|
// Write the rule content to the file
|
|
39
39
|
await this.ruleFileManager.install(config.filePath, config.writeMode, ruleContent);
|
|
40
40
|
return {
|
|
@@ -54,7 +54,7 @@ export class RulesConnector {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
isSupported(agent) {
|
|
57
|
-
return AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.
|
|
57
|
+
return AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.connectorType);
|
|
58
58
|
}
|
|
59
59
|
async status(agent) {
|
|
60
60
|
const config = RULES_CONNECTOR_CONFIGS[agent];
|
|
@@ -8,6 +8,10 @@ import { BRV_RULE_MARKERS, BRV_RULE_TAG } from './constants.js';
|
|
|
8
8
|
* @returns The wrapped content with boundary markers.
|
|
9
9
|
*/
|
|
10
10
|
const wrapContentWithBoundaryMarkers = (content, agent, header) => {
|
|
11
|
+
if (agent === 'Windsurf' && header !== '') {
|
|
12
|
+
const parts = [header, BRV_RULE_MARKERS.START, content, '---', `${BRV_RULE_TAG} ${agent}`, BRV_RULE_MARKERS.END];
|
|
13
|
+
return parts.join('\n');
|
|
14
|
+
}
|
|
11
15
|
const parts = [BRV_RULE_MARKERS.START, header, content, '---', `${BRV_RULE_TAG} ${agent}`, BRV_RULE_MARKERS.END];
|
|
12
16
|
return parts.join('\n');
|
|
13
17
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SkillConnector } from './skill-connector.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SkillConnector } from './skill-connector.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope for skill connector configuration.
|
|
3
|
+
* - 'project': Path relative to project root
|
|
4
|
+
* - 'global': Path relative to user home directory
|
|
5
|
+
*/
|
|
6
|
+
export type SkillConfigScope = 'global' | 'project';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for agent-specific skill file directories.
|
|
9
|
+
*/
|
|
10
|
+
export type SkillConnectorConfig = {
|
|
11
|
+
/** Base directory for skill files (relative to scope root) */
|
|
12
|
+
basePath: string;
|
|
13
|
+
/** Whether path is relative to project root or home dir */
|
|
14
|
+
scope: SkillConfigScope;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Agent-specific skill connector configurations.
|
|
18
|
+
* Maps each supported agent to its skill directory path and scope.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SKILL_CONNECTOR_CONFIGS: {
|
|
21
|
+
readonly 'Claude Code': {
|
|
22
|
+
readonly basePath: ".claude/skills/byterover";
|
|
23
|
+
readonly scope: "project";
|
|
24
|
+
};
|
|
25
|
+
readonly Codex: {
|
|
26
|
+
readonly basePath: ".codex/skills/byterover";
|
|
27
|
+
readonly scope: "global";
|
|
28
|
+
};
|
|
29
|
+
readonly Cursor: {
|
|
30
|
+
readonly basePath: ".cursor/skills/byterover";
|
|
31
|
+
readonly scope: "project";
|
|
32
|
+
};
|
|
33
|
+
readonly 'Github Copilot': {
|
|
34
|
+
readonly basePath: ".github/skills/byterover";
|
|
35
|
+
readonly scope: "project";
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Type representing agents that have skill connector support.
|
|
40
|
+
*/
|
|
41
|
+
export type SkillSupportedAgent = keyof typeof SKILL_CONNECTOR_CONFIGS;
|
|
42
|
+
/**
|
|
43
|
+
* Names of the skill files written by the skill connector.
|
|
44
|
+
*/
|
|
45
|
+
export declare const SKILL_FILE_NAMES: readonly ["SKILL.md", "TROUBLESHOOTING.md", "WORKFLOWS.md"];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-specific skill connector configurations.
|
|
3
|
+
* Maps each supported agent to its skill directory path and scope.
|
|
4
|
+
*/
|
|
5
|
+
export const SKILL_CONNECTOR_CONFIGS = {
|
|
6
|
+
'Claude Code': {
|
|
7
|
+
basePath: '.claude/skills/byterover',
|
|
8
|
+
scope: 'project',
|
|
9
|
+
},
|
|
10
|
+
Codex: {
|
|
11
|
+
basePath: '.codex/skills/byterover',
|
|
12
|
+
scope: 'global',
|
|
13
|
+
},
|
|
14
|
+
Cursor: {
|
|
15
|
+
basePath: '.cursor/skills/byterover',
|
|
16
|
+
scope: 'project',
|
|
17
|
+
},
|
|
18
|
+
'Github Copilot': {
|
|
19
|
+
basePath: '.github/skills/byterover',
|
|
20
|
+
scope: 'project',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Names of the skill files written by the skill connector.
|
|
25
|
+
*/
|
|
26
|
+
export const SKILL_FILE_NAMES = ['SKILL.md', 'TROUBLESHOOTING.md', 'WORKFLOWS.md'];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Agent } from '../../../core/domain/entities/agent.js';
|
|
2
|
+
import type { ConnectorType } from '../../../core/domain/entities/connector-type.js';
|
|
3
|
+
import type { ConnectorInstallResult, ConnectorStatus, ConnectorUninstallResult } from '../../../core/interfaces/connectors/connector-types.js';
|
|
4
|
+
import type { IConnector } from '../../../core/interfaces/connectors/i-connector.js';
|
|
5
|
+
import type { IFileService } from '../../../core/interfaces/i-file-service.js';
|
|
6
|
+
import type { SkillSupportedAgent } from './skill-connector-config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Options for constructing SkillConnector.
|
|
9
|
+
*/
|
|
10
|
+
type SkillConnectorOptions = {
|
|
11
|
+
fileService: IFileService;
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Connector that integrates BRV with coding agents via skill files.
|
|
16
|
+
* Writes static markdown files (SKILL.md, TROUBLESHOOTING.md, WORKFLOWS.md)
|
|
17
|
+
* into an agent-specific subdirectory.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SkillConnector implements IConnector {
|
|
20
|
+
readonly connectorType: ConnectorType;
|
|
21
|
+
private readonly contentLoader;
|
|
22
|
+
private readonly fileService;
|
|
23
|
+
private readonly projectRoot;
|
|
24
|
+
private readonly supportedAgents;
|
|
25
|
+
constructor(options: SkillConnectorOptions);
|
|
26
|
+
getConfigPath(agent: Agent): string;
|
|
27
|
+
getSupportedAgents(): Agent[];
|
|
28
|
+
install(agent: Agent): Promise<ConnectorInstallResult>;
|
|
29
|
+
isSupported(agent: Agent): agent is SkillSupportedAgent;
|
|
30
|
+
status(agent: Agent): Promise<ConnectorStatus>;
|
|
31
|
+
uninstall(agent: Agent): Promise<ConnectorUninstallResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Get the full (absolute) path for skill file operations.
|
|
34
|
+
* - Project scope: relative to project root
|
|
35
|
+
* - Global scope: relative to os.homedir()
|
|
36
|
+
*/
|
|
37
|
+
private getFullPath;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { AGENT_CONNECTOR_CONFIG } from '../../../core/domain/entities/agent.js';
|
|
4
|
+
import { SKILL_CONNECTOR_CONFIGS, SKILL_FILE_NAMES } from './skill-connector-config.js';
|
|
5
|
+
import { SkillContentLoader } from './skill-content-loader.js';
|
|
6
|
+
/**
|
|
7
|
+
* Connector that integrates BRV with coding agents via skill files.
|
|
8
|
+
* Writes static markdown files (SKILL.md, TROUBLESHOOTING.md, WORKFLOWS.md)
|
|
9
|
+
* into an agent-specific subdirectory.
|
|
10
|
+
*/
|
|
11
|
+
export class SkillConnector {
|
|
12
|
+
connectorType = 'skill';
|
|
13
|
+
contentLoader;
|
|
14
|
+
fileService;
|
|
15
|
+
projectRoot;
|
|
16
|
+
supportedAgents;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.fileService = options.fileService;
|
|
19
|
+
this.projectRoot = options.projectRoot;
|
|
20
|
+
this.contentLoader = new SkillContentLoader(options.fileService);
|
|
21
|
+
this.supportedAgents = Object.entries(AGENT_CONNECTOR_CONFIG)
|
|
22
|
+
.filter(([_, config]) => config.supported.includes(this.connectorType))
|
|
23
|
+
.map(([agent]) => agent);
|
|
24
|
+
}
|
|
25
|
+
getConfigPath(agent) {
|
|
26
|
+
if (!this.isSupported(agent)) {
|
|
27
|
+
throw new Error(`Skill connector does not support agent: ${agent}`);
|
|
28
|
+
}
|
|
29
|
+
return SKILL_CONNECTOR_CONFIGS[agent].basePath;
|
|
30
|
+
}
|
|
31
|
+
getSupportedAgents() {
|
|
32
|
+
return this.supportedAgents;
|
|
33
|
+
}
|
|
34
|
+
async install(agent) {
|
|
35
|
+
if (!this.isSupported(agent)) {
|
|
36
|
+
return {
|
|
37
|
+
alreadyInstalled: false,
|
|
38
|
+
configPath: '',
|
|
39
|
+
message: `Skill connector does not support agent: ${agent}`,
|
|
40
|
+
success: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const config = SKILL_CONNECTOR_CONFIGS[agent];
|
|
44
|
+
const fullDir = this.getFullPath(config.basePath, config.scope);
|
|
45
|
+
try {
|
|
46
|
+
// Check if already installed
|
|
47
|
+
const skillFilePath = path.join(fullDir, SKILL_FILE_NAMES[0]);
|
|
48
|
+
if (await this.fileService.exists(skillFilePath)) {
|
|
49
|
+
return {
|
|
50
|
+
alreadyInstalled: true,
|
|
51
|
+
configPath: config.basePath,
|
|
52
|
+
message: `Skill connector is already installed for ${agent}`,
|
|
53
|
+
success: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Write all skill files
|
|
57
|
+
await Promise.all(SKILL_FILE_NAMES.map(async (fileName) => {
|
|
58
|
+
const content = await this.contentLoader.loadSkillFile(fileName);
|
|
59
|
+
const filePath = path.join(fullDir, fileName);
|
|
60
|
+
await this.fileService.write(content, filePath, 'overwrite');
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
alreadyInstalled: false,
|
|
64
|
+
configPath: fullDir,
|
|
65
|
+
message: `Skill connector installed for ${agent} (created ${fullDir}/)`,
|
|
66
|
+
success: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
alreadyInstalled: false,
|
|
72
|
+
configPath: config.basePath,
|
|
73
|
+
message: `Failed to install skill connector for ${agent}: ${error instanceof Error ? error.message : String(error)}`,
|
|
74
|
+
success: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
isSupported(agent) {
|
|
79
|
+
return agent in SKILL_CONNECTOR_CONFIGS && AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.connectorType);
|
|
80
|
+
}
|
|
81
|
+
async status(agent) {
|
|
82
|
+
if (!this.isSupported(agent)) {
|
|
83
|
+
return {
|
|
84
|
+
configExists: false,
|
|
85
|
+
configPath: '',
|
|
86
|
+
error: `Skill connector does not support agent: ${agent}`,
|
|
87
|
+
installed: false,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const config = SKILL_CONNECTOR_CONFIGS[agent];
|
|
91
|
+
const fullDir = this.getFullPath(config.basePath, config.scope);
|
|
92
|
+
try {
|
|
93
|
+
const skillFilePath = path.join(fullDir, SKILL_FILE_NAMES[0]);
|
|
94
|
+
const exists = await this.fileService.exists(skillFilePath);
|
|
95
|
+
return {
|
|
96
|
+
configExists: exists,
|
|
97
|
+
configPath: config.basePath,
|
|
98
|
+
installed: exists,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
configExists: false,
|
|
104
|
+
configPath: config.basePath,
|
|
105
|
+
error: error instanceof Error ? error.message : String(error),
|
|
106
|
+
installed: false,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async uninstall(agent) {
|
|
111
|
+
if (!this.isSupported(agent)) {
|
|
112
|
+
return {
|
|
113
|
+
configPath: '',
|
|
114
|
+
message: `Skill connector does not support agent: ${agent}`,
|
|
115
|
+
success: false,
|
|
116
|
+
wasInstalled: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const config = SKILL_CONNECTOR_CONFIGS[agent];
|
|
120
|
+
const fullDir = this.getFullPath(config.basePath, config.scope);
|
|
121
|
+
try {
|
|
122
|
+
const skillFilePath = path.join(fullDir, SKILL_FILE_NAMES[0]);
|
|
123
|
+
const exists = await this.fileService.exists(skillFilePath);
|
|
124
|
+
if (!exists) {
|
|
125
|
+
return {
|
|
126
|
+
configPath: config.basePath,
|
|
127
|
+
message: `Skill connector is not installed for ${agent}`,
|
|
128
|
+
success: true,
|
|
129
|
+
wasInstalled: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
await this.fileService.deleteDirectory(fullDir);
|
|
133
|
+
return {
|
|
134
|
+
configPath: config.basePath,
|
|
135
|
+
message: `Skill connector uninstalled for ${agent}`,
|
|
136
|
+
success: true,
|
|
137
|
+
wasInstalled: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
configPath: config.basePath,
|
|
143
|
+
message: `Failed to uninstall skill connector for ${agent}: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
success: false,
|
|
145
|
+
wasInstalled: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get the full (absolute) path for skill file operations.
|
|
151
|
+
* - Project scope: relative to project root
|
|
152
|
+
* - Global scope: relative to os.homedir()
|
|
153
|
+
*/
|
|
154
|
+
getFullPath(basePath, scope) {
|
|
155
|
+
if (scope === 'global') {
|
|
156
|
+
return path.join(os.homedir(), basePath);
|
|
157
|
+
}
|
|
158
|
+
return path.join(this.projectRoot, basePath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IFileService } from '../../../core/interfaces/i-file-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads static skill markdown files from the templates/skill/ directory.
|
|
4
|
+
* Uses the same import.meta.url path resolution pattern as FsTemplateLoader.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SkillContentLoader {
|
|
7
|
+
private readonly fileService;
|
|
8
|
+
private readonly skillDir;
|
|
9
|
+
constructor(fileService: IFileService);
|
|
10
|
+
/**
|
|
11
|
+
* Loads a skill file by name from the templates/skill/ directory.
|
|
12
|
+
*
|
|
13
|
+
* @param fileName - Name of the skill file (e.g., 'SKILL.md')
|
|
14
|
+
* @returns Promise resolving to the file content
|
|
15
|
+
* @throws Error if the file cannot be read
|
|
16
|
+
*/
|
|
17
|
+
loadSkillFile(fileName: string): Promise<string>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
/**
|
|
4
|
+
* Loads static skill markdown files from the templates/skill/ directory.
|
|
5
|
+
* Uses the same import.meta.url path resolution pattern as FsTemplateLoader.
|
|
6
|
+
*/
|
|
7
|
+
export class SkillContentLoader {
|
|
8
|
+
fileService;
|
|
9
|
+
skillDir;
|
|
10
|
+
constructor(fileService) {
|
|
11
|
+
this.fileService = fileService;
|
|
12
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
13
|
+
const currentDir = path.dirname(currentFilePath);
|
|
14
|
+
// Navigate from src/infra/connectors/skill/ to src/templates/skill/
|
|
15
|
+
this.skillDir = path.join(currentDir, '..', '..', '..', 'templates', 'skill');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Loads a skill file by name from the templates/skill/ directory.
|
|
19
|
+
*
|
|
20
|
+
* @param fileName - Name of the skill file (e.g., 'SKILL.md')
|
|
21
|
+
* @returns Promise resolving to the file content
|
|
22
|
+
* @throws Error if the file cannot be read
|
|
23
|
+
*/
|
|
24
|
+
async loadSkillFile(fileName) {
|
|
25
|
+
const fullPath = path.join(this.skillDir, fileName);
|
|
26
|
+
try {
|
|
27
|
+
return await this.fileService.read(fullPath);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Failed to load skill file '${fileName}': ${error instanceof Error ? error.message : String(error)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -11,6 +11,13 @@ export declare class FsFileService implements IFileService {
|
|
|
11
11
|
* @returns A promise that resolves when the file has been deleted.
|
|
12
12
|
*/
|
|
13
13
|
delete(filePath: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Deletes a directory and all its contents recursively.
|
|
16
|
+
*
|
|
17
|
+
* @param dirPath The path to the directory to delete.
|
|
18
|
+
* @returns A promise that resolves when the directory has been deleted.
|
|
19
|
+
*/
|
|
20
|
+
deleteDirectory(dirPath: string): Promise<void>;
|
|
14
21
|
/**
|
|
15
22
|
* Checks if a file exists at the specified path.
|
|
16
23
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, appendFile, copyFile, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { access, appendFile, copyFile, mkdir, readFile, rm, unlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
/**
|
|
4
4
|
* File service implementation using Node.js fs module.
|
|
@@ -30,6 +30,20 @@ export class FsFileService {
|
|
|
30
30
|
throw new Error(`Failed to delete file '${filePath}': ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Deletes a directory and all its contents recursively.
|
|
35
|
+
*
|
|
36
|
+
* @param dirPath The path to the directory to delete.
|
|
37
|
+
* @returns A promise that resolves when the directory has been deleted.
|
|
38
|
+
*/
|
|
39
|
+
async deleteDirectory(dirPath) {
|
|
40
|
+
try {
|
|
41
|
+
await rm(dirPath, { force: true, recursive: true });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new Error(`Failed to delete directory '${dirPath}': ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
33
47
|
/**
|
|
34
48
|
* Checks if a file exists at the specified path.
|
|
35
49
|
*
|
|
@@ -29,6 +29,14 @@ export async function waitForTaskResult(client, taskId, timeoutMs = 120_000) {
|
|
|
29
29
|
}, timeoutMs);
|
|
30
30
|
// Set up all event listeners
|
|
31
31
|
unsubscribers.push(
|
|
32
|
+
// Listen for connection state changes - fail fast on disconnect
|
|
33
|
+
client.onStateChange((state) => {
|
|
34
|
+
if (state === 'disconnected' && !completed) {
|
|
35
|
+
completed = true;
|
|
36
|
+
cleanup();
|
|
37
|
+
reject(new Error('Connection lost to ByteRover instance'));
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
32
40
|
// Listen for LLM response content
|
|
33
41
|
client.on('llmservice:response', (payload) => {
|
|
34
42
|
if (payload.taskId === taskId && payload.content) {
|
|
@@ -29,7 +29,6 @@ import { QueryExecutor } from '../core/executors/query-executor.js';
|
|
|
29
29
|
import { createTaskProcessor } from '../core/task-processor.js';
|
|
30
30
|
import { createTokenStore } from '../storage/token-store.js';
|
|
31
31
|
import { createTransportClient } from '../transport/transport-factory.js';
|
|
32
|
-
import { CURATE_MAX_CONCURRENT } from './constants.js';
|
|
33
32
|
import { createParentHeartbeat } from './parent-heartbeat.js';
|
|
34
33
|
import { TaskQueueManager } from './task-queue-manager.js';
|
|
35
34
|
// IPC types imported from ./ipc-types.ts
|
|
@@ -104,19 +103,37 @@ let eventForwarders = [];
|
|
|
104
103
|
// ============================================================================
|
|
105
104
|
/**
|
|
106
105
|
* Task queue manager handles:
|
|
107
|
-
* -
|
|
108
|
-
* -
|
|
106
|
+
* - Unified queue for all task types (curate, query)
|
|
107
|
+
* - Sequential FIFO execution (max 1 concurrent)
|
|
109
108
|
* - Task deduplication (same taskId can't be queued twice)
|
|
110
109
|
* - Cancel tasks from queue before processing
|
|
111
|
-
* - FIFO processing order
|
|
112
110
|
*/
|
|
113
111
|
const taskQueueManager = new TaskQueueManager({
|
|
114
|
-
|
|
112
|
+
maxConcurrent: 1, // Sequential FIFO execution for all tasks
|
|
115
113
|
onExecutorError(taskId, error) {
|
|
116
114
|
agentLog(`Executor error for task ${taskId}: ${error}`);
|
|
117
115
|
},
|
|
118
|
-
query: { maxConcurrent: Infinity },
|
|
119
116
|
});
|
|
117
|
+
/**
|
|
118
|
+
* Notify clients about dropped tasks and clear the queue.
|
|
119
|
+
* Extracted to avoid DRY violation across reinit, stop, and shutdown paths.
|
|
120
|
+
*/
|
|
121
|
+
function notifyQueuedTasksAboutDropAndClear(reason) {
|
|
122
|
+
const queuedTasks = taskQueueManager.getQueuedTasks();
|
|
123
|
+
if (queuedTasks.length > 0) {
|
|
124
|
+
const error = serializeTaskError(new AgentNotInitializedError(`Task dropped - ${reason}`));
|
|
125
|
+
if (transportClient) {
|
|
126
|
+
agentLog(`Notifying ${queuedTasks.length} queued task(s): ${reason}`);
|
|
127
|
+
for (const task of queuedTasks) {
|
|
128
|
+
transportClient.request('task:error', { error, taskId: task.taskId }).catch(logTransportError);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
agentLog(`Cannot notify ${queuedTasks.length} queued task(s): no transport client`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
taskQueueManager.clear();
|
|
136
|
+
}
|
|
120
137
|
/**
|
|
121
138
|
* Get Transport port from environment.
|
|
122
139
|
*/
|
|
@@ -318,7 +335,7 @@ const TASK_EXECUTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
|
318
335
|
function setupTaskExecutor() {
|
|
319
336
|
taskQueueManager.setExecutor(async (task) => {
|
|
320
337
|
const { taskId, type } = task;
|
|
321
|
-
const stats = taskQueueManager.getStats(
|
|
338
|
+
const stats = taskQueueManager.getStats();
|
|
322
339
|
agentLog(`Processing task ${taskId} (${type}), ${stats.queued} queued, ${stats.active} active`);
|
|
323
340
|
// Fix #2: Lazy initialization - if agent not ready, try to initialize now
|
|
324
341
|
// This enables processing tasks that arrived while init was failing
|
|
@@ -499,9 +516,8 @@ async function tryInitializeAgent(forceReinit = false) {
|
|
|
499
516
|
try {
|
|
500
517
|
// If forcing reinit, drain queue and stop existing agent first
|
|
501
518
|
if (forceReinit) {
|
|
502
|
-
// Drain task queue before reinit to prevent tasks executing with stale processor
|
|
503
519
|
agentLog('Draining task queue before reinit...');
|
|
504
|
-
|
|
520
|
+
notifyQueuedTasksAboutDropAndClear('credential/config change');
|
|
505
521
|
// Wait for active tasks to complete (with timeout)
|
|
506
522
|
await waitForActiveTasksToComplete(10_000);
|
|
507
523
|
await stopExistingAgentForReinit();
|
|
@@ -727,7 +743,7 @@ async function startAgent() {
|
|
|
727
743
|
}
|
|
728
744
|
const result = taskQueueManager.enqueue(data);
|
|
729
745
|
if (result.success) {
|
|
730
|
-
const stats = taskQueueManager.getStats(
|
|
746
|
+
const stats = taskQueueManager.getStats();
|
|
731
747
|
agentLog(`Task ${data.taskId} (${data.type}) queued at position ${result.position}, ${stats.queued} in queue`);
|
|
732
748
|
}
|
|
733
749
|
else if (result.reason === 'duplicate') {
|
|
@@ -857,8 +873,8 @@ async function startAgent() {
|
|
|
857
873
|
async function stopCipherAgent() {
|
|
858
874
|
// Cleanup event forwarders
|
|
859
875
|
cleanupAgentEventForwarding();
|
|
860
|
-
//
|
|
861
|
-
|
|
876
|
+
// Notify and clear task queue (can't process without agent)
|
|
877
|
+
notifyQueuedTasksAboutDropAndClear('agent stopped');
|
|
862
878
|
// Stop CipherAgent
|
|
863
879
|
if (cipherAgent) {
|
|
864
880
|
try {
|
|
@@ -1098,8 +1114,8 @@ async function stopAgent() {
|
|
|
1098
1114
|
// Stop polling and heartbeat first
|
|
1099
1115
|
stopCredentialsPolling();
|
|
1100
1116
|
parentHeartbeat?.stop();
|
|
1101
|
-
//
|
|
1102
|
-
|
|
1117
|
+
// Notify and clear task queue
|
|
1118
|
+
notifyQueuedTasksAboutDropAndClear('agent shutting down');
|
|
1103
1119
|
// Cleanup event forwarders before stopping agent
|
|
1104
1120
|
cleanupAgentEventForwarding();
|
|
1105
1121
|
// Stop CipherAgent first
|