nolo-cli 0.1.23 → 0.1.26

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 (76) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/hybridRecordStore.ts +30 -9
  3. package/agent-runtime/localLoop.ts +12 -4
  4. package/agent-runtime/localToolPolicy.ts +22 -10
  5. package/agent-runtime/localWorkspaceTools.ts +140 -374
  6. package/agent-runtime/runtimeToolPolicy.ts +43 -2
  7. package/agent-runtime/types.ts +5 -0
  8. package/agentAliases.ts +1 -1
  9. package/agentCommandSupport.ts +77 -0
  10. package/agentInternalCommandEntries.ts +34 -0
  11. package/agentListCommands.ts +123 -0
  12. package/agentListHelpers.ts +187 -0
  13. package/agentMachineCommands.ts +402 -0
  14. package/agentRecordCommands.ts +141 -0
  15. package/agentRecordHelpers.ts +395 -0
  16. package/agentRunCommand.ts +1 -2
  17. package/agentRuntimeCommands.ts +7 -701
  18. package/ai/agent/_executeModel.ts +29 -3
  19. package/ai/agent/buildSystemPrompt.ts +17 -0
  20. package/ai/agent/cliChatClient.ts +38 -4
  21. package/ai/agent/cliExecutor.ts +304 -25
  22. package/ai/agent/createAgentSchema.ts +2 -2
  23. package/ai/agent/guidedCreation/assistant.ts +82 -0
  24. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  25. package/ai/agent/guidedCreation/draft.ts +62 -0
  26. package/ai/agent/guidedCreation/types.ts +38 -0
  27. package/ai/agent/machineRunPermissions.ts +21 -3
  28. package/ai/agent/runtimeGuidance.ts +1 -1
  29. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  30. package/ai/agent/streamAgentChatTurn.ts +492 -19
  31. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  32. package/ai/ai.locale.ts +14 -2
  33. package/ai/chat/sendOpenAICompletionsRequest.ts +28 -3
  34. package/ai/chat/sendOpenAIResponseRequest.ts +3 -2
  35. package/ai/llm/getPricing.ts +14 -0
  36. package/ai/llm/kimi.ts +3 -0
  37. package/ai/llm/modelAvatar.ts +1 -0
  38. package/ai/llm/providers.ts +1 -1
  39. package/ai/taskRun/taskRunProtocol.ts +24 -10
  40. package/ai/token/modelUsageQuery.ts +17 -0
  41. package/ai/tools/agent/agentTools.ts +21 -0
  42. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  43. package/ai/tools/checkEnvTool.ts +52 -20
  44. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  45. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  46. package/ai/tools/index.ts +24 -14
  47. package/ai/tools/marxistsOfflineBook.ts +253 -0
  48. package/ai/tools/modelUsageTools.ts +4 -2
  49. package/ai/tools/readTool.ts +31 -0
  50. package/ai/tools/toolApiClient.ts +35 -1
  51. package/ai/tools/toolNameAliases.ts +13 -0
  52. package/cliCommandAdapters.ts +28 -0
  53. package/cliCommandDispatch.ts +66 -0
  54. package/cliCommandFactories.ts +34 -0
  55. package/cliCommandTypes.ts +49 -0
  56. package/cliEnvHelpers.ts +44 -0
  57. package/client/agentRun.test.ts +123 -19
  58. package/client/agentRun.ts +40 -1
  59. package/client/hybridRecordStore.test.ts +42 -0
  60. package/client/hybridRecordStore.ts +11 -0
  61. package/client/localRuntimeAdapter.test.ts +189 -21
  62. package/client/localRuntimeAdapter.ts +84 -8
  63. package/client/localRuntimeDryRun.test.ts +5 -2
  64. package/client/taskRunPrompt.ts +1 -0
  65. package/commandRegistry.ts +11 -61
  66. package/connector-experimental/capabilities.ts +1 -0
  67. package/database/server/coreDataProxy.ts +43 -26
  68. package/index.ts +54 -191
  69. package/internalCommandEntries.ts +12 -0
  70. package/machineCommands.ts +301 -32
  71. package/offlineMarxistsAgentCommand.ts +314 -0
  72. package/package.json +20 -2
  73. package/runtimeDoctorCommands.ts +118 -0
  74. package/scriptCommandEntries.ts +71 -0
  75. package/systemInternalCommandEntries.ts +71 -0
  76. package/workflowInternalCommandEntries.ts +11 -0
@@ -9,6 +9,7 @@ import { generateRequestBody } from "ai/llm/generateRequestBody";
9
9
  import { getApiEndpoint } from "ai/llm/providers";
10
10
  import { selectCurrentServer } from "app/settings/settingSlice";
11
11
  import { selectCurrentToken } from "auth/authSlice";
12
+ import { applyChatCompletionsStreamMode } from "integrations/openai/chatCompletionStreamMode";
12
13
 
13
14
  import { sendOpenAICompletionsRequest } from "../chat/sendOpenAICompletionsRequest";
14
15
  import { performFetchRequest } from "../chat/fetchUtils";
@@ -73,13 +74,13 @@ export const _executeModel = async (
73
74
  messages = [{ role: "user", content: args.content }];
74
75
  }
75
76
 
76
- const bodyData = generateRequestBody({
77
+ const requestBody = generateRequestBody({
77
78
  agentConfig: resolvedConfig,
78
79
  messages,
79
80
  userInput: content,
80
81
  contexts: agentContexts,
81
82
  });
82
- bodyData.stream = isStreaming;
83
+ const bodyData = applyChatCompletionsStreamMode(requestBody, isStreaming);
83
84
 
84
85
  if (isStreaming) {
85
86
  await sendOpenAICompletionsRequest({
@@ -97,7 +98,32 @@ export const _executeModel = async (
97
98
  currentServer: selectCurrentServer(state),
98
99
  token: selectCurrentToken(state),
99
100
  });
101
+ if (!response.ok) {
102
+ let message = `Model request failed with HTTP ${response.status}`;
103
+ try {
104
+ const errorBody = await response.json();
105
+ const upstreamMessage =
106
+ typeof errorBody?.error?.message === "string"
107
+ ? errorBody.error.message
108
+ : typeof errorBody?.message === "string"
109
+ ? errorBody.message
110
+ : "";
111
+ if (upstreamMessage.trim()) message = upstreamMessage.trim();
112
+ } catch {
113
+ // Keep the status-based message when the response body is not JSON.
114
+ }
115
+ throw new Error(message);
116
+ }
100
117
  const result = await response.json();
118
+ const content = result?.choices?.[0]?.message?.content;
119
+ if (typeof content !== "string") {
120
+ const upstreamMessage =
121
+ typeof result?.error?.message === "string" ? result.error.message : "";
122
+ throw new Error(
123
+ upstreamMessage.trim() ||
124
+ "Model response missing choices[0].message.content"
125
+ );
126
+ }
101
127
  if (args.billingDialogKey && result?.usage) {
102
128
  await updateTokensAction(
103
129
  {
@@ -109,7 +135,7 @@ export const _executeModel = async (
109
135
  thunkApi
110
136
  );
111
137
  }
112
- return result.choices[0].message.content;
138
+ return content;
113
139
  }
114
140
  } catch (error: any) {
115
141
  console.error(`_executeModel failed`, error);
@@ -198,6 +198,8 @@ const CODE_EDITING_FIDELITY_INSTRUCTIONS = `--- 代码编辑真值规则 ---
198
198
  当任务涉及 readFile / writeFile / applyEdit / applyLineEdits 等代码编辑工具时,必须遵守以下规则:
199
199
 
200
200
  - 先读取目标文件的最新内容,再决定修改。
201
+ - 读取本地或代码仓库文件时,必须调用 readFile。不要用 read、readDoc、readPage 读取本地路径;这些工具用于知识库页面/文档 ID。
202
+ - 执行本地 shell/bash 命令时,必须调用 execShell。
201
203
  - 如果用户提供了 URL、文档页或产品页,且你需要据此改代码,必须先抓取这些网页,再开始编辑。
202
204
  - 如果用户已经提供可用 URL,就不要再搜索替代来源;只有这些 URL 无法满足任务时,才允许降级到搜索,并在最终回复中说明。
203
205
  - 网页中直接出现的模型名、价格、上下文窗口、发布日期、能力描述、是否支持 function calling / vision 等字段,视为本次修改的权威来源。
@@ -219,6 +221,16 @@ const TABLE_SHARE_INSTRUCTIONS = `--- 表格创建与分享 ---
219
221
  - 传入表的 dbKey,或 tenantId + tableId;
220
222
  - 完成后直接返回 /share/... 链接。`;
221
223
 
224
+ const AGENT_CREATION_DRAFT_INSTRUCTIONS = `--- Agent 创建草稿流程 ---
225
+ 当用户想创建、定制、配置自己的 Agent / AI 助手时,必须走草稿优先流程:
226
+
227
+ - 不要直接创建真实 Agent 记录,也不要声称已经创建完成。
228
+ - 先基于当前对话和用户刚说的话整理一个可预览草稿,调用 prepareAgentDraft。
229
+ - 草稿应尽量包含名称、用途简介、prompt 摘要、完整 prompt、能力、知识引用建议、公开状态和仍需确认的问题。
230
+ - 用户表达模糊时,也先给出合理的初版草稿;信息不足时每次只问一个关键问题。
231
+ - 对知识引用、公开状态、高风险工具能力,必须等待用户确认后再视为已选。
232
+ - 草稿足够完整后,引导用户点击“预览并创建”,进入 /create/agent 做最终确认。`;
233
+
222
234
  // ============================================================================
223
235
  // 无 prompt 时的澄清模式
224
236
  // ============================================================================
@@ -453,6 +465,10 @@ export const buildSystemPromptContext = (options: {
453
465
  ? TABLE_SHARE_INSTRUCTIONS
454
466
  : "";
455
467
 
468
+ const agentCreationDraftSection = agentTools.includes("prepareAgentDraft")
469
+ ? AGENT_CREATION_DRAFT_INSTRUCTIONS
470
+ : "";
471
+
456
472
  // 知识管理:有页面读写工具时注入通用知识管理说明
457
473
  const knowledgeManagementSection = agentTools.some((t) =>
458
474
  ["createDoc", "updateDoc", "read", "readDoc", "readPage"].includes(t)
@@ -519,6 +535,7 @@ export const buildSystemPromptContext = (options: {
519
535
  { id: "web-access", owner: "platform", content: webAccessSection },
520
536
  { id: "local-file-organization", owner: "platform", content: localFileOrganizationSection },
521
537
  { id: "table-share", owner: "platform", content: tableShareSection },
538
+ { id: "agent-creation-draft", owner: "platform", content: agentCreationDraftSection },
522
539
  { id: "menu-usage", owner: "platform", content: menuUsageSection },
523
540
  { id: "clarification-mode", owner: "platform", content: clarifyingSection },
524
541
  { id: "knowledge-management", owner: "platform", content: knowledgeManagementSection },
@@ -8,6 +8,15 @@ type CliChatAction = "start" | "turn" | "get" | "close";
8
8
  type CliChatRequestBase = {
9
9
  cliProvider?: CliProvider;
10
10
  model?: string;
11
+ systemPrompt?: string;
12
+ reasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max";
13
+ temperature?: number;
14
+ topP?: number;
15
+ frequencyPenalty?: number;
16
+ presencePenalty?: number;
17
+ maxTokens?: number;
18
+ enableThinking?: boolean;
19
+ thinkingBudget?: number;
11
20
  };
12
21
 
13
22
  function getCliChatRequestConfig(thunkApi: any) {
@@ -41,10 +50,18 @@ export async function startCliChatSession(
41
50
  ) {
42
51
  const response = await postCliChat(thunkApi, {
43
52
  action: "start" satisfies CliChatAction,
44
- cliProvider: args.cliProvider,
45
- model: args.model,
46
- systemPrompt: args.systemPrompt,
47
- });
53
+ cliProvider: args.cliProvider,
54
+ model: args.model,
55
+ systemPrompt: args.systemPrompt,
56
+ reasoningEffort: args.reasoningEffort,
57
+ temperature: args.temperature,
58
+ topP: args.topP,
59
+ frequencyPenalty: args.frequencyPenalty,
60
+ presencePenalty: args.presencePenalty,
61
+ maxTokens: args.maxTokens,
62
+ enableThinking: args.enableThinking,
63
+ thinkingBudget: args.thinkingBudget,
64
+ });
48
65
  return response.json();
49
66
  }
50
67
 
@@ -108,11 +125,28 @@ export function createCliChatTurnStream(
108
125
  sessionId: args.sessionId,
109
126
  prompt: args.prompt,
110
127
  model: args.model,
128
+ reasoningEffort: args.reasoningEffort,
129
+ temperature: args.temperature,
130
+ topP: args.topP,
131
+ frequencyPenalty: args.frequencyPenalty,
132
+ presencePenalty: args.presencePenalty,
133
+ maxTokens: args.maxTokens,
134
+ enableThinking: args.enableThinking,
135
+ thinkingBudget: args.thinkingBudget,
111
136
  }
112
137
  : {
113
138
  prompt: args.prompt,
114
139
  model: args.model,
115
140
  cliProvider: args.cliProvider,
141
+ systemPrompt: args.systemPrompt,
142
+ reasoningEffort: args.reasoningEffort,
143
+ temperature: args.temperature,
144
+ topP: args.topP,
145
+ frequencyPenalty: args.frequencyPenalty,
146
+ presencePenalty: args.presencePenalty,
147
+ maxTokens: args.maxTokens,
148
+ enableThinking: args.enableThinking,
149
+ thinkingBudget: args.thinkingBudget,
116
150
  },
117
151
  signal,
118
152
  );
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CLI Executor - 通过命令行工具执行 AI 任务
3
3
  *
4
- * 设计为可扩展:当前支持 Copilot CLI、Gemini CLI、Codex CLI 与 Claude CLI。
4
+ * 设计为可扩展:当前支持 Copilot CLI、Gemini CLI、Codex CLI、Claude CLI 与 Antigravity CLI。
5
5
  *
6
6
  * 注意:
7
7
  * - CLI provider 与普通 model 路由共享 prompt / model / 最近文本上下文这些能力面
@@ -13,7 +13,7 @@
13
13
  * const result = await executeCli("copilot", prompt, { model: "claude-haiku-4.5" });
14
14
  */
15
15
 
16
- import { exec, spawn } from "child_process";
16
+ import { exec, execSync, spawn } from "child_process";
17
17
  import { randomUUID } from "node:crypto";
18
18
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
19
19
  import { join } from "node:path";
@@ -23,7 +23,7 @@ import { buildCliPrompt } from "./cliPrompt";
23
23
  // ── 类型定义 ──────────────────────────────────────────────────────────────────
24
24
 
25
25
  /** 已支持的 CLI 工具。新增时在此联合类型追加,并在 EXECUTORS 里注册实现 */
26
- export type CliProvider = "copilot" | "gemini" | "codex" | "claude";
26
+ export type CliProvider = "copilot" | "gemini" | "codex" | "claude" | "agy";
27
27
 
28
28
  export interface CliExecuteOptions {
29
29
  model?: string;
@@ -31,12 +31,22 @@ export interface CliExecuteOptions {
31
31
  cwd?: string;
32
32
  env?: Record<string, string | undefined>;
33
33
  yolo?: boolean; // 允许所有工具,默认 true(后台任务常用)
34
+ systemPrompt?: string;
35
+ reasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max";
36
+ temperature?: number;
37
+ topP?: number;
38
+ frequencyPenalty?: number;
39
+ presencePenalty?: number;
40
+ maxTokens?: number;
41
+ enableThinking?: boolean;
42
+ thinkingBudget?: number;
34
43
  }
35
44
 
36
45
  export interface CliExecuteResult {
37
46
  text: string; // 解析后的纯文本回复
38
47
  raw: string; // 原始 stdout
39
48
  elapsed: number; // 耗时 ms
49
+ warnings?: string[];
40
50
  }
41
51
 
42
52
  type CliSessionMessage = {
@@ -63,16 +73,163 @@ export interface CliSessionTurnResult extends CliExecuteResult {
63
73
 
64
74
  const cliSessions = new Map<string, CliSessionState>();
65
75
 
76
+ type ProxyEnv = {
77
+ HTTP_PROXY?: string;
78
+ HTTPS_PROXY?: string;
79
+ ALL_PROXY?: string;
80
+ NO_PROXY?: string;
81
+ http_proxy?: string;
82
+ https_proxy?: string;
83
+ all_proxy?: string;
84
+ no_proxy?: string;
85
+ };
86
+
87
+ function readMacSystemProxyEnv(): ProxyEnv {
88
+ if (process.platform !== "darwin") return {};
89
+
90
+ try {
91
+ const raw = String(execSync("scutil --proxy", { encoding: "utf8" }) || "");
92
+ const values = new Map<string, string>();
93
+ for (const line of raw.split("\n")) {
94
+ const match = line.match(/^\s*([A-Za-z0-9_]+)\s*:\s*(.+?)\s*$/);
95
+ if (!match) continue;
96
+ values.set(match[1], match[2].replace(/^"|"$/g, ""));
97
+ }
98
+
99
+ const httpEnabled = values.get("HTTPEnable") === "1";
100
+ const httpsEnabled = values.get("HTTPSEnable") === "1";
101
+ const httpHost = values.get("HTTPProxy")?.trim();
102
+ const httpsHost = values.get("HTTPSProxy")?.trim();
103
+ const httpPort = values.get("HTTPPort")?.trim();
104
+ const httpsPort = values.get("HTTPSPort")?.trim();
105
+ const noProxy = values.get("ExceptionsList")?.trim();
106
+
107
+ const httpProxy = httpEnabled && httpHost && httpPort ? `http://${httpHost}:${httpPort}` : "";
108
+ const httpsProxy = httpsEnabled && httpsHost && httpsPort ? `http://${httpsHost}:${httpsPort}` : "";
109
+ const allProxy = httpsProxy || httpProxy;
110
+
111
+ return {
112
+ ...(httpProxy ? { HTTP_PROXY: httpProxy, http_proxy: httpProxy } : {}),
113
+ ...(httpsProxy ? { HTTPS_PROXY: httpsProxy, https_proxy: httpsProxy } : {}),
114
+ ...(allProxy ? { ALL_PROXY: allProxy, all_proxy: allProxy } : {}),
115
+ ...(noProxy ? { NO_PROXY: noProxy, no_proxy: noProxy } : {}),
116
+ };
117
+ } catch {
118
+ return {};
119
+ }
120
+ }
121
+
122
+ function normalizeProxyEnv(env: Record<string, string | undefined>): Record<string, string | undefined> {
123
+ const explicitHttp = env.HTTP_PROXY?.trim() || env.http_proxy?.trim() || "";
124
+ const explicitHttps = env.HTTPS_PROXY?.trim() || env.https_proxy?.trim() || "";
125
+ const explicitAll = env.ALL_PROXY?.trim() || env.all_proxy?.trim() || "";
126
+ const explicitNoProxy = env.NO_PROXY?.trim() || env.no_proxy?.trim() || "";
127
+ const fallback = explicitHttp || explicitHttps || explicitAll ? {} : readMacSystemProxyEnv();
128
+
129
+ const httpProxy = explicitHttp || fallback.HTTP_PROXY || fallback.http_proxy || "";
130
+ const httpsProxy = explicitHttps || fallback.HTTPS_PROXY || fallback.https_proxy || httpProxy;
131
+ const allProxy = explicitAll || fallback.ALL_PROXY || fallback.all_proxy || httpsProxy || httpProxy;
132
+ const noProxy = explicitNoProxy || fallback.NO_PROXY || fallback.no_proxy || "";
133
+
134
+ return {
135
+ ...env,
136
+ ...(httpProxy ? { HTTP_PROXY: httpProxy, http_proxy: httpProxy } : {}),
137
+ ...(httpsProxy ? { HTTPS_PROXY: httpsProxy, https_proxy: httpsProxy } : {}),
138
+ ...(allProxy ? { ALL_PROXY: allProxy, all_proxy: allProxy } : {}),
139
+ ...(noProxy ? { NO_PROXY: noProxy, no_proxy: noProxy } : {}),
140
+ };
141
+ }
142
+
66
143
  function buildCliProcessEnv(extraEnv?: Record<string, string | undefined>) {
67
144
  return Object.fromEntries(
68
- Object.entries({
145
+ Object.entries(normalizeProxyEnv({
69
146
  ...process.env,
70
147
  ...extraEnv,
71
148
  NO_COLOR: "1",
72
- }).filter((entry): entry is [string, string] => typeof entry[1] === "string")
149
+ })).filter((entry): entry is [string, string] => typeof entry[1] === "string")
73
150
  );
74
151
  }
75
152
 
153
+ function getCliProviderLabel(provider: CliProvider): string {
154
+ switch (provider) {
155
+ case "claude":
156
+ return "Claude CLI";
157
+ case "codex":
158
+ return "Codex CLI";
159
+ case "copilot":
160
+ return "Copilot CLI";
161
+ case "agy":
162
+ return "Antigravity CLI";
163
+ case "gemini":
164
+ return "Gemini CLI";
165
+ }
166
+ }
167
+
168
+ function supportsNativeSystemPrompt(provider: CliProvider): boolean {
169
+ return provider === "claude";
170
+ }
171
+
172
+ function supportsModel(provider: CliProvider): boolean {
173
+ return provider !== "agy";
174
+ }
175
+
176
+ function supportsReasoningEffort(provider: CliProvider): boolean {
177
+ return provider === "claude" || provider === "copilot";
178
+ }
179
+
180
+ function collectCliCapabilityWarnings(
181
+ provider: CliProvider,
182
+ options: CliExecuteOptions
183
+ ): string[] {
184
+ const warnings: string[] = [];
185
+ const label = getCliProviderLabel(provider);
186
+
187
+ if (options.reasoningEffort !== undefined && !supportsReasoningEffort(provider)) {
188
+ warnings.push(`${label} does not support reasoning_effort; ignored.`);
189
+ }
190
+
191
+ if (options.model !== undefined && !supportsModel(provider)) {
192
+ warnings.push(`${label} does not support model selection; ignored.`);
193
+ }
194
+
195
+ const unsupportedFields: Array<[string, unknown]> = [
196
+ ["temperature", options.temperature],
197
+ ["top_p", options.topP],
198
+ ["frequency_penalty", options.frequencyPenalty],
199
+ ["presence_penalty", options.presencePenalty],
200
+ ["max_tokens", options.maxTokens],
201
+ ["enableThinking", options.enableThinking ? true : undefined],
202
+ ["thinkingBudget", options.thinkingBudget],
203
+ ];
204
+
205
+ for (const [field, value] of unsupportedFields) {
206
+ if (value !== undefined) {
207
+ warnings.push(`${label} does not support ${field}; ignored.`);
208
+ }
209
+ }
210
+
211
+ return warnings;
212
+ }
213
+
214
+ function resolveCliExecution(
215
+ provider: CliProvider,
216
+ prompt: string,
217
+ options: CliExecuteOptions
218
+ ): { prompt: string; options: CliExecuteOptions; warnings: string[] } {
219
+ const warnings = collectCliCapabilityWarnings(provider, options);
220
+ if (supportsNativeSystemPrompt(provider) || !options.systemPrompt?.trim()) {
221
+ return { prompt, options, warnings };
222
+ }
223
+ return {
224
+ prompt: buildCliPrompt(options.systemPrompt, prompt),
225
+ options: {
226
+ ...options,
227
+ systemPrompt: undefined,
228
+ },
229
+ warnings,
230
+ };
231
+ }
232
+
76
233
  // ── 各 provider 实现 ─────────────────────────────────────────────────────────
77
234
 
78
235
  /**
@@ -85,6 +242,7 @@ function executeCopilot(
85
242
  ): Promise<CliExecuteResult> {
86
243
  const {
87
244
  model,
245
+ reasoningEffort,
88
246
  timeout = 120_000,
89
247
  cwd = process.cwd(),
90
248
  yolo = true,
@@ -101,6 +259,7 @@ function executeCopilot(
101
259
  "--no-color",
102
260
  ];
103
261
  if (model) args.push(`--model ${model}`);
262
+ if (reasoningEffort) args.push(`--reasoning-effort ${reasoningEffort}`);
104
263
  if (yolo) args.push("--yolo");
105
264
 
106
265
  const cmd = args.join(" ");
@@ -327,6 +486,8 @@ function executeClaude(
327
486
  ): Promise<CliExecuteResult> {
328
487
  const {
329
488
  model,
489
+ systemPrompt,
490
+ reasoningEffort,
330
491
  timeout = 120_000,
331
492
  cwd = process.cwd(),
332
493
  yolo = true,
@@ -338,6 +499,14 @@ function executeClaude(
338
499
  args.push("--model");
339
500
  args.push(model);
340
501
  }
502
+ if (systemPrompt?.trim()) {
503
+ args.push("--system-prompt");
504
+ args.push(systemPrompt.trim());
505
+ }
506
+ if (reasoningEffort) {
507
+ args.push("--effort");
508
+ args.push(reasoningEffort);
509
+ }
341
510
  if (yolo) {
342
511
  args.push("--permission-mode");
343
512
  args.push("bypassPermissions");
@@ -389,6 +558,77 @@ function executeClaude(
389
558
  });
390
559
  }
391
560
 
561
+ function formatCliTimeout(timeoutMs: number) {
562
+ return `${Math.max(1, Math.ceil(timeoutMs / 1000))}s`;
563
+ }
564
+
565
+ function executeAgy(
566
+ prompt: string,
567
+ options: CliExecuteOptions
568
+ ): Promise<CliExecuteResult> {
569
+ const {
570
+ timeout = 120_000,
571
+ cwd = process.cwd(),
572
+ yolo = true,
573
+ } = options;
574
+
575
+ return new Promise((resolve, reject) => {
576
+ const args = [
577
+ "--print",
578
+ prompt,
579
+ "--print-timeout",
580
+ formatCliTimeout(timeout),
581
+ ];
582
+ if (yolo) {
583
+ args.push("--dangerously-skip-permissions");
584
+ }
585
+
586
+ const start = Date.now();
587
+ const proc = spawn("agy", args, {
588
+ cwd,
589
+ env: buildCliProcessEnv(options.env),
590
+ stdio: ["ignore", "pipe", "pipe"],
591
+ });
592
+ let stdout = "";
593
+ let stderr = "";
594
+ let timer: ReturnType<typeof setTimeout> | undefined;
595
+
596
+ if (timeout > 0) {
597
+ timer = setTimeout(() => {
598
+ proc.kill("SIGTERM");
599
+ reject(new Error(`Antigravity CLI timed out after ${timeout}ms`));
600
+ }, timeout + 1_000);
601
+ }
602
+
603
+ proc.stdout.on("data", (data: Buffer) => {
604
+ stdout += data.toString();
605
+ });
606
+
607
+ proc.stderr.on("data", (data: Buffer) => {
608
+ stderr += data.toString();
609
+ });
610
+
611
+ proc.on("close", (code) => {
612
+ if (timer) clearTimeout(timer);
613
+ if (code !== 0 && code !== null) {
614
+ reject(new Error(`Antigravity CLI exited with code ${code}\nstderr: ${stderr}`));
615
+ return;
616
+ }
617
+
618
+ resolve({
619
+ text: stdout.trim(),
620
+ raw: stdout,
621
+ elapsed: Date.now() - start,
622
+ });
623
+ });
624
+
625
+ proc.on("error", (err) => {
626
+ if (timer) clearTimeout(timer);
627
+ reject(err);
628
+ });
629
+ });
630
+ }
631
+
392
632
  // ── 注册表(新增 CLI 工具时在这里加) ────────────────────────────────────────
393
633
 
394
634
  const EXECUTORS: Record<
@@ -399,6 +639,7 @@ const EXECUTORS: Record<
399
639
  gemini: executeGemini,
400
640
  codex: executeCodex,
401
641
  claude: executeClaude,
642
+ agy: executeAgy,
402
643
  };
403
644
 
404
645
  function formatCliSessionTask(messages: CliSessionMessage[]): string {
@@ -416,13 +657,10 @@ function buildCliSessionPrompt(session: CliSessionState, userInput: string): str
416
657
  { role: "user", content: userInput },
417
658
  ]);
418
659
 
419
- return buildCliPrompt(
420
- session.systemPrompt,
421
- [
422
- "以下是当前对话,请基于完整上下文继续回答最后一条用户消息。",
423
- transcript,
424
- ].join("\n\n"),
425
- );
660
+ return [
661
+ "以下是当前对话,请基于完整上下文继续回答最后一条用户消息。",
662
+ transcript,
663
+ ].join("\n\n");
426
664
  }
427
665
 
428
666
  function getCliSessionOrThrow(sessionId: string): CliSessionState {
@@ -438,7 +676,7 @@ function getCliSessionOrThrow(sessionId: string): CliSessionState {
438
676
  /**
439
677
  * 执行 CLI 任务
440
678
  *
441
- * @param provider CLI 工具类型(如 "copilot" | "gemini" | "codex" | "claude")
679
+ * @param provider CLI 工具类型(如 "copilot" | "gemini" | "codex" | "claude" | "agy")
442
680
  * @param prompt 完整 prompt(system prompt 已由调用方拼好)
443
681
  * @param options 执行选项
444
682
  */
@@ -451,7 +689,12 @@ export async function executeCli(
451
689
  if (!executor) {
452
690
  throw new Error(`Unknown CLI provider: "${provider}". Supported: ${Object.keys(EXECUTORS).join(", ")}`);
453
691
  }
454
- return executor(prompt, options);
692
+ const resolved = resolveCliExecution(provider, prompt, options);
693
+ const result = await executor(resolved.prompt, resolved.options);
694
+ return {
695
+ ...result,
696
+ warnings: [...resolved.warnings, ...(result.warnings ?? [])],
697
+ };
455
698
  }
456
699
 
457
700
  export { buildCliPrompt };
@@ -473,7 +716,17 @@ export function startCliSession(
473
716
  model: options.model,
474
717
  timeout: options.timeout,
475
718
  cwd: options.cwd,
719
+ env: options.env,
476
720
  yolo: options.yolo,
721
+ systemPrompt: options.systemPrompt,
722
+ reasoningEffort: options.reasoningEffort,
723
+ temperature: options.temperature,
724
+ topP: options.topP,
725
+ frequencyPenalty: options.frequencyPenalty,
726
+ presencePenalty: options.presencePenalty,
727
+ maxTokens: options.maxTokens,
728
+ enableThinking: options.enableThinking,
729
+ thinkingBudget: options.thinkingBudget,
477
730
  },
478
731
  messages: [],
479
732
  createdAt: Date.now(),
@@ -567,31 +820,52 @@ export function executeCliStreaming(
567
820
  prompt: string,
568
821
  options: CliExecuteOptions & { onChunk: (chunk: string) => void }
569
822
  ): Promise<CliExecuteResult> {
823
+ const resolved = resolveCliExecution(provider, prompt, options);
824
+ const executor = EXECUTORS[provider];
570
825
  if (provider === "gemini") {
571
- return executeGeminiStreaming(prompt, options);
826
+ return executeGeminiStreaming(resolved.prompt, resolved.options as CliExecuteOptions & { onChunk: (chunk: string) => void })
827
+ .then((result) => ({ ...result, warnings: [...resolved.warnings, ...(result.warnings ?? [])] }));
572
828
  }
573
829
  if (provider === "codex") {
574
- return executeCli(provider, prompt, options).then((result) => {
575
- if (result.text) options.onChunk(result.text);
576
- return result;
830
+ return executor(resolved.prompt, resolved.options).then((result) => {
831
+ const merged = { ...result, warnings: [...resolved.warnings, ...(result.warnings ?? [])] };
832
+ if (merged.text) options.onChunk(merged.text);
833
+ return merged;
577
834
  });
578
835
  }
579
836
  if (provider === "claude") {
580
- return executeCli(provider, prompt, options).then((result) => {
581
- if (result.text) options.onChunk(result.text);
582
- return result;
837
+ return executor(resolved.prompt, resolved.options).then((result) => {
838
+ const merged = { ...result, warnings: [...resolved.warnings, ...(result.warnings ?? [])] };
839
+ if (merged.text) options.onChunk(merged.text);
840
+ return merged;
841
+ });
842
+ }
843
+ if (provider === "agy") {
844
+ return executor(resolved.prompt, resolved.options).then((result) => {
845
+ const merged = { ...result, warnings: [...resolved.warnings, ...(result.warnings ?? [])] };
846
+ if (merged.text) options.onChunk(merged.text);
847
+ return merged;
583
848
  });
584
849
  }
585
850
  if (provider !== "copilot") {
586
- return executeCli(provider, prompt, options);
851
+ return executor(resolved.prompt, resolved.options).then((result) => ({
852
+ ...result,
853
+ warnings: [...resolved.warnings, ...(result.warnings ?? [])],
854
+ }));
587
855
  }
588
856
 
589
- const { model, timeout = 120_000, cwd = process.cwd(), yolo = true, onChunk } = options;
857
+ const {
858
+ model,
859
+ timeout = 120_000,
860
+ cwd = process.cwd(),
861
+ yolo = true,
862
+ onChunk,
863
+ } = resolved.options as CliExecuteOptions & { onChunk: (chunk: string) => void };
590
864
 
591
865
  return new Promise((resolve, reject) => {
592
866
  const args = [
593
867
  "copilot", "--",
594
- "-p", prompt,
868
+ "-p", resolved.prompt,
595
869
  "--silent",
596
870
  "--disable-builtin-mcps",
597
871
  ];
@@ -636,7 +910,12 @@ export function executeCliStreaming(
636
910
  .filter(filterNoiseLine)
637
911
  .join("\n")
638
912
  .trim();
639
- resolve({ text, raw, elapsed: Date.now() - start });
913
+ resolve({
914
+ text,
915
+ raw,
916
+ elapsed: Date.now() - start,
917
+ warnings: [...resolved.warnings],
918
+ });
640
919
  });
641
920
 
642
921
  proc.on("error", (err) => {
@@ -66,10 +66,10 @@ export const getCreateAgentSchema = (t: TFunction) =>
66
66
  apiSource: z.enum(["platform", "custom", "cli"]).default("platform"),
67
67
 
68
68
  /**
69
- * CLI provider(apiSource=cli 时有效):支持 "copilot" | "gemini" | "codex" | "claude"
69
+ * CLI provider(apiSource=cli 时有效):支持 "copilot" | "gemini" | "codex" | "claude" | "agy"
70
70
  */
71
71
  cliProvider: z
72
- .enum(["copilot", "gemini", "codex", "claude"])
72
+ .enum(["copilot", "gemini", "codex", "claude", "agy"])
73
73
  .optional()
74
74
  .or(z.literal("")),
75
75