@ai-setting/roy-agent-cli 1.0.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 +126 -0
- package/dist/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
- package/package.json +91 -0
- package/src/bin/roy.ts +12 -0
- package/src/cli.ts +101 -0
- package/src/commands/act.ts +480 -0
- package/src/commands/commands-add.ts +110 -0
- package/src/commands/commands-dirs.ts +70 -0
- package/src/commands/commands-info.ts +90 -0
- package/src/commands/commands-list.ts +161 -0
- package/src/commands/commands-remove.ts +147 -0
- package/src/commands/commands.ts +55 -0
- package/src/commands/config/config-service.test.ts +449 -0
- package/src/commands/config/config-service.ts +312 -0
- package/src/commands/config/deep-merge.test.ts +168 -0
- package/src/commands/config/deep-merge.ts +63 -0
- package/src/commands/config/export.ts +97 -0
- package/src/commands/config/filter-history-e2e.test.ts +141 -0
- package/src/commands/config/import-preserve-refs.test.ts +212 -0
- package/src/commands/config/import.ts +119 -0
- package/src/commands/config/index.ts +35 -0
- package/src/commands/config/list.ts +281 -0
- package/src/commands/config/roy-config-e2e.test.ts +297 -0
- package/src/commands/config/types.ts +54 -0
- package/src/commands/debug/index.ts +38 -0
- package/src/commands/debug/log.test.ts +233 -0
- package/src/commands/debug/log.ts +123 -0
- package/src/commands/debug/span.test.ts +297 -0
- package/src/commands/debug/span.ts +211 -0
- package/src/commands/debug/trace.test.ts +254 -0
- package/src/commands/debug/trace.ts +140 -0
- package/src/commands/eventsource/add.ts +133 -0
- package/src/commands/eventsource/index.ts +48 -0
- package/src/commands/eventsource/list.ts +194 -0
- package/src/commands/eventsource/remove.ts +95 -0
- package/src/commands/eventsource/start.ts +103 -0
- package/src/commands/eventsource/status.ts +185 -0
- package/src/commands/eventsource/stop.ts +89 -0
- package/src/commands/index.ts +22 -0
- package/src/commands/input-handler.test.ts +76 -0
- package/src/commands/input-handler.ts +43 -0
- package/src/commands/interactive-esc.test.ts +254 -0
- package/src/commands/interactive.shutdown.test.ts +122 -0
- package/src/commands/interactive.test.ts +221 -0
- package/src/commands/interactive.ts +1015 -0
- package/src/commands/lsp/check.ts +92 -0
- package/src/commands/lsp/index.ts +32 -0
- package/src/commands/lsp/install.ts +126 -0
- package/src/commands/lsp/list.ts +64 -0
- package/src/commands/mcp/index.ts +27 -0
- package/src/commands/mcp/list.ts +116 -0
- package/src/commands/mcp/reload.ts +70 -0
- package/src/commands/mcp/tools.ts +121 -0
- package/src/commands/memory/extract-e2e.test.ts +388 -0
- package/src/commands/memory/index.ts +11 -0
- package/src/commands/memory/memory-simplified.test.ts +58 -0
- package/src/commands/memory/memory.ts +25 -0
- package/src/commands/memory/organize.ts +300 -0
- package/src/commands/memory/recall.test.ts +120 -0
- package/src/commands/memory/recall.ts +88 -0
- package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
- package/src/commands/memory/record-prompt-component.test.ts +343 -0
- package/src/commands/memory/record.test.ts +92 -0
- package/src/commands/memory/record.ts +332 -0
- package/src/commands/plugin.test.ts +292 -0
- package/src/commands/plugin.ts +267 -0
- package/src/commands/sessions/active.ts +96 -0
- package/src/commands/sessions/add-message.ts +96 -0
- package/src/commands/sessions/checkpoints.ts +154 -0
- package/src/commands/sessions/compact.test.ts +215 -0
- package/src/commands/sessions/compact.ts +269 -0
- package/src/commands/sessions/delete.ts +236 -0
- package/src/commands/sessions/get.ts +165 -0
- package/src/commands/sessions/grep.ts +233 -0
- package/src/commands/sessions/index.ts +95 -0
- package/src/commands/sessions/list.ts +210 -0
- package/src/commands/sessions/messages.test.ts +333 -0
- package/src/commands/sessions/messages.ts +248 -0
- package/src/commands/sessions/mock.ts +194 -0
- package/src/commands/sessions/new.ts +82 -0
- package/src/commands/sessions/rename.ts +98 -0
- package/src/commands/shared/event-handler.ts +213 -0
- package/src/commands/shared/event-message-formatter.ts +295 -0
- package/src/commands/shared/index.ts +11 -0
- package/src/commands/shared/query-executor.test.ts +434 -0
- package/src/commands/shared/query-executor.ts +324 -0
- package/src/commands/shared/repl-engine.test.ts +354 -0
- package/src/commands/shared/session-manager.test.ts +212 -0
- package/src/commands/shared/session-manager.ts +114 -0
- package/src/commands/skills/get.ts +90 -0
- package/src/commands/skills/index.ts +39 -0
- package/src/commands/skills/list.ts +129 -0
- package/src/commands/skills/reload.ts +59 -0
- package/src/commands/skills/search.ts +132 -0
- package/src/commands/skills/show-config.ts +93 -0
- package/src/commands/tasks/complete.ts +92 -0
- package/src/commands/tasks/create.ts +118 -0
- package/src/commands/tasks/delete.ts +86 -0
- package/src/commands/tasks/get.ts +116 -0
- package/src/commands/tasks/index.ts +53 -0
- package/src/commands/tasks/list.ts +140 -0
- package/src/commands/tasks/operations.ts +120 -0
- package/src/commands/tasks/update.ts +122 -0
- package/src/commands/tools/exec-tool.ts +128 -0
- package/src/commands/tools/get.ts +114 -0
- package/src/commands/tools/index.ts +35 -0
- package/src/commands/tools/list.ts +107 -0
- package/src/commands/tools/shared/index.ts +7 -0
- package/src/commands/tools/shared/schema-helper.ts +111 -0
- package/src/commands/workflow/commands/add.ts +315 -0
- package/src/commands/workflow/commands/get.ts +193 -0
- package/src/commands/workflow/commands/list.ts +137 -0
- package/src/commands/workflow/commands/nodes.ts +528 -0
- package/src/commands/workflow/commands/remove.ts +94 -0
- package/src/commands/workflow/commands/run.ts +398 -0
- package/src/commands/workflow/commands/status.ts +147 -0
- package/src/commands/workflow/commands/stop.ts +91 -0
- package/src/commands/workflow/commands/update.ts +130 -0
- package/src/commands/workflow/commands/validate.ts +139 -0
- package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
- package/src/commands/workflow/index.ts +65 -0
- package/src/commands/workflow/renderers.ts +358 -0
- package/src/commands/workflow/validators/index.ts +8 -0
- package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
- package/src/commands/workflow/validators/node-validator.ts +125 -0
- package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
- package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
- package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
- package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
- package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
- package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
- package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
- package/src/commands/workflow/validators/types.ts +78 -0
- package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
- package/src/commands/workflow/validators/workflow-validator.ts +320 -0
- package/src/index.ts +19 -0
- package/src/plugin/apply.ts +103 -0
- package/src/plugin/discover.ts +219 -0
- package/src/plugin/index.ts +45 -0
- package/src/plugin/registry.ts +272 -0
- package/src/plugin/types.ts +165 -0
- package/src/services/context-handler.service.test.ts +501 -0
- package/src/services/context-handler.service.ts +372 -0
- package/src/services/environment.service.commands-prompt.test.ts +167 -0
- package/src/services/environment.service.ts +656 -0
- package/src/services/output.service.test.ts +92 -0
- package/src/services/output.service.ts +122 -0
- package/src/services/quiet-mode.service.test.ts +114 -0
- package/src/services/quiet-mode.service.ts +81 -0
- package/src/services/stream-output.service.test.ts +214 -0
- package/src/services/stream-output.service.ts +323 -0
- package/src/util/which.test.ts +101 -0
- package/src/util/which.ts +55 -0
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-setting/roy-agent-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for roy-agent - Non-interactive command execution",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"roy": "./dist/bin/roy"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.mjs",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./cli": {
|
|
18
|
+
"import": "./dist/cli.js",
|
|
19
|
+
"types": "./dist/cli.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./bin": {
|
|
22
|
+
"import": "./dist/bin/roy.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist/*.js",
|
|
27
|
+
"dist/*.d.ts",
|
|
28
|
+
"dist/**/*.js",
|
|
29
|
+
"dist/**/*.d.ts",
|
|
30
|
+
"dist/bin/**/*",
|
|
31
|
+
"src/**/*.ts",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"postinstall": "bun run scripts/lsp-check.ts",
|
|
36
|
+
"build": "bun run scripts/build.ts",
|
|
37
|
+
"build:dev": "bun run scripts/build.ts --dev",
|
|
38
|
+
"build:prod": "bun run scripts/build.ts --production",
|
|
39
|
+
"build:ts": "tsc",
|
|
40
|
+
"build:single": "bun run scripts/build.ts --single",
|
|
41
|
+
"build:dev:single": "bun run scripts/build.ts --single --dev",
|
|
42
|
+
"build:prod:single": "bun run scripts/build.ts --single --production",
|
|
43
|
+
"typecheck": "tsc --noEmit --project tsconfig.typecheck.json",
|
|
44
|
+
"dev": "bun run src/bin/roy.ts",
|
|
45
|
+
"test": "bun test",
|
|
46
|
+
"cli": "bun run src/bin/roy.ts",
|
|
47
|
+
"prepublishOnly": "PRODUCTION=1 bun run scripts/build.ts --skip-install"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@ai-setting/roy-agent-coder-harness": "workspace:*",
|
|
51
|
+
"@ai-setting/roy-agent-core": "workspace:*",
|
|
52
|
+
"chalk": "^5.6.2",
|
|
53
|
+
"commander": "^14.0.3",
|
|
54
|
+
"pyright": "^1.1.409",
|
|
55
|
+
"typescript-language-server": "^5.1.3",
|
|
56
|
+
"vscode-css-languageserver-bin": "^1.4.0",
|
|
57
|
+
"vscode-html-languageserver-bin": "^1.4.0",
|
|
58
|
+
"vscode-json-languageserver": "^1.3.4",
|
|
59
|
+
"yargs": "^17.7.2",
|
|
60
|
+
"zod-to-json-schema": "^3.25.2"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^20.10.0",
|
|
64
|
+
"@types/yargs": "^17.0.32",
|
|
65
|
+
"typescript": "^5.3.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0"
|
|
69
|
+
},
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "https://github.com/ai-setting/roy-agent.git",
|
|
73
|
+
"directory": "packages/cli"
|
|
74
|
+
},
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"registry": "https://registry.npmjs.org",
|
|
77
|
+
"access": "public"
|
|
78
|
+
},
|
|
79
|
+
"keywords": [
|
|
80
|
+
"roy-agent",
|
|
81
|
+
"agent",
|
|
82
|
+
"ai",
|
|
83
|
+
"cli",
|
|
84
|
+
"command-line"
|
|
85
|
+
],
|
|
86
|
+
"author": "ai-setting",
|
|
87
|
+
"license": "MIT",
|
|
88
|
+
"bugs": {
|
|
89
|
+
"url": "https://github.com/ai-setting/roy-agent/issues"
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/bin/roy.ts
ADDED
package/src/cli.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI Main Entry
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// @ts-ignore - yargs types issue with bundler module resolution
|
|
6
|
+
import yargsLib from "yargs";
|
|
7
|
+
import { hideBin } from "yargs/helpers";
|
|
8
|
+
import { CliQuietModeService } from "./services/quiet-mode.service";
|
|
9
|
+
import { ActCommand } from "./commands/act";
|
|
10
|
+
import { InteractiveCommand } from "./commands/interactive";
|
|
11
|
+
import { SessionsCommand } from "./commands/sessions/index";
|
|
12
|
+
import { TasksCommand } from "./commands/tasks/index";
|
|
13
|
+
import { SkillsCommand } from "./commands/skills/index";
|
|
14
|
+
import { CommandsCommand } from "./commands/commands";
|
|
15
|
+
import { ConfigCommand } from "./commands/config";
|
|
16
|
+
import { McpCommand } from "./commands/mcp/index";
|
|
17
|
+
import { ToolsCommand } from "./commands/tools/index";
|
|
18
|
+
import { MemoryCommand } from "./commands/memory/index";
|
|
19
|
+
import { EventSourceCommand } from "./commands/eventsource/index";
|
|
20
|
+
import { DebugCommand } from "./commands/debug/index";
|
|
21
|
+
import { WorkflowCommand } from "./commands/workflow/index";
|
|
22
|
+
import { LspCommand } from "./commands/lsp/index";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 全局 quiet 模式 middleware
|
|
26
|
+
*
|
|
27
|
+
* 使用统一的 CliQuietModeService 管理 quiet 模式状态
|
|
28
|
+
* 与 LogTraceComponent 的 setQuietMode 机制保持一致
|
|
29
|
+
*/
|
|
30
|
+
function quietModeMiddleware(argv: Record<string, unknown>): void {
|
|
31
|
+
const quietService = CliQuietModeService.getInstance();
|
|
32
|
+
quietService.setQuietFromArgv(argv);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Plugin 帮助信息
|
|
37
|
+
*/
|
|
38
|
+
const PLUGIN_HELP = `
|
|
39
|
+
Plugins:
|
|
40
|
+
coder-harness plugins:
|
|
41
|
+
lsp LSP code intelligence (diagnostics, completions)
|
|
42
|
+
code-check External command code checking (linter + type check)
|
|
43
|
+
reminder Max iterations reminder for ReAct loop
|
|
44
|
+
|
|
45
|
+
component plugins:
|
|
46
|
+
task-tag Task tag suggestion and similar task experience
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
--plugin lsp Enable LSP plugin
|
|
50
|
+
--plugin code-check Enable external command checking
|
|
51
|
+
--plugin reminder Enable iteration reminder
|
|
52
|
+
--plugin lsp code-check reminder Enable multiple coder plugins
|
|
53
|
+
--plugin task-tag Enable task-tag plugin
|
|
54
|
+
--plugin task-tag lsp Enable both plugins`;
|
|
55
|
+
|
|
56
|
+
export async function runCli(): Promise<void> {
|
|
57
|
+
const pkg = await import("../package.json", { with: { type: "json" } });
|
|
58
|
+
|
|
59
|
+
// @ts-ignore - yargs types issue
|
|
60
|
+
const yargs = yargsLib as any;
|
|
61
|
+
|
|
62
|
+
await yargs(hideBin(process.argv))
|
|
63
|
+
.scriptName("roy")
|
|
64
|
+
.version(pkg.default.version)
|
|
65
|
+
// 全局 quiet 选项
|
|
66
|
+
.option("quiet", {
|
|
67
|
+
alias: "q",
|
|
68
|
+
type: "boolean",
|
|
69
|
+
description: "Suppress all log output",
|
|
70
|
+
global: true, // 所有子命令都继承此选项
|
|
71
|
+
})
|
|
72
|
+
// 全局 plugin 选项
|
|
73
|
+
.option("plugin", {
|
|
74
|
+
alias: "p",
|
|
75
|
+
type: "string",
|
|
76
|
+
array: true,
|
|
77
|
+
description: "Enable plugin (e.g., --plugin lsp, --plugin task-tag)",
|
|
78
|
+
global: true,
|
|
79
|
+
})
|
|
80
|
+
// 全局 middleware,在命令执行前设置 quiet 模式
|
|
81
|
+
.middleware(quietModeMiddleware, true)
|
|
82
|
+
.command(ActCommand)
|
|
83
|
+
.command(InteractiveCommand)
|
|
84
|
+
.command(SessionsCommand)
|
|
85
|
+
.command(TasksCommand)
|
|
86
|
+
.command(SkillsCommand)
|
|
87
|
+
.command(CommandsCommand)
|
|
88
|
+
.command(ConfigCommand)
|
|
89
|
+
.command(McpCommand)
|
|
90
|
+
.command(ToolsCommand)
|
|
91
|
+
.command(MemoryCommand)
|
|
92
|
+
.command(EventSourceCommand)
|
|
93
|
+
.command(DebugCommand)
|
|
94
|
+
.command(WorkflowCommand)
|
|
95
|
+
.command(LspCommand)
|
|
96
|
+
.demandCommand()
|
|
97
|
+
.help()
|
|
98
|
+
.alias("help", "h")
|
|
99
|
+
.epilog(PLUGIN_HELP)
|
|
100
|
+
.parse();
|
|
101
|
+
}
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Act Command
|
|
3
|
+
*
|
|
4
|
+
* 自然语言交互命令 - CLI 核心命令
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* - 支持在指定 session 上继续对话(--session)
|
|
8
|
+
* - 支持继续上次会话(--continue)
|
|
9
|
+
* - 支持上下文阈值自动压缩
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { CommandModule } from "yargs";
|
|
13
|
+
import { EnvironmentService } from "../services/environment.service";
|
|
14
|
+
import { OutputService } from "../services/output.service";
|
|
15
|
+
import { StreamOutputService } from "../services/stream-output.service";
|
|
16
|
+
import { ContextHandlerService } from "../services/context-handler.service";
|
|
17
|
+
import { ContextError, ErrorCodes, getTracerProvider } from "@ai-setting/roy-agent-core";
|
|
18
|
+
import { CliQuietModeService } from "../services/quiet-mode.service";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 生成唯一的 trace ID
|
|
22
|
+
*/
|
|
23
|
+
function generateTraceId(): string {
|
|
24
|
+
const timestamp = Date.now().toString(36);
|
|
25
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
26
|
+
return `req_${timestamp}_${random}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ActOptions {
|
|
30
|
+
message?: string;
|
|
31
|
+
continue?: boolean;
|
|
32
|
+
session?: string;
|
|
33
|
+
model?: string;
|
|
34
|
+
env?: string;
|
|
35
|
+
config?: string;
|
|
36
|
+
quiet?: boolean;
|
|
37
|
+
reasoning?: boolean;
|
|
38
|
+
toolCalls?: boolean;
|
|
39
|
+
toolResults?: boolean;
|
|
40
|
+
plugins?: string[]; // 启用的 coder 插件列表,如 ["lsp", "code-check", "reminder"]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ActCommand - 自然语言交互命令
|
|
45
|
+
*
|
|
46
|
+
* 通过自然语言驱动整个操作系统
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* # 在指定 session 上继续对话
|
|
50
|
+
* roy act "继续开发" --session s_abc123
|
|
51
|
+
*
|
|
52
|
+
* # 继续上次会话
|
|
53
|
+
* roy act "继续" --continue
|
|
54
|
+
*
|
|
55
|
+
* # 组合使用
|
|
56
|
+
* roy act "继续" --continue --reasoning
|
|
57
|
+
*
|
|
58
|
+
* # 启用 coder 插件
|
|
59
|
+
* roy act "帮我重构这段代码" --plugin lsp --plugin code-check --plugin reminder
|
|
60
|
+
*/
|
|
61
|
+
export const ActCommand: CommandModule<object, ActOptions> = {
|
|
62
|
+
command: "act [message]",
|
|
63
|
+
describe: "自然语言交互 - 通过自然语言驱动整个操作系统",
|
|
64
|
+
|
|
65
|
+
builder: (yargs) =>
|
|
66
|
+
yargs
|
|
67
|
+
.positional("message", {
|
|
68
|
+
describe: "要执行的消息",
|
|
69
|
+
type: "string",
|
|
70
|
+
})
|
|
71
|
+
.option("continue", {
|
|
72
|
+
alias: "c",
|
|
73
|
+
describe: "继续上次会话",
|
|
74
|
+
type: "boolean",
|
|
75
|
+
default: false,
|
|
76
|
+
})
|
|
77
|
+
.option("session", {
|
|
78
|
+
alias: "s",
|
|
79
|
+
describe: "指定会话 ID",
|
|
80
|
+
type: "string",
|
|
81
|
+
})
|
|
82
|
+
.option("model", {
|
|
83
|
+
describe: "使用的模型",
|
|
84
|
+
type: "string",
|
|
85
|
+
})
|
|
86
|
+
.option("config", {
|
|
87
|
+
alias: "C",
|
|
88
|
+
describe: "配置文件路径",
|
|
89
|
+
type: "string",
|
|
90
|
+
})
|
|
91
|
+
.option("quiet", {
|
|
92
|
+
alias: "q",
|
|
93
|
+
describe: "安静模式:只输出最终结果(默认开启)",
|
|
94
|
+
type: "boolean",
|
|
95
|
+
default: true,
|
|
96
|
+
})
|
|
97
|
+
.option("reasoning", {
|
|
98
|
+
alias: "r",
|
|
99
|
+
describe: "显示 AI 思考过程",
|
|
100
|
+
type: "boolean",
|
|
101
|
+
default: false,
|
|
102
|
+
})
|
|
103
|
+
.option("tool-calls", {
|
|
104
|
+
describe: "显示工具调用",
|
|
105
|
+
type: "boolean",
|
|
106
|
+
default: false,
|
|
107
|
+
})
|
|
108
|
+
.option("tool-results", {
|
|
109
|
+
describe: "显示工具执行结果",
|
|
110
|
+
type: "boolean",
|
|
111
|
+
default: false,
|
|
112
|
+
})
|
|
113
|
+
.option("plugin", {
|
|
114
|
+
alias: "p",
|
|
115
|
+
describe: "启用 coder plugin (lsp, code-check, reminder)",
|
|
116
|
+
type: "array",
|
|
117
|
+
string: true,
|
|
118
|
+
}),
|
|
119
|
+
|
|
120
|
+
async handler(args) {
|
|
121
|
+
// 设置日志 quiet 模式
|
|
122
|
+
const quiet = args.quiet ?? true;
|
|
123
|
+
CliQuietModeService.getInstance().setQuiet(quiet);
|
|
124
|
+
|
|
125
|
+
const output = new OutputService();
|
|
126
|
+
output.configure({ quiet });
|
|
127
|
+
|
|
128
|
+
// 验证消息
|
|
129
|
+
if (!args.message) {
|
|
130
|
+
output.error("请提供要执行的消息,使用方式:roy act <消息>");
|
|
131
|
+
output.info("示例:roy act \"帮我写一个 hello world\"");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const envService = new EnvironmentService(output);
|
|
136
|
+
|
|
137
|
+
// Plugin adapter dispose 函数(需要在 finally 中调用)
|
|
138
|
+
let pluginAdapterDispose: (() => Promise<void>) | null = null;
|
|
139
|
+
// LSP Manager dispose 函数
|
|
140
|
+
let lspManagerDispose: (() => Promise<void>) | null = null;
|
|
141
|
+
|
|
142
|
+
// ========== 解析插件参数 ==========
|
|
143
|
+
const rawPlugins = args.plugin;
|
|
144
|
+
const pluginNames: string[] = Array.isArray(rawPlugins)
|
|
145
|
+
? rawPlugins
|
|
146
|
+
: rawPlugins
|
|
147
|
+
? [rawPlugins]
|
|
148
|
+
: [];
|
|
149
|
+
|
|
150
|
+
// coder-harness 插件名称集合
|
|
151
|
+
const CODER_HARNESS_PLUGINS = new Set(["lsp", "code-check", "reminder"]);
|
|
152
|
+
|
|
153
|
+
// 分离 coder-harness 插件和其他插件
|
|
154
|
+
const coderPluginNames: string[] = [];
|
|
155
|
+
const otherPluginNames: string[] = [];
|
|
156
|
+
|
|
157
|
+
for (const plugin of pluginNames) {
|
|
158
|
+
const name = plugin.split(":")[0];
|
|
159
|
+
if (CODER_HARNESS_PLUGINS.has(name)) {
|
|
160
|
+
coderPluginNames.push(plugin);
|
|
161
|
+
} else {
|
|
162
|
+
otherPluginNames.push(plugin);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 日志输出加载的插件
|
|
167
|
+
if (otherPluginNames.length > 0 && !quiet) {
|
|
168
|
+
output.info(`📌 加载插件: ${otherPluginNames.join(", ")}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
if (!quiet) {
|
|
173
|
+
output.info("初始化环境...");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 创建环境
|
|
177
|
+
await envService.create({
|
|
178
|
+
envName: args.env,
|
|
179
|
+
configPath: args.config,
|
|
180
|
+
plugins: [...otherPluginNames, ...coderPluginNames],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const env = envService.getEnvironment();
|
|
184
|
+
const sessionComponent = envService.getSession();
|
|
185
|
+
|
|
186
|
+
if (!sessionComponent) {
|
|
187
|
+
output.error("SessionComponent not available");
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ========== Session 处理 ==========
|
|
192
|
+
let sessionId = args.session;
|
|
193
|
+
let isNewSession = false;
|
|
194
|
+
|
|
195
|
+
// 如果指定了 --continue,获取上次会话
|
|
196
|
+
if (args.continue && !sessionId) {
|
|
197
|
+
const activeSession = await sessionComponent.getActiveSession();
|
|
198
|
+
if (activeSession) {
|
|
199
|
+
sessionId = activeSession.id;
|
|
200
|
+
if (!quiet) {
|
|
201
|
+
output.info(`继续会话: ${activeSession.title} (${sessionId})`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
output.warn("没有找到上次会话,将创建新会话");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 如果指定了 session,设置为 active 并获取会话信息
|
|
209
|
+
if (sessionId) {
|
|
210
|
+
await sessionComponent.setActiveSession(sessionId);
|
|
211
|
+
const session = await sessionComponent.get(sessionId);
|
|
212
|
+
if (session) {
|
|
213
|
+
if (!quiet) {
|
|
214
|
+
output.info(`会话: ${session.title} (${sessionId})`);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
output.warn(`会话不存在: ${sessionId},将创建新会话`);
|
|
218
|
+
sessionId = undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 如果没有 session,创建一个新会话
|
|
223
|
+
if (!sessionId) {
|
|
224
|
+
const newSession = await sessionComponent.create({
|
|
225
|
+
title: `Session - ${new Date().toLocaleString("zh-CN")}`,
|
|
226
|
+
});
|
|
227
|
+
sessionId = newSession.id;
|
|
228
|
+
isNewSession = true;
|
|
229
|
+
await sessionComponent.setActiveSession(sessionId);
|
|
230
|
+
if (!quiet) {
|
|
231
|
+
output.info(`创建新会话: ${newSession.title} (${sessionId})`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 初始化 SummaryAgent(用于 compact)
|
|
236
|
+
const llmComponent = envService.getLLM();
|
|
237
|
+
if (!env) {
|
|
238
|
+
output.error("Environment not available");
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
const promptComponent = env.getComponent("prompt") as any;
|
|
242
|
+
if (llmComponent && promptComponent) {
|
|
243
|
+
sessionComponent.setSummaryComponents(promptComponent, llmComponent);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ========== Plugin 加载(coder-harness 插件) ==========
|
|
247
|
+
// 组件插件已通过 envService.create 加载
|
|
248
|
+
if (coderPluginNames.length > 0) {
|
|
249
|
+
try {
|
|
250
|
+
// 使用 globalHookManager(ToolComponent 内部使用)
|
|
251
|
+
const { globalHookManager } = await import("@ai-setting/roy-agent-core");
|
|
252
|
+
const { createPluginHookAdapter, createGlobalLSPManager } = await import("@ai-setting/roy-agent-coder-harness");
|
|
253
|
+
|
|
254
|
+
const adapter = createPluginHookAdapter(globalHookManager as any, {
|
|
255
|
+
enableLSP: coderPluginNames.includes("lsp"),
|
|
256
|
+
enableCodeCheck: coderPluginNames.includes("code-check"),
|
|
257
|
+
enableReminder: coderPluginNames.includes("reminder"),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// 初始化插件(启动 LSP 预加载等)
|
|
261
|
+
await adapter.initialize();
|
|
262
|
+
|
|
263
|
+
pluginAdapterDispose = () => adapter.dispose();
|
|
264
|
+
|
|
265
|
+
// 过滤有效的插件名称
|
|
266
|
+
const enabledPlugins = coderPluginNames.filter(
|
|
267
|
+
(p) => CODER_HARNESS_PLUGINS.has(p.split(":")[0])
|
|
268
|
+
);
|
|
269
|
+
if (enabledPlugins.length > 0) {
|
|
270
|
+
console.log(`已启用插件: ${enabledPlugins.join(", ")}`)
|
|
271
|
+
output.success(`✅ 已启用插件: ${enabledPlugins.join(", ")}`);
|
|
272
|
+
|
|
273
|
+
// 显示 LSP 预加载信息
|
|
274
|
+
if (coderPluginNames.includes("lsp") && !quiet) {
|
|
275
|
+
output.info(`🔧 LSP 服务预加载中,首次诊断可能需要等待...`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 如果启用了 LSP 插件,预热 LSP 服务器
|
|
280
|
+
if (coderPluginNames.includes("lsp")) {
|
|
281
|
+
try {
|
|
282
|
+
// 加载 LSP 配置
|
|
283
|
+
const { createLSPConfigLoader } = await import("@ai-setting/roy-agent-coder-harness");
|
|
284
|
+
const configLoader = createLSPConfigLoader();
|
|
285
|
+
const lspConfig = configLoader.load();
|
|
286
|
+
|
|
287
|
+
if (!quiet) {
|
|
288
|
+
output.info("正在预热 LSP 服务器...");
|
|
289
|
+
if (lspConfig.preload) {
|
|
290
|
+
output.info("📋 使用配置文件中的预加载设置");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const lspManager = createGlobalLSPManager({
|
|
294
|
+
idleTimeout: lspConfig.idleTimeout ?? 300000, // 使用配置中的值
|
|
295
|
+
maxConnections: lspConfig.maxConnections ?? 10,
|
|
296
|
+
autoDownload: lspConfig.autoInstall ?? false,
|
|
297
|
+
preloadLanguages: lspConfig.preloadLanguages, // 使用配置中的预加载语言列表
|
|
298
|
+
});
|
|
299
|
+
// 注册 shutdown handler 确保进程退出时清理
|
|
300
|
+
lspManager.registerShutdownHandler();
|
|
301
|
+
// 保存 dispose 函数以便在退出时调用
|
|
302
|
+
lspManagerDispose = () => lspManager.dispose();
|
|
303
|
+
// 预热所有服务器(异步,不阻塞)
|
|
304
|
+
// 如果配置了 preload: true 或者明确启用了 lsp 插件,就预热
|
|
305
|
+
if (lspConfig.preload || coderPluginNames.includes("lsp")) {
|
|
306
|
+
lspManager.prewarm().then(() => {
|
|
307
|
+
if (!quiet) {
|
|
308
|
+
output.success("✅ LSP 服务器预热完成");
|
|
309
|
+
}
|
|
310
|
+
}).catch((err) => {
|
|
311
|
+
if (!quiet) {
|
|
312
|
+
output.warn(`⚠ LSP 预热部分失败: ${err.message}`);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
output.warn(`⚠ LSP 预热失败: ${error}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch (error) {
|
|
321
|
+
output.error(`❌ 加载 coder-harness 插件失败: ${error}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 创建流式输出服务
|
|
326
|
+
const streamService = new StreamOutputService({
|
|
327
|
+
showReasoning: args.reasoning,
|
|
328
|
+
showToolCalls: args.toolCalls,
|
|
329
|
+
showToolResults: args.toolResults,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// 设置 context window 配置(如果有 LLM component)
|
|
333
|
+
if (llmComponent) {
|
|
334
|
+
const contextConfig = llmComponent.getContextThresholdConfig("minimax");
|
|
335
|
+
streamService.setContextInfo(contextConfig.contextWindow, contextConfig.contextWindow * contextConfig.thresholdRatio);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 订阅 LLM 事件和上下文事件进行流式输出
|
|
339
|
+
const unsubscribe = env.subscribeTo(
|
|
340
|
+
[
|
|
341
|
+
"llm.start",
|
|
342
|
+
"llm.text",
|
|
343
|
+
"llm.reasoning",
|
|
344
|
+
"llm.tool_call",
|
|
345
|
+
"llm.completed",
|
|
346
|
+
"llm.error",
|
|
347
|
+
"tool.result",
|
|
348
|
+
"tool.error",
|
|
349
|
+
"context.threshold_exceeded",
|
|
350
|
+
"context.compacting",
|
|
351
|
+
"context.compacted",
|
|
352
|
+
],
|
|
353
|
+
(event) => {
|
|
354
|
+
// 工具执行事件(使用 log() 绕过 quiet 模式)
|
|
355
|
+
if (event.type === "llm.tool_call" && args.toolCalls) {
|
|
356
|
+
const payload = event.payload as any;
|
|
357
|
+
output.log(`🔧 ${payload.toolCall.name}`);
|
|
358
|
+
output.log(` ${payload.toolCall.arguments}`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (event.type === "tool.result" && args.toolResults) {
|
|
362
|
+
const payload = event.payload as any;
|
|
363
|
+
const result = payload.result?.output ?? payload.result?.error ?? "无输出";
|
|
364
|
+
output.log(`📤 ${payload.name}: ${String(result).substring(0, 200)}`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (event.type === "tool.error") {
|
|
368
|
+
const payload = event.payload as { toolName?: string; error?: string };
|
|
369
|
+
output.error(`❌ ${payload.toolName ?? "unknown"}: ${payload.error ?? "unknown error"}`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 上下文压缩事件
|
|
374
|
+
if (event.type === "context.threshold_exceeded" && !quiet) {
|
|
375
|
+
const payload = event.payload as any;
|
|
376
|
+
output.warn(`⚙ 上下文阈值 (${payload.totalTokens}/${payload.contextWindow})`);
|
|
377
|
+
}
|
|
378
|
+
if (event.type === "context.compacting" && !quiet) {
|
|
379
|
+
output.info("⚙ 压缩中...");
|
|
380
|
+
}
|
|
381
|
+
if (event.type === "context.compacted" && !quiet) {
|
|
382
|
+
const payload = event.payload as { checkpointId?: string };
|
|
383
|
+
output.success(`✓ 已压缩 (${payload.checkpointId ?? "unknown"})`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 其他事件交给 streamService 处理
|
|
387
|
+
streamService.handleEvent(event);
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (!quiet) {
|
|
392
|
+
output.info(`执行: ${args.message}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ========================================================================
|
|
396
|
+
// 使用 OTel tracer 创建根 span
|
|
397
|
+
//
|
|
398
|
+
// 使用统一的 roy-tracer,这样可以继承全局 context
|
|
399
|
+
// ========================================================================
|
|
400
|
+
const provider = getTracerProvider();
|
|
401
|
+
const tracer = provider.getTracer("roy-tracer");
|
|
402
|
+
const rootSpan = tracer.startSpan("act.execute", {
|
|
403
|
+
attributes: {
|
|
404
|
+
message: args.message.substring(0, 200),
|
|
405
|
+
sessionId,
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
const traceId = rootSpan.spanContext.traceId;
|
|
409
|
+
|
|
410
|
+
if (!quiet) {
|
|
411
|
+
output.info(`[Trace] ${traceId}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 设置全局 context
|
|
415
|
+
provider.setGlobalContext(rootSpan.spanContext);
|
|
416
|
+
|
|
417
|
+
// 创建 Context Handler(用于自动压缩)
|
|
418
|
+
const contextHandler = new ContextHandlerService(
|
|
419
|
+
env,
|
|
420
|
+
sessionComponent,
|
|
421
|
+
{ maxRetries: 1, autoCompact: true }
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// 构建 context(携带 trace id)
|
|
425
|
+
const context = {
|
|
426
|
+
sessionId,
|
|
427
|
+
metadata: {
|
|
428
|
+
originalQuery: args.message,
|
|
429
|
+
traceId,
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// 执行查询(带上下文阈值处理)
|
|
434
|
+
let result: string;
|
|
435
|
+
try {
|
|
436
|
+
result = await contextHandler.handleQueryWithContext(args.message, context);
|
|
437
|
+
rootSpan.end(result);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
rootSpan.setAttribute("error", String(error));
|
|
440
|
+
rootSpan.end(undefined, error instanceof Error ? error : new Error(String(error)));
|
|
441
|
+
|
|
442
|
+
if (error instanceof ContextError && error.code === ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED) {
|
|
443
|
+
output.error(
|
|
444
|
+
`上下文阈值超出限制: ${error.usage?.totalTokens}/${error.contextWindow}`
|
|
445
|
+
);
|
|
446
|
+
output.info(
|
|
447
|
+
"请手动压缩会话: roy sessions compact " + (sessionId || "<session-id>")
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
throw error;
|
|
451
|
+
} finally {
|
|
452
|
+
// 清理全局 context
|
|
453
|
+
provider.setGlobalContext(undefined);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
unsubscribe();
|
|
457
|
+
|
|
458
|
+
// 流式输出已完成
|
|
459
|
+
if (!quiet) {
|
|
460
|
+
output.success("执行完成");
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
output.error(`执行失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
464
|
+
if (process.env.DEBUG) {
|
|
465
|
+
console.error(error);
|
|
466
|
+
}
|
|
467
|
+
process.exit(1);
|
|
468
|
+
} finally {
|
|
469
|
+
// 清理 LSP Manager(关闭所有 LSP 连接和进程)
|
|
470
|
+
if (lspManagerDispose) {
|
|
471
|
+
await lspManagerDispose();
|
|
472
|
+
}
|
|
473
|
+
// 清理 PluginAdapter
|
|
474
|
+
if (pluginAdapterDispose) {
|
|
475
|
+
await pluginAdapterDispose();
|
|
476
|
+
}
|
|
477
|
+
await envService.dispose();
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
};
|