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
@@ -0,0 +1,217 @@
1
+ import React from "react";
2
+ import {
3
+ buildAgentFormDataFromGuidedDraft,
4
+ mergeGuidedAgentDraft,
5
+ validateGuidedAgentDraft,
6
+ } from "ai/agent/guidedCreation/draft";
7
+ import { DEFAULT_MODEL } from "ai/llm/providers";
8
+ import type {
9
+ GuidedAgentAssistantMessage,
10
+ GuidedAgentCapabilityId,
11
+ GuidedAgentDraft,
12
+ } from "ai/agent/guidedCreation/types";
13
+ import { mapCapabilityIdsToToolIds } from "ai/agent/guidedCreation/capabilities";
14
+
15
+ const EMPTY_DRAFT: GuidedAgentDraft = {
16
+ name: "",
17
+ introduction: "",
18
+ prompt: "",
19
+ promptSummary: "",
20
+ provider: DEFAULT_MODEL.provider,
21
+ model: DEFAULT_MODEL.name,
22
+ isPublic: false,
23
+ capabilityIds: [],
24
+ toolIds: [],
25
+ references: [],
26
+ tags: [],
27
+ unresolved: ["用途", "回答风格", "能力"],
28
+ };
29
+
30
+ const QUESTIONS = [
31
+ "你想让这个 AI 主要帮你完成什么?",
32
+ "它主要服务谁?希望用什么语气和风格回答?",
33
+ "它需要哪些能力:联网搜索、读写文档、处理表格、调用其他 AI、创建应用或代码?",
34
+ "有哪些资料、边界或禁区需要它遵守?",
35
+ "这个 AI 是只给自己用,还是公开给别人使用?",
36
+ ];
37
+
38
+ const textIncludes = (text: string, words: string[]) =>
39
+ words.some((word) => text.includes(word));
40
+
41
+ const inferCapabilityIds = (text: string): GuidedAgentCapabilityId[] => {
42
+ const lower = text.toLowerCase();
43
+ const ids: GuidedAgentCapabilityId[] = [];
44
+ if (textIncludes(lower, ["搜索", "联网", "网页", "资料", "最新", "research", "search"])) {
45
+ ids.push("webSearch");
46
+ }
47
+ if (textIncludes(lower, ["文档", "知识库", "页面", "资料", "doc", "docs", "knowledge"])) {
48
+ ids.push("docs");
49
+ }
50
+ if (textIncludes(lower, ["表格", "数据", "记录", "table", "sheet", "csv"])) {
51
+ ids.push("tables");
52
+ }
53
+ if (textIncludes(lower, ["多 agent", "多agent", "评审", "协作", "agent"])) {
54
+ ids.push("agents");
55
+ }
56
+ if (textIncludes(lower, ["应用", "代码", "app", "code", "开发", "生成页面"])) {
57
+ ids.push("apps");
58
+ }
59
+ return Array.from(new Set(ids));
60
+ };
61
+
62
+ const inferName = (text: string) => {
63
+ const cleaned = text
64
+ .replace(/[。!?!?,,;;::]/g, " ")
65
+ .replace(/\bAI\b/gi, "")
66
+ .replace(/帮我创建|创建一个|创建|面向/g, "")
67
+ .replace(/\s+/g, " ")
68
+ .trim();
69
+ if (!cleaned) return "";
70
+ const purpose = cleaned.slice(0, 18);
71
+ return `${purpose.replace(/\s+/g, "")}助手`;
72
+ };
73
+
74
+ const buildPrompt = (messages: GuidedAgentAssistantMessage[], draft: GuidedAgentDraft) => {
75
+ const userNeeds = messages
76
+ .filter((message) => message.role === "user")
77
+ .map((message) => `- ${message.content}`)
78
+ .join("\n");
79
+ return [
80
+ `你是${draft.name || "用户的专属 AI"}。`,
81
+ "你需要先理解用户目标,再给出可执行、清晰、有边界的帮助。",
82
+ "根据以下需求工作:",
83
+ userNeeds || "- 尚未补充具体需求",
84
+ "如果信息不足,先提出必要澄清问题;不要编造用户没有确认的资料来源。",
85
+ ].join("\n");
86
+ };
87
+
88
+ const buildNextQuestion = (draft: GuidedAgentDraft) => {
89
+ if (!draft.name || !draft.prompt) return QUESTIONS[1];
90
+ if (draft.capabilityIds.length === 0) return QUESTIONS[2];
91
+ if (!draft.introduction) return QUESTIONS[3];
92
+ if (draft.unresolved.includes("公开状态")) return QUESTIONS[4];
93
+ return "配置草稿已经可创建。你还想补充哪些资料、语气或使用边界?";
94
+ };
95
+
96
+ const createAssistantDraft = (
97
+ messages: GuidedAgentAssistantMessage[],
98
+ current: GuidedAgentDraft
99
+ ) => {
100
+ const userText = messages
101
+ .filter((message) => message.role === "user")
102
+ .map((message) => message.content)
103
+ .join("\n");
104
+ const capabilityIds = Array.from(
105
+ new Set([...current.capabilityIds, ...inferCapabilityIds(userText)])
106
+ );
107
+ const name = current.name || inferName(userText);
108
+ const isPublic =
109
+ current.isPublic ||
110
+ textIncludes(userText.toLowerCase(), ["公开", "分享", "public", "给别人"]);
111
+ const unresolved = [
112
+ !name ? "name" : "",
113
+ !userText.trim() ? "prompt" : "",
114
+ capabilityIds.length === 0 ? "能力" : "",
115
+ current.introduction || userText.length > 24 ? "" : "introduction",
116
+ textIncludes(userText.toLowerCase(), ["公开", "私有", "private", "public"]) ? "" : "公开状态",
117
+ ].filter(Boolean);
118
+ const next: GuidedAgentDraft = mergeGuidedAgentDraft(current, {
119
+ name,
120
+ introduction:
121
+ current.introduction ||
122
+ (userText.trim()
123
+ ? `面向你的需求,帮助整理、判断并执行:${userText.trim().slice(0, 70)}`
124
+ : ""),
125
+ promptSummary: userText.trim()
126
+ ? `围绕“${userText.trim().slice(0, 46)}”提供结构化帮助,必要时先追问。`
127
+ : "",
128
+ capabilityIds,
129
+ toolIds: mapCapabilityIdsToToolIds(capabilityIds),
130
+ isPublic,
131
+ tags: Array.from(new Set([...current.tags, ...capabilityIds])),
132
+ unresolved,
133
+ });
134
+ return mergeGuidedAgentDraft(next, {
135
+ prompt: buildPrompt(messages, next),
136
+ });
137
+ };
138
+
139
+ const createId = () => `${Date.now()}-${Math.random().toString(16).slice(2)}`;
140
+
141
+ export const useGuidedAgentCreation = (initialDraft?: Partial<GuidedAgentDraft> | null) => {
142
+ const [draft, setDraft] = React.useState<GuidedAgentDraft>(() =>
143
+ initialDraft ? mergeGuidedAgentDraft(EMPTY_DRAFT, initialDraft) : EMPTY_DRAFT
144
+ );
145
+ const [messages, setMessages] = React.useState<GuidedAgentAssistantMessage[]>([
146
+ {
147
+ id: "assistant-initial",
148
+ role: "assistant",
149
+ content: initialDraft?.name
150
+ ? "我已经带入了当前对话整理出的草稿。你可以继续补充要求,或直接预览并创建。"
151
+ : QUESTIONS[0],
152
+ },
153
+ ]);
154
+ const [input, setInput] = React.useState("");
155
+ const [isThinking, setIsThinking] = React.useState(false);
156
+ const [error, setError] = React.useState<string | null>(null);
157
+
158
+ const submit = React.useCallback(async () => {
159
+ const content = input.trim();
160
+ if (!content || isThinking) return;
161
+
162
+ setInput("");
163
+ setError(null);
164
+ setIsThinking(true);
165
+
166
+ const userMessage: GuidedAgentAssistantMessage = {
167
+ id: createId(),
168
+ role: "user",
169
+ content,
170
+ };
171
+
172
+ try {
173
+ const nextMessages = [...messages, userMessage];
174
+ const nextDraft = createAssistantDraft(nextMessages, draft);
175
+ const assistantMessage: GuidedAgentAssistantMessage = {
176
+ id: createId(),
177
+ role: "assistant",
178
+ content: buildNextQuestion(nextDraft),
179
+ };
180
+ setMessages([...nextMessages, assistantMessage]);
181
+ setDraft(nextDraft);
182
+ } catch {
183
+ setError("暂时没能整理这段需求,请换一种说法再试。");
184
+ } finally {
185
+ setIsThinking(false);
186
+ }
187
+ }, [draft, input, isThinking, messages]);
188
+
189
+ const toggleReference = React.useCallback((dbKey: string, selected: boolean) => {
190
+ setDraft((current) =>
191
+ mergeGuidedAgentDraft(current, {
192
+ references: current.references.map((reference) =>
193
+ reference.dbKey === dbKey ? { ...reference, selected } : reference
194
+ ),
195
+ })
196
+ );
197
+ }, []);
198
+
199
+ const validation = validateGuidedAgentDraft(draft);
200
+ const formInitialValues = React.useMemo(
201
+ () => buildAgentFormDataFromGuidedDraft(draft),
202
+ [draft]
203
+ );
204
+
205
+ return {
206
+ draft,
207
+ messages,
208
+ input,
209
+ setInput,
210
+ submit,
211
+ isThinking,
212
+ error,
213
+ validation,
214
+ formInitialValues,
215
+ toggleReference,
216
+ };
217
+ };
package/ai/ai.locale.ts CHANGED
@@ -27,6 +27,9 @@ export default {
27
27
  textOnly: "Text-only",
28
28
  details: "Details",
29
29
  price: "Price",
30
+ modelCost: "Model cost",
31
+ inputCostPerMillionTokens: "Input",
32
+ outputCostPerMillionTokens: "Output",
30
33
  referencePricePerTurn: "Per-turn reference price",
31
34
  defaultImageProfileEstimate: "Default profile estimate",
32
35
  perMillionTokens: "per 1M tokens",
@@ -251,7 +254,7 @@ export default {
251
254
  description: "Create a new Agent (intelligent assistant/app) based on the given configuration and return the complete Agent information.",
252
255
  params: {
253
256
  name: "The name of the Agent, e.g., 'Meeting Minutes Assistant'.",
254
- model: "The model name to use. Logic: Default 'xiaomi/mimo-v2.5-pro'. Complex tasks: 'openai/gpt-5.2'. Cost-saving: 'openai/gpt-5.1'. Aesthetic/Creative: 'gemini-3-pro-preview'. Long Context/Fast: 'x-ai/grok-4.1-fast'. Image Generation: 'gemini-3-pro-image-preview' (Best/Expensive) or 'gemini-2.5-flash-image' (Ordinary).",
257
+ model: "The model name to use. Logic: Default 'xiaomi/mimo-v2.5-pro'. Complex tasks: 'openai/gpt-5.2'. Cost-saving: 'openai/gpt-5.1'. Aesthetic/Creative: 'gemini-3-pro-preview'. Reliable low-cost summary/reasoning: 'deepseek-v4-pro'. Image Generation: 'gemini-3-pro-image-preview' (Best/Expensive) or 'gemini-2.5-flash-image' (Ordinary).",
255
258
  provider: "The model provider identifier, must match the selected model, e.g., 'openrouter', 'deepseek', 'fireworks', 'mistral', etc.",
256
259
  prompt: "System prompt/instruction describing the Agent's role, tone, and usage.",
257
260
  introduction: "Introduction displayed in the app market or list, explanatory text for end users.",
@@ -318,6 +321,9 @@ export default {
318
321
  textOnly: "纯文本",
319
322
  details: "详细信息",
320
323
  price: "价格",
324
+ modelCost: "模型成本",
325
+ inputCostPerMillionTokens: "输入",
326
+ outputCostPerMillionTokens: "输出",
321
327
  referencePricePerTurn: "按次参考价",
322
328
  defaultImageProfileEstimate: "默认档参考价",
323
329
  perMillionTokens: "每百万Token",
@@ -539,7 +545,7 @@ export default {
539
545
  description: "根据给定配置创建一个新的 Agent(智能体 / 应用),并返回完整的 Agent 信息。",
540
546
  params: {
541
547
  name: "Agent 的名称,例如:'会议纪要助手'。",
542
- model: "默认使用 'xiaomi/mimo-v2.5-pro'。复杂任务: 'openai/gpt-5.2'。省成本但复杂: 'openai/gpt-5.1'。追求美观/创意: 'gemini-3-pro-preview'。长上下文/快速: 'x-ai/grok-4.1-fast'。生成图片: 贵/高质量选 'gemini-3-pro-image-preview',普通选 'gemini-2.5-flash-image'。",
548
+ model: "默认使用 'xiaomi/mimo-v2.5-pro'。复杂任务: 'openai/gpt-5.2'。省成本但复杂: 'openai/gpt-5.1'。追求美观/创意: 'gemini-3-pro-preview'。低成本摘要/推理: 'deepseek-v4-pro'。生成图片: 贵/高质量选 'gemini-3-pro-image-preview',普通选 'gemini-2.5-flash-image'。",
543
549
  provider: "模型提供方标识,必须与所选模型相匹配,例如 'openrouter'、'deepseek'、'fireworks'、'mistral' 等。",
544
550
  prompt: "系统提示词 / 指令,描述这个 Agent 的角色、语气和使用方式。",
545
551
  introduction: "在应用市场或列表中展示的简介,面向终端用户的说明文案。",
@@ -606,6 +612,9 @@ export default {
606
612
  textOnly: "純文字",
607
613
  details: "詳細資訊",
608
614
  price: "價格",
615
+ modelCost: "模型成本",
616
+ inputCostPerMillionTokens: "輸入",
617
+ outputCostPerMillionTokens: "輸出",
609
618
  referencePricePerTurn: "按次參考價",
610
619
  defaultImageProfileEstimate: "預設檔參考價",
611
620
  perMillionTokens: "每百萬Token",
@@ -852,6 +861,9 @@ export default {
852
861
  textOnly: "テキストのみ",
853
862
  details: "詳細",
854
863
  price: "価格",
864
+ modelCost: "モデルコスト",
865
+ inputCostPerMillionTokens: "入力",
866
+ outputCostPerMillionTokens: "出力",
855
867
  referencePricePerTurn: "回ごとの参考価格",
856
868
  defaultImageProfileEstimate: "デフォルト構成の参考価格",
857
869
  perMillionTokens: "100万トークンあたり",
@@ -160,16 +160,19 @@ export type CompletionMeta = {
160
160
  usage?: any;
161
161
  };
162
162
 
163
+ const STREAM_READ_TIMEOUT_MS = 45_000;
164
+
163
165
  const logQuickChatPerfStage = (
164
166
  startedAt: number | undefined,
165
167
  stage: string,
166
168
  details?: Record<string, unknown>
167
169
  ) => {
168
170
  if (!startedAt) return;
171
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
169
172
  console.info("[QuickChatPerf]", {
170
173
  stage,
171
- elapsedMs: Date.now() - startedAt,
172
- ...(typeof performance !== "undefined" ? { atMs: performance.now() } : {}),
174
+ elapsedMs: now - startedAt,
175
+ ...(typeof performance !== "undefined" ? { atMs: now } : {}),
173
176
  ...(details ?? {}),
174
177
  });
175
178
  };
@@ -412,6 +415,28 @@ async function processAccumulatedToolCalls(
412
415
  };
413
416
  }
414
417
 
418
+ async function readStreamChunkWithTimeout(
419
+ reader: ReadableStreamDefaultReader<Uint8Array>
420
+ ) {
421
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
422
+ try {
423
+ return await Promise.race([
424
+ reader.read(),
425
+ new Promise<ReadableStreamReadResult<Uint8Array>>((_, reject) => {
426
+ timeoutId = setTimeout(() => {
427
+ reject(
428
+ new Error(
429
+ `模型响应流 ${Math.round(STREAM_READ_TIMEOUT_MS / 1000)} 秒内没有返回新内容`
430
+ )
431
+ );
432
+ }, STREAM_READ_TIMEOUT_MS);
433
+ }),
434
+ ]);
435
+ } finally {
436
+ if (timeoutId) clearTimeout(timeoutId);
437
+ }
438
+ }
439
+
415
440
  /**
416
441
  * 处理「流结束」场景(done === true)
417
442
  */
@@ -608,7 +633,7 @@ export const sendOpenAICompletionsRequest = async ({
608
633
  let loggedFirstVisibleDelta = false;
609
634
 
610
635
  while (true) {
611
- const { done, value } = await reader.read();
636
+ const { done, value } = await readStreamChunkWithTimeout(reader);
612
637
 
613
638
  if (done) {
614
639
  const completion = await handleStreamCompletion(
@@ -48,10 +48,11 @@ const logQuickChatPerfStage = (
48
48
  details?: Record<string, unknown>
49
49
  ) => {
50
50
  if (!startedAt) return;
51
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
51
52
  console.info("[QuickChatPerf]", {
52
53
  stage,
53
- elapsedMs: Date.now() - startedAt,
54
- ...(typeof performance !== "undefined" ? { atMs: performance.now() } : {}),
54
+ elapsedMs: now - startedAt,
55
+ ...(typeof performance !== "undefined" ? { atMs: now } : {}),
55
56
  ...(details ?? {}),
56
57
  });
57
58
  };
@@ -209,6 +209,20 @@ export const formatPriceAmount = (amount: number): string => {
209
209
  return amount.toFixed(6);
210
210
  };
211
211
 
212
+ export const formatModelCostPerMillion = (
213
+ value?: number,
214
+ creditsUnit = "积分"
215
+ ): string => {
216
+ if (typeof value !== "number" || !Number.isFinite(value)) return "未知";
217
+ if (value === 0) return "免费";
218
+ const decimals = Math.abs(value) < 1 ? 3 : 2;
219
+ const amount = value
220
+ .toFixed(decimals)
221
+ .replace(/\.0+$/, "")
222
+ .replace(/(\.\d*?[1-9])0+$/, "$1");
223
+ return `${amount} ${creditsUnit}`;
224
+ };
225
+
212
226
  export const formatCompactTurnPrice = (
213
227
  amount: number
214
228
  ): CompactTurnPriceDisplay => {
package/ai/llm/kimi.ts CHANGED
@@ -7,6 +7,9 @@ export const KIMI_PLATFORM_FALLBACK_STATUSES = [401, 402, 429, 500, 502, 503, 50
7
7
  export const isFireworksKimiModel = (model?: string | null): boolean =>
8
8
  model === FIREWORKS_KIMI_LATEST_MODEL || model === FIREWORKS_KIMI_CURRENT_MODEL;
9
9
 
10
+ export const isDeepInfraKimiModel = (model?: string | null): boolean =>
11
+ model === DEEPINFRA_KIMI_FALLBACK_MODEL;
12
+
10
13
  export const resolveFireworksKimiModel = (model?: string | null): string => {
11
14
  if (model === FIREWORKS_KIMI_LATEST_MODEL) {
12
15
  return FIREWORKS_KIMI_CURRENT_MODEL;
@@ -393,6 +393,7 @@ export async function getModelAvatarComponent(
393
393
  if (cliProviderKey === "copilot") return get("copilot");
394
394
  if (cliProviderKey === "codex") return get("openai");
395
395
  if (cliProviderKey === "claude") return get("claude");
396
+ if (cliProviderKey === "agy") return get("gemini");
396
397
 
397
398
  // ── 按 model 名称匹配(优先,处理 openrouter/deepinfra 等聚合场景) ──
398
399
  if (m.includes("claude")) return get("claude");
@@ -267,7 +267,7 @@ export function getApiEndpoint(agent: Agent): string {
267
267
  }
268
268
 
269
269
  /* custom provider / apiSource=custom 但未给 URL 的兜底
270
- * 注意:apiSource="custom" 的 agent(如 kimi-k2.5)不需要 provider 字段,
270
+ * 注意:apiSource="custom" 的 agent 不需要 provider 字段,
271
271
  * 使用 customProviderUrl + 自己的 apiKey,上面已经 return 了。
272
272
  * 只有 customProviderUrl 为空时才会走到这里报错。
273
273
  */
@@ -156,8 +156,11 @@ export type TaskRunReview = {
156
156
  export type TaskRunAutonomy = "manual" | "supervised" | "auto24h";
157
157
 
158
158
  export type TaskRunBudgetRule = {
159
+ agentKey?: string;
159
160
  provider?: string;
160
161
  model?: string;
162
+ customProviderUrl?: string;
163
+ apiKeyHeader?: string;
161
164
  freeUntil?: string;
162
165
  dailyUsdLimit?: number;
163
166
  hardStopAfter?: string;
@@ -249,20 +252,31 @@ export const DEFAULT_TASK_RUN_CONTROL_POLICY: TaskRunControlPolicy = {
249
252
  budget: {
250
253
  modelBudget: {
251
254
  projectManager: {
255
+ agentKey: "agent-b2e06f801f-01MIMO25MONTH00000001PGT4X",
252
256
  provider: "custom",
253
- model: "kimi-k2.5",
254
- note: "Default low-cost PM routing.",
257
+ model: "mimo-v2.5-pro",
258
+ customProviderUrl: "https://token-plan-cn.xiaomimimo.com/v1",
259
+ apiKeyHeader: "api-key",
260
+ freeUntil: "2026-05-31",
261
+ note: "Default PM and execution routing uses the monthly Mimo custom-key pool; medium intelligence, no marginal cost through the current month.",
255
262
  },
256
263
  frontend: {
257
- model: "kimi-2.6",
258
- freeUntil: "2026-05-22",
259
- dailyUsdLimit: 1,
260
- note: "Frontend implementation should prefer Kimi 2.6 while the free window is active.",
264
+ agentKey: "agent-b2e06f801f-01MIMO25MONTH00000001PGT4X",
265
+ provider: "custom",
266
+ model: "mimo-v2.5-pro",
267
+ customProviderUrl: "https://token-plan-cn.xiaomimimo.com/v1",
268
+ apiKeyHeader: "api-key",
269
+ freeUntil: "2026-05-31",
270
+ note: "Frontend implementation uses the monthly Mimo custom-key pool by default; escalate to Codex only for quality gates or hard unblocks.",
261
271
  },
262
272
  mimoCustomKey: {
263
- provider: "mimo",
273
+ agentKey: "agent-b2e06f801f-01MIMO25MONTH00000001PGT4X",
274
+ provider: "custom",
275
+ model: "mimo-v2.5-pro",
276
+ customProviderUrl: "https://token-plan-cn.xiaomimimo.com/v1",
277
+ apiKeyHeader: "api-key",
264
278
  freeUntil: "2026-05-31",
265
- note: "Free-use window before normal budget enforcement.",
279
+ note: "Use the existing monthly Mimo custom-key agent/config for high-token routine implementation, long-context reads, table/doc processing, and low-risk probes.",
266
280
  },
267
281
  },
268
282
  },
@@ -858,11 +872,11 @@ export function recordTaskRunReview(args: {
858
872
  meta.review = review;
859
873
  let nextMeta = meta;
860
874
  const targetWorkItemId = args.targetWorkItemId ?? meta.review?.targetWorkItemId;
861
- if (args.status === "needs_changes" && targetWorkItemId) {
875
+ if ((args.status === "needs_changes" || args.status === "blocked") && targetWorkItemId) {
862
876
  nextMeta = updateTaskRunWorkItem({
863
877
  meta: nextMeta,
864
878
  id: targetWorkItemId,
865
- status: "needs_changes",
879
+ status: args.status,
866
880
  artifactRefs: args.artifactIds,
867
881
  dialogIds: args.dialogId ? [args.dialogId] : undefined,
868
882
  now,
@@ -64,6 +64,7 @@ export type ModelUsageQueryResult = {
64
64
  threshold: {
65
65
  costCredits: number | null;
66
66
  costUsd: number | null;
67
+ usedPercent: number | null;
67
68
  exceeded: boolean;
68
69
  };
69
70
  };
@@ -83,6 +84,17 @@ const toNumber = (value: unknown): number =>
83
84
 
84
85
  const round6 = (value: number): number => Number(value.toFixed(6));
85
86
 
87
+ const clampPercent = (value: number): number =>
88
+ Math.max(0, Math.min(100, Math.round(value)));
89
+
90
+ const calculateThresholdUsedPercent = (
91
+ costCredits: number,
92
+ thresholdCredits: number | null,
93
+ ): number | null => {
94
+ if (thresholdCredits == null || thresholdCredits <= 0) return null;
95
+ return clampPercent((costCredits / thresholdCredits) * 100);
96
+ };
97
+
86
98
  const normalizeDateKey = (value: unknown): string | null => {
87
99
  if (typeof value !== "string" || !value.trim()) return null;
88
100
  const trimmed = value.trim();
@@ -227,6 +239,10 @@ export async function queryModelUsage(
227
239
  : thresholdCredits == null
228
240
  ? null
229
241
  : round6(thresholdCredits / creditsPerUsd);
242
+ const thresholdUsedPercent = calculateThresholdUsedPercent(
243
+ total.costCredits,
244
+ thresholdCredits,
245
+ );
230
246
 
231
247
  return {
232
248
  scope,
@@ -246,6 +262,7 @@ export async function queryModelUsage(
246
262
  threshold: {
247
263
  costCredits: thresholdCredits,
248
264
  costUsd: thresholdUsd,
265
+ usedPercent: thresholdUsedPercent,
249
266
  exceeded: thresholdCredits == null ? false : total.costCredits > thresholdCredits,
250
267
  },
251
268
  };
@@ -8,6 +8,10 @@ import {
8
8
  createAgentToolFunctionSchema,
9
9
  createAgentToolFunc,
10
10
  } from "./createAgentTool";
11
+ import {
12
+ prepareAgentDraftToolFunctionSchema,
13
+ prepareAgentDraftToolFunc,
14
+ } from "./prepareAgentDraftTool";
11
15
  import {
12
16
  createSkillAgentToolFunctionSchema,
13
17
  createSkillAgentToolFunc,
@@ -48,6 +52,23 @@ import {
48
52
  import type { ToolDefinition } from "../index";
49
53
 
50
54
  export const agentToolDefinitions: ToolDefinition[] = [
55
+ {
56
+ id: "prepareAgentDraft",
57
+ schema: prepareAgentDraftToolFunctionSchema,
58
+ executor: prepareAgentDraftToolFunc,
59
+ description: {
60
+ name: "prepareAgentDraft",
61
+ description:
62
+ "整理 Agent 创建草稿并交给用户预览确认,不创建真实 Agent。",
63
+ category: "计划与编排",
64
+ },
65
+ behavior: "data",
66
+ uiGroup: "agent",
67
+ capability: "general",
68
+ riskLevel: "low",
69
+ costLevel: "low",
70
+ defaultConsent: "auto",
71
+ },
51
72
  {
52
73
  id: "createAgent",
53
74
  schema: createAgentToolFunctionSchema,