@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.
Files changed (158) hide show
  1. package/README.md +126 -0
  2. package/dist/bin/roy.js +127297 -0
  3. package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
  4. package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
  5. package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
  6. package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
  7. package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
  8. package/package.json +91 -0
  9. package/src/bin/roy.ts +12 -0
  10. package/src/cli.ts +101 -0
  11. package/src/commands/act.ts +480 -0
  12. package/src/commands/commands-add.ts +110 -0
  13. package/src/commands/commands-dirs.ts +70 -0
  14. package/src/commands/commands-info.ts +90 -0
  15. package/src/commands/commands-list.ts +161 -0
  16. package/src/commands/commands-remove.ts +147 -0
  17. package/src/commands/commands.ts +55 -0
  18. package/src/commands/config/config-service.test.ts +449 -0
  19. package/src/commands/config/config-service.ts +312 -0
  20. package/src/commands/config/deep-merge.test.ts +168 -0
  21. package/src/commands/config/deep-merge.ts +63 -0
  22. package/src/commands/config/export.ts +97 -0
  23. package/src/commands/config/filter-history-e2e.test.ts +141 -0
  24. package/src/commands/config/import-preserve-refs.test.ts +212 -0
  25. package/src/commands/config/import.ts +119 -0
  26. package/src/commands/config/index.ts +35 -0
  27. package/src/commands/config/list.ts +281 -0
  28. package/src/commands/config/roy-config-e2e.test.ts +297 -0
  29. package/src/commands/config/types.ts +54 -0
  30. package/src/commands/debug/index.ts +38 -0
  31. package/src/commands/debug/log.test.ts +233 -0
  32. package/src/commands/debug/log.ts +123 -0
  33. package/src/commands/debug/span.test.ts +297 -0
  34. package/src/commands/debug/span.ts +211 -0
  35. package/src/commands/debug/trace.test.ts +254 -0
  36. package/src/commands/debug/trace.ts +140 -0
  37. package/src/commands/eventsource/add.ts +133 -0
  38. package/src/commands/eventsource/index.ts +48 -0
  39. package/src/commands/eventsource/list.ts +194 -0
  40. package/src/commands/eventsource/remove.ts +95 -0
  41. package/src/commands/eventsource/start.ts +103 -0
  42. package/src/commands/eventsource/status.ts +185 -0
  43. package/src/commands/eventsource/stop.ts +89 -0
  44. package/src/commands/index.ts +22 -0
  45. package/src/commands/input-handler.test.ts +76 -0
  46. package/src/commands/input-handler.ts +43 -0
  47. package/src/commands/interactive-esc.test.ts +254 -0
  48. package/src/commands/interactive.shutdown.test.ts +122 -0
  49. package/src/commands/interactive.test.ts +221 -0
  50. package/src/commands/interactive.ts +1015 -0
  51. package/src/commands/lsp/check.ts +92 -0
  52. package/src/commands/lsp/index.ts +32 -0
  53. package/src/commands/lsp/install.ts +126 -0
  54. package/src/commands/lsp/list.ts +64 -0
  55. package/src/commands/mcp/index.ts +27 -0
  56. package/src/commands/mcp/list.ts +116 -0
  57. package/src/commands/mcp/reload.ts +70 -0
  58. package/src/commands/mcp/tools.ts +121 -0
  59. package/src/commands/memory/extract-e2e.test.ts +388 -0
  60. package/src/commands/memory/index.ts +11 -0
  61. package/src/commands/memory/memory-simplified.test.ts +58 -0
  62. package/src/commands/memory/memory.ts +25 -0
  63. package/src/commands/memory/organize.ts +300 -0
  64. package/src/commands/memory/recall.test.ts +120 -0
  65. package/src/commands/memory/recall.ts +88 -0
  66. package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
  67. package/src/commands/memory/record-prompt-component.test.ts +343 -0
  68. package/src/commands/memory/record.test.ts +92 -0
  69. package/src/commands/memory/record.ts +332 -0
  70. package/src/commands/plugin.test.ts +292 -0
  71. package/src/commands/plugin.ts +267 -0
  72. package/src/commands/sessions/active.ts +96 -0
  73. package/src/commands/sessions/add-message.ts +96 -0
  74. package/src/commands/sessions/checkpoints.ts +154 -0
  75. package/src/commands/sessions/compact.test.ts +215 -0
  76. package/src/commands/sessions/compact.ts +269 -0
  77. package/src/commands/sessions/delete.ts +236 -0
  78. package/src/commands/sessions/get.ts +165 -0
  79. package/src/commands/sessions/grep.ts +233 -0
  80. package/src/commands/sessions/index.ts +95 -0
  81. package/src/commands/sessions/list.ts +210 -0
  82. package/src/commands/sessions/messages.test.ts +333 -0
  83. package/src/commands/sessions/messages.ts +248 -0
  84. package/src/commands/sessions/mock.ts +194 -0
  85. package/src/commands/sessions/new.ts +82 -0
  86. package/src/commands/sessions/rename.ts +98 -0
  87. package/src/commands/shared/event-handler.ts +213 -0
  88. package/src/commands/shared/event-message-formatter.ts +295 -0
  89. package/src/commands/shared/index.ts +11 -0
  90. package/src/commands/shared/query-executor.test.ts +434 -0
  91. package/src/commands/shared/query-executor.ts +324 -0
  92. package/src/commands/shared/repl-engine.test.ts +354 -0
  93. package/src/commands/shared/session-manager.test.ts +212 -0
  94. package/src/commands/shared/session-manager.ts +114 -0
  95. package/src/commands/skills/get.ts +90 -0
  96. package/src/commands/skills/index.ts +39 -0
  97. package/src/commands/skills/list.ts +129 -0
  98. package/src/commands/skills/reload.ts +59 -0
  99. package/src/commands/skills/search.ts +132 -0
  100. package/src/commands/skills/show-config.ts +93 -0
  101. package/src/commands/tasks/complete.ts +92 -0
  102. package/src/commands/tasks/create.ts +118 -0
  103. package/src/commands/tasks/delete.ts +86 -0
  104. package/src/commands/tasks/get.ts +116 -0
  105. package/src/commands/tasks/index.ts +53 -0
  106. package/src/commands/tasks/list.ts +140 -0
  107. package/src/commands/tasks/operations.ts +120 -0
  108. package/src/commands/tasks/update.ts +122 -0
  109. package/src/commands/tools/exec-tool.ts +128 -0
  110. package/src/commands/tools/get.ts +114 -0
  111. package/src/commands/tools/index.ts +35 -0
  112. package/src/commands/tools/list.ts +107 -0
  113. package/src/commands/tools/shared/index.ts +7 -0
  114. package/src/commands/tools/shared/schema-helper.ts +111 -0
  115. package/src/commands/workflow/commands/add.ts +315 -0
  116. package/src/commands/workflow/commands/get.ts +193 -0
  117. package/src/commands/workflow/commands/list.ts +137 -0
  118. package/src/commands/workflow/commands/nodes.ts +528 -0
  119. package/src/commands/workflow/commands/remove.ts +94 -0
  120. package/src/commands/workflow/commands/run.ts +398 -0
  121. package/src/commands/workflow/commands/status.ts +147 -0
  122. package/src/commands/workflow/commands/stop.ts +91 -0
  123. package/src/commands/workflow/commands/update.ts +130 -0
  124. package/src/commands/workflow/commands/validate.ts +139 -0
  125. package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
  126. package/src/commands/workflow/index.ts +65 -0
  127. package/src/commands/workflow/renderers.ts +358 -0
  128. package/src/commands/workflow/validators/index.ts +8 -0
  129. package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
  130. package/src/commands/workflow/validators/node-validator.ts +125 -0
  131. package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
  132. package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
  133. package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
  134. package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
  135. package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
  136. package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
  137. package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
  138. package/src/commands/workflow/validators/types.ts +78 -0
  139. package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
  140. package/src/commands/workflow/validators/workflow-validator.ts +320 -0
  141. package/src/index.ts +19 -0
  142. package/src/plugin/apply.ts +103 -0
  143. package/src/plugin/discover.ts +219 -0
  144. package/src/plugin/index.ts +45 -0
  145. package/src/plugin/registry.ts +272 -0
  146. package/src/plugin/types.ts +165 -0
  147. package/src/services/context-handler.service.test.ts +501 -0
  148. package/src/services/context-handler.service.ts +372 -0
  149. package/src/services/environment.service.commands-prompt.test.ts +167 -0
  150. package/src/services/environment.service.ts +656 -0
  151. package/src/services/output.service.test.ts +92 -0
  152. package/src/services/output.service.ts +122 -0
  153. package/src/services/quiet-mode.service.test.ts +114 -0
  154. package/src/services/quiet-mode.service.ts +81 -0
  155. package/src/services/stream-output.service.test.ts +214 -0
  156. package/src/services/stream-output.service.ts +323 -0
  157. package/src/util/which.test.ts +101 -0
  158. 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
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @roy-agent/cli - CLI entry point
5
+ */
6
+
7
+ import { runCli } from "../cli";
8
+
9
+ runCli().catch((error) => {
10
+ console.error("Error:", error);
11
+ process.exit(1);
12
+ });
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
+ };