nolo-cli 0.1.22 → 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 (128) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/agentRecordConfig.ts +4 -0
  3. package/agent-runtime/hostAdapter.ts +2 -0
  4. package/agent-runtime/hybridRecordStore.ts +30 -9
  5. package/agent-runtime/index.ts +7 -0
  6. package/agent-runtime/localLoop.ts +14 -4
  7. package/agent-runtime/localToolPolicy.ts +22 -10
  8. package/agent-runtime/localWorkspaceTools.ts +140 -374
  9. package/agent-runtime/platformChatProvider.ts +3 -0
  10. package/agent-runtime/runtimeToolPolicy.ts +133 -0
  11. package/agent-runtime/types.ts +47 -0
  12. package/agentAliases.ts +1 -1
  13. package/agentCommandSupport.ts +77 -0
  14. package/agentInternalCommandEntries.ts +34 -0
  15. package/agentListCommands.ts +123 -0
  16. package/agentListHelpers.ts +187 -0
  17. package/agentMachineCommands.ts +402 -0
  18. package/agentRecordCommands.ts +141 -0
  19. package/agentRecordHelpers.ts +395 -0
  20. package/agentRunCommand.ts +75 -3
  21. package/agentRuntimeCommands.ts +7 -773
  22. package/ai/agent/_executeModel.ts +29 -3
  23. package/ai/agent/buildSystemPrompt.ts +17 -0
  24. package/ai/agent/cliChatClient.ts +38 -4
  25. package/ai/agent/cliExecutor.ts +304 -25
  26. package/ai/agent/createAgentSchema.ts +2 -2
  27. package/ai/agent/guidedCreation/assistant.ts +82 -0
  28. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  29. package/ai/agent/guidedCreation/draft.ts +62 -0
  30. package/ai/agent/guidedCreation/types.ts +38 -0
  31. package/ai/agent/machineRunPermissions.ts +21 -3
  32. package/ai/agent/runtimeGuidance.ts +1 -1
  33. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  34. package/ai/agent/streamAgentChatTurn.ts +593 -36
  35. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  36. package/ai/ai.locale.ts +14 -2
  37. package/ai/chat/fetchUtils.native.ts +2 -0
  38. package/ai/chat/fetchUtils.ts +2 -0
  39. package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
  40. package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
  41. package/ai/llm/getPricing.ts +14 -0
  42. package/ai/llm/kimi.ts +4 -1
  43. package/ai/llm/modelAvatar.ts +1 -0
  44. package/ai/llm/providers.ts +4 -1
  45. package/ai/llm/reasoningModels.ts +1 -0
  46. package/ai/taskRun/taskRunProtocol.ts +25 -10
  47. package/ai/token/modelUsageQuery.ts +17 -0
  48. package/ai/tools/agent/agentTools.ts +38 -0
  49. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  50. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  51. package/ai/tools/checkEnvTool.ts +52 -20
  52. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  53. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  54. package/ai/tools/index.ts +24 -14
  55. package/ai/tools/marxistsOfflineBook.ts +253 -0
  56. package/ai/tools/modelUsageTools.ts +9 -2
  57. package/ai/tools/readTool.ts +31 -0
  58. package/ai/tools/toolApiClient.ts +35 -1
  59. package/ai/tools/toolNameAliases.ts +13 -0
  60. package/cliCommandAdapters.ts +28 -0
  61. package/cliCommandDispatch.ts +66 -0
  62. package/cliCommandFactories.ts +34 -0
  63. package/cliCommandTypes.ts +49 -0
  64. package/cliEnvHelpers.ts +44 -0
  65. package/client/agentRun.test.ts +330 -24
  66. package/client/agentRun.ts +117 -35
  67. package/client/hybridRecordStore.test.ts +42 -0
  68. package/client/hybridRecordStore.ts +11 -0
  69. package/client/localRuntimeAdapter.test.ts +191 -21
  70. package/client/localRuntimeAdapter.ts +99 -10
  71. package/client/localRuntimeDryRun.test.ts +5 -2
  72. package/client/taskRunPrompt.ts +1 -0
  73. package/commandRegistry.ts +11 -61
  74. package/connector-experimental/capabilities.ts +1 -0
  75. package/database/actions/common.ts +4 -3
  76. package/database/config.ts +19 -0
  77. package/database/server/coreDataProxy.ts +43 -26
  78. package/index.ts +54 -191
  79. package/internalCommandEntries.ts +12 -0
  80. package/machineCommands.ts +688 -64
  81. package/offlineMarxistsAgentCommand.ts +314 -0
  82. package/package.json +22 -2
  83. package/render/canvas/canvasEditContext.ts +127 -0
  84. package/render/canvas/canvasRuntime.ts +57 -0
  85. package/render/canvas/canvasSnapshotParser.ts +76 -0
  86. package/render/canvas/canvasTree.ts +308 -0
  87. package/render/canvas/types.ts +46 -0
  88. package/render/layout/deleteBehavior.ts +52 -0
  89. package/render/layout/mainLayoutSidebar.ts +17 -0
  90. package/render/layout/mainLayoutViewMode.ts +56 -0
  91. package/render/layout/topbarUtils.ts +87 -0
  92. package/render/layout/useDevReloadPending.ts +30 -0
  93. package/render/page/createPageAction.ts +183 -0
  94. package/render/page/docSlice.ts +468 -0
  95. package/render/page/server/createPage.ts +174 -0
  96. package/render/page/server/handleCreatePage.ts +91 -0
  97. package/render/page/server/index.ts +4 -0
  98. package/render/page/types.ts +17 -0
  99. package/render/page/useKeyboardSave.ts +48 -0
  100. package/render/styles/zIndex.ts +12 -0
  101. package/render/surf/WeatherIconStyles.ts +17 -0
  102. package/render/surf/color.ts +9 -0
  103. package/render/surf/config.ts +46 -0
  104. package/render/surf/screens/style.ts +1 -0
  105. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  106. package/render/surf/utils/groupedWeatherData.ts +32 -0
  107. package/render/surf/weatherUtils.ts +50 -0
  108. package/render/table/activityColumns.ts +6 -0
  109. package/render/table/createTableAction.ts +270 -0
  110. package/render/table/deleteTableAction.ts +129 -0
  111. package/render/table/fetchAndCacheTableRows.ts +174 -0
  112. package/render/table/tableSlice.ts +1106 -0
  113. package/render/table/tableView.ts +289 -0
  114. package/render/table/toolValueUtils.ts +363 -0
  115. package/render/table/types.ts +252 -0
  116. package/render/table/useCreateTable.ts +72 -0
  117. package/render/table/useTable.ts +61 -0
  118. package/render/table/utils/tableSerialization.ts +50 -0
  119. package/render/web/elements/artifactPreviewCode.ts +43 -0
  120. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  121. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  122. package/render/web/elements/mermaidPreview.ts +21 -0
  123. package/render/web/ui/useInlineEdit.ts +135 -0
  124. package/runtimeDoctorCommands.ts +118 -0
  125. package/scriptCommandEntries.ts +71 -0
  126. package/systemInternalCommandEntries.ts +71 -0
  127. package/tableCommands.ts +42 -5
  128. package/workflowInternalCommandEntries.ts +11 -0
@@ -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;
@@ -193,6 +196,7 @@ export type TaskRunControlPolicy = {
193
196
  autonomy?: TaskRunAutonomy;
194
197
  nextWakeAt?: string;
195
198
  pausedReason?: string;
199
+ runtimeToolPolicy?: Record<string, unknown>;
196
200
  budget?: {
197
201
  dailyUsdLimit?: number;
198
202
  modelBudget?: Record<string, TaskRunBudgetRule>;
@@ -248,20 +252,31 @@ export const DEFAULT_TASK_RUN_CONTROL_POLICY: TaskRunControlPolicy = {
248
252
  budget: {
249
253
  modelBudget: {
250
254
  projectManager: {
255
+ agentKey: "agent-b2e06f801f-01MIMO25MONTH00000001PGT4X",
251
256
  provider: "custom",
252
- model: "kimi-k2.5",
253
- 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.",
254
262
  },
255
263
  frontend: {
256
- model: "kimi-2.6",
257
- freeUntil: "2026-05-22",
258
- dailyUsdLimit: 1,
259
- 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.",
260
271
  },
261
272
  mimoCustomKey: {
262
- 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",
263
278
  freeUntil: "2026-05-31",
264
- 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.",
265
280
  },
266
281
  },
267
282
  },
@@ -857,11 +872,11 @@ export function recordTaskRunReview(args: {
857
872
  meta.review = review;
858
873
  let nextMeta = meta;
859
874
  const targetWorkItemId = args.targetWorkItemId ?? meta.review?.targetWorkItemId;
860
- if (args.status === "needs_changes" && targetWorkItemId) {
875
+ if ((args.status === "needs_changes" || args.status === "blocked") && targetWorkItemId) {
861
876
  nextMeta = updateTaskRunWorkItem({
862
877
  meta: nextMeta,
863
878
  id: targetWorkItemId,
864
- status: "needs_changes",
879
+ status: args.status,
865
880
  artifactRefs: args.artifactIds,
866
881
  dialogIds: args.dialogId ? [args.dialogId] : undefined,
867
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,
@@ -28,6 +32,10 @@ import {
28
32
  streamParallelAgentsFunctionSchema,
29
33
  streamParallelAgentsFunc,
30
34
  } from "./streamParallelAgentsTool";
35
+ import {
36
+ startAgentDialogFunctionSchema,
37
+ startAgentDialogFunc,
38
+ } from "./startAgentDialogTool";
31
39
  import {
32
40
  createDialogFunctionSchema,
33
41
  createDialogFunc,
@@ -44,6 +52,23 @@ import {
44
52
  import type { ToolDefinition } from "../index";
45
53
 
46
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
+ },
47
72
  {
48
73
  id: "createAgent",
49
74
  schema: createAgentToolFunctionSchema,
@@ -151,6 +176,19 @@ export const agentToolDefinitions: ToolDefinition[] = [
151
176
  behavior: "orchestrator",
152
177
  uiGroup: "agent",
153
178
  },
179
+ {
180
+ id: "startAgentDialog",
181
+ schema: startAgentDialogFunctionSchema,
182
+ executor: startAgentDialogFunc,
183
+ description: {
184
+ name: "startAgentDialog",
185
+ description:
186
+ "启动一个子 Agent dialog,返回 childDialogId,供调用方后续查询、接管或投影状态。",
187
+ category: "计划与编排",
188
+ },
189
+ behavior: "orchestrator",
190
+ uiGroup: "agent",
191
+ },
154
192
  {
155
193
  id: "createDialog",
156
194
  schema: createDialogFunctionSchema,
@@ -0,0 +1,159 @@
1
+ import { DEFAULT_MODEL } from "ai/llm/providers";
2
+ import {
3
+ GUIDED_AGENT_CAPABILITIES,
4
+ mapCapabilityIdsToToolIds,
5
+ } from "ai/agent/guidedCreation/capabilities";
6
+ import type {
7
+ GuidedAgentCapabilityId,
8
+ GuidedAgentDraft,
9
+ GuidedAgentReferenceChoice,
10
+ } from "ai/agent/guidedCreation/types";
11
+
12
+ export type PrepareAgentDraftToolArgs = Partial<GuidedAgentDraft>;
13
+
14
+ const capabilityIds = new Set(Object.keys(GUIDED_AGENT_CAPABILITIES));
15
+
16
+ const trim = (value: unknown) =>
17
+ typeof value === "string" ? value.trim() : "";
18
+
19
+ const stringArray = (value: unknown): string[] =>
20
+ Array.isArray(value)
21
+ ? value.map((item) => trim(item)).filter(Boolean)
22
+ : [];
23
+
24
+ const guidedCapabilityArray = (value: unknown): GuidedAgentCapabilityId[] =>
25
+ stringArray(value).filter((id): id is GuidedAgentCapabilityId =>
26
+ capabilityIds.has(id)
27
+ );
28
+
29
+ const referencesArray = (value: unknown): GuidedAgentReferenceChoice[] =>
30
+ Array.isArray(value)
31
+ ? value
32
+ .filter((item): item is Record<string, unknown> =>
33
+ !!item && typeof item === "object" && !Array.isArray(item)
34
+ )
35
+ .map((item) => ({
36
+ dbKey: trim(item.dbKey),
37
+ title: trim(item.title),
38
+ type: item.type === "instruction" ? "instruction" : "knowledge",
39
+ selected: item.selected === true,
40
+ ...(trim(item.reason) ? { reason: trim(item.reason) } : {}),
41
+ }))
42
+ .filter((item) => item.dbKey)
43
+ : [];
44
+
45
+ const buildPrompt = (args: PrepareAgentDraftToolArgs) => {
46
+ const prompt = trim(args.prompt);
47
+ if (prompt) return prompt;
48
+ const name = trim(args.name) || "用户的专属 AI";
49
+ const summary = trim(args.promptSummary) || trim(args.introduction);
50
+ return [
51
+ `你是${name}。`,
52
+ summary || "你需要理解用户目标,并给出清晰、可执行、有边界的帮助。",
53
+ "如果信息不足,先提出必要澄清问题;不要编造用户没有确认的资料来源。",
54
+ ].join("\n");
55
+ };
56
+
57
+ export const buildPreparedAgentDraft = (
58
+ args: PrepareAgentDraftToolArgs
59
+ ): GuidedAgentDraft => {
60
+ const selectedCapabilities = guidedCapabilityArray(args.capabilityIds);
61
+ const providedToolIds = stringArray(args.toolIds);
62
+ const mappedToolIds = mapCapabilityIdsToToolIds(selectedCapabilities);
63
+ return {
64
+ name: trim(args.name),
65
+ introduction: trim(args.introduction),
66
+ prompt: buildPrompt(args),
67
+ promptSummary: trim(args.promptSummary),
68
+ provider: trim(args.provider) || DEFAULT_MODEL.provider,
69
+ model: trim(args.model) || DEFAULT_MODEL.name,
70
+ isPublic: args.isPublic === true,
71
+ capabilityIds: selectedCapabilities,
72
+ toolIds: Array.from(new Set([...providedToolIds, ...mappedToolIds])),
73
+ references: referencesArray(args.references),
74
+ tags: stringArray(args.tags),
75
+ unresolved: stringArray(args.unresolved),
76
+ };
77
+ };
78
+
79
+ export const prepareAgentDraftToolFunctionSchema = {
80
+ name: "prepareAgentDraft",
81
+ description:
82
+ "整理一个可预览的 Agent 创建草稿。只生成草稿,不创建真实 Agent 记录。",
83
+ parameters: {
84
+ type: "object",
85
+ properties: {
86
+ name: { type: "string", description: "建议的 Agent 名称。" },
87
+ introduction: { type: "string", description: "面向用户展示的简介。" },
88
+ prompt: { type: "string", description: "完整系统 prompt 草稿。" },
89
+ promptSummary: { type: "string", description: "prompt 的简短摘要。" },
90
+ provider: { type: "string", description: "建议 provider。" },
91
+ model: { type: "string", description: "建议模型名称。" },
92
+ isPublic: { type: "boolean", description: "是否建议公开。" },
93
+ capabilityIds: {
94
+ type: "array",
95
+ items: {
96
+ type: "string",
97
+ enum: Object.keys(GUIDED_AGENT_CAPABILITIES),
98
+ },
99
+ description: "已确认或高度确定的能力标签。",
100
+ },
101
+ toolIds: {
102
+ type: "array",
103
+ items: { type: "string" },
104
+ description: "可选的底层工具名;通常由 capabilityIds 推导。",
105
+ },
106
+ references: {
107
+ type: "array",
108
+ items: {
109
+ type: "object",
110
+ properties: {
111
+ dbKey: { type: "string" },
112
+ title: { type: "string" },
113
+ type: { type: "string", enum: ["knowledge", "instruction"] },
114
+ selected: { type: "boolean" },
115
+ reason: { type: "string" },
116
+ },
117
+ required: ["dbKey"],
118
+ },
119
+ description: "建议挂载的知识引用,默认等待用户确认。",
120
+ },
121
+ tags: {
122
+ type: "array",
123
+ items: { type: "string" },
124
+ },
125
+ unresolved: {
126
+ type: "array",
127
+ items: { type: "string" },
128
+ description: "仍需确认的字段或问题。",
129
+ },
130
+ },
131
+ required: ["name", "introduction", "promptSummary"],
132
+ },
133
+ };
134
+
135
+ export async function prepareAgentDraftToolFunc(
136
+ args: PrepareAgentDraftToolArgs
137
+ ): Promise<{
138
+ rawData: { draft: GuidedAgentDraft; createUrl: string };
139
+ displayData: string;
140
+ }> {
141
+ const draft = buildPreparedAgentDraft(args);
142
+ const missing = draft.unresolved.length
143
+ ? `\n- 待确认: ${draft.unresolved.join(", ")}`
144
+ : "";
145
+ return {
146
+ rawData: {
147
+ draft,
148
+ createUrl: "/create/agent",
149
+ },
150
+ displayData: [
151
+ `Agent 草稿: ${draft.name || "未命名 AI"}`,
152
+ draft.promptSummary ? `- 摘要: ${draft.promptSummary}` : "",
153
+ draft.capabilityIds.length
154
+ ? `- 能力: ${draft.capabilityIds.join(", ")}`
155
+ : "",
156
+ missing,
157
+ ].filter(Boolean).join("\n"),
158
+ };
159
+ }
@@ -0,0 +1,53 @@
1
+ export const startAgentDialogFunctionSchema = {
2
+ name: "startAgentDialog",
3
+ description:
4
+ "启动一个子 Agent dialog 并立即返回 childDialogId。用于通用 agent 派发/交接,不等待子 Agent 完成,也不替调用方决定任务状态。",
5
+ parameters: {
6
+ type: "object",
7
+ properties: {
8
+ targetAgentKey: {
9
+ type: "string",
10
+ description: "要启动的目标 Agent key。",
11
+ },
12
+ message: {
13
+ type: "string",
14
+ description: "发送给目标 Agent 的自然语言任务或上下文。",
15
+ },
16
+ parentDialogId: {
17
+ type: "string",
18
+ description: "可选。父 dialogId;未提供时使用当前 dialogId。",
19
+ },
20
+ subjectRefs: {
21
+ type: "array",
22
+ description: "可选。与本次子 dialog 相关的业务对象引用,保持行业中立。",
23
+ items: {
24
+ type: "object",
25
+ properties: {
26
+ kind: { type: "string" },
27
+ id: { type: "string" },
28
+ role: { type: "string" },
29
+ },
30
+ required: ["kind", "id"],
31
+ },
32
+ },
33
+ idempotencyKey: {
34
+ type: "string",
35
+ description: "可选。调用方用于去重的稳定 key;当前版本仅透传给运行上下文。",
36
+ },
37
+ serverBase: {
38
+ type: "string",
39
+ description:
40
+ "可选。目标 Agent 所在的 nolo server origin;跨域目标必须由服务端 AGENT_TOOL_ALLOWED_SERVER_BASES 放行。",
41
+ },
42
+ timeoutMs: {
43
+ type: "number",
44
+ description: "可选。子 Agent background run 的执行超时预算(毫秒)。",
45
+ },
46
+ },
47
+ required: ["targetAgentKey", "message"],
48
+ },
49
+ };
50
+
51
+ export async function startAgentDialogFunc(): Promise<never> {
52
+ throw new Error("startAgentDialog is a server-side agent tool. Use it through /api/agent/run.");
53
+ }
@@ -3,39 +3,60 @@
3
3
 
4
4
  import { callToolApi } from "./toolApiClient";
5
5
 
6
- export type CheckEnvArgs = {
7
- check?: "build" | "context";
8
- };
6
+ export type CheckEnvArgs = {
7
+ check?: "build" | "context";
8
+ key?: string;
9
+ };
9
10
 
10
11
  export const checkEnvFunctionSchema = {
11
12
  name: "checkEnv",
12
13
  description: [
13
- "执行环境检查并返回结构化结果。",
14
- "check='context' 可查看当前平台、工作目录与可用 shell;check='build' 可快速验证是否可编译。",
14
+ "执行环境检查并返回结构化结果。",
15
+ "check='context' 可查看当前平台、工作目录与可用 shell;check='build' 可快速验证是否可编译。",
16
+ "key 可检查某个本地工具,例如 key='gemini' 会探测本机 Gemini CLI 与 npm 最新版本。",
15
17
  ].join("\n"),
16
18
  parameters: {
17
19
  type: "object",
18
20
  properties: {
19
- check: {
20
- type: "string",
21
- enum: ["build", "context"],
22
- description: "检查项:context 返回当前运行环境;build 执行构建检查。",
23
- },
24
- },
25
- },
26
- };
21
+ check: {
22
+ type: "string",
23
+ enum: ["build", "context"],
24
+ description: "检查项:context 返回当前运行环境;build 执行构建检查。",
25
+ },
26
+ key: {
27
+ type: "string",
28
+ enum: ["gemini", "node", "npm", "bun", "python", "git", "shell"],
29
+ description: "可选:探测某个本地工具,不执行 build。常用值如 gemini、node、npm、bun、python、git、shell。",
30
+ },
31
+ },
32
+ },
33
+ };
34
+
35
+ const formatProbeOutput = (probe: any): string => {
36
+ const status = probe?.ok ? "OK" : "FAIL";
37
+ const lines = [`- [${status}] ${probe?.label ?? "probe"}`];
38
+ const stdout = typeof probe?.stdout === "string" ? probe.stdout.trim() : "";
39
+ const stderr = typeof probe?.stderr === "string" ? probe.stderr.trim() : "";
40
+ const error = typeof probe?.error === "string" ? probe.error.trim() : "";
41
+ if (stdout) lines.push(` stdout: ${stdout}`);
42
+ if (stderr) lines.push(` stderr: ${stderr}`);
43
+ if (error) lines.push(` error: ${error}`);
44
+ return lines.join("\n");
45
+ };
27
46
 
28
47
  export async function checkEnvFunc(
29
48
  args: CheckEnvArgs,
30
49
  thunkApi: any,
31
50
  context?: { agentKey?: string }
32
51
  ): Promise<{ rawData: any; displayData: string }> {
33
- const check = args?.check === "context" ? "context" : "build";
34
-
52
+ const key = typeof args?.key === "string" ? args.key.trim().toLowerCase() : "";
53
+ const check = args?.check === "context" ? "context" : "build";
54
+ const payload = key ? { key } : { check };
55
+
35
56
  const result = await callToolApi<any>(
36
57
  thunkApi,
37
58
  "/api/check-env",
38
- { check },
59
+ payload,
39
60
  {
40
61
  withAuth: true,
41
62
  agentKey: context?.agentKey,
@@ -45,7 +66,18 @@ export async function checkEnvFunc(
45
66
  const ok = !!result?.ok;
46
67
  const exitCode = typeof result?.exitCode === "number" ? result.exitCode : -1;
47
68
 
48
- if (check === "context") {
69
+ if (result?.check === "tool") {
70
+ const probes = Array.isArray(result?.probes) ? result.probes : [];
71
+ return {
72
+ rawData: result,
73
+ displayData: [
74
+ `本地工具检查: ${result?.key ?? key}`,
75
+ ...probes.map(formatProbeOutput),
76
+ ].join("\n"),
77
+ };
78
+ }
79
+
80
+ if (check === "context") {
49
81
  const displayParts = [
50
82
  `平台: ${result?.platform ?? "unknown"}`,
51
83
  `cwd: ${result?.cwd ?? "unknown"}`,
@@ -59,9 +91,9 @@ export async function checkEnvFunc(
59
91
  rawData: result,
60
92
  displayData: `🧭 当前环境\n${displayParts.join("\n")}`,
61
93
  };
62
- }
63
-
64
- return {
94
+ }
95
+
96
+ return {
65
97
  rawData: result,
66
98
  displayData: ok
67
99
  ? `✅ 环境检查通过: ${check}`
@@ -0,0 +1,103 @@
1
+ import { convertMarxistsBookToOfflineHtml } from "./marxistsOfflineBook";
2
+
3
+ export const convertMarxistsBookToOfflineHtmlFunctionSchema = {
4
+ name: "convertMarxistsBookToOfflineHtml",
5
+ description:
6
+ "将 Marxists.org 中文旧式书籍页面转换成离线单 HTML。会从章节 URL 推导目录页,下载同目录章节、CSS 和背景图,保留原始排版并内嵌资源,适合 iPad/移动设备离线阅读。",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ startUrl: {
11
+ type: "string",
12
+ description:
13
+ "任意一个 Marxists.org 中文书籍章节 URL,例如 https://www.marxists.org/chinese/fromm/1973/01.htm。",
14
+ },
15
+ encoding: {
16
+ type: "string",
17
+ description: "源站 HTML/CSS 编码,老中文页面通常是 gb2312。默认 gb2312。",
18
+ default: "gb2312",
19
+ },
20
+ outputMode: {
21
+ type: "string",
22
+ enum: ["summary", "html"],
23
+ description:
24
+ "summary 只返回元数据和文件引用;html 同时返回完整 HTML 字符串。需要直接拿到文件内容时用 html。",
25
+ default: "summary",
26
+ },
27
+ singleFileName: {
28
+ type: "string",
29
+ description:
30
+ "生成文件建议名称,默认根据书名或 URL 生成;server-side agent 可用它作为保存文件名。",
31
+ },
32
+ maxPages: {
33
+ type: "number",
34
+ description: "最多允许抓取的同目录章节页数量,默认 80,防止误抓整个站点。",
35
+ default: 80,
36
+ },
37
+ saveAsFile: {
38
+ type: "boolean",
39
+ description:
40
+ "server-side agent 可设为 true,将 HTML 保存为 Nolo 文件并返回下载 URL;如果只需要 HTML 字符串,可设为 false。",
41
+ default: true,
42
+ },
43
+ },
44
+ required: ["startUrl"],
45
+ },
46
+ };
47
+
48
+ export type ConvertMarxistsBookToolArgs = {
49
+ startUrl: string;
50
+ encoding?: string;
51
+ outputMode?: "summary" | "html";
52
+ singleFileName?: string;
53
+ maxPages?: number;
54
+ saveAsFile?: boolean;
55
+ };
56
+
57
+ export async function convertMarxistsBookToOfflineHtmlFunc(
58
+ args: ConvertMarxistsBookToolArgs,
59
+ ): Promise<{ rawData: Record<string, unknown>; displayData: string }> {
60
+ const result = await convertMarxistsBookToOfflineHtml({
61
+ startUrl: args.startUrl,
62
+ encoding: args.encoding,
63
+ maxPages: args.maxPages,
64
+ });
65
+
66
+ const rawData: Record<string, unknown> = {
67
+ title: result.title,
68
+ startUrl: result.startUrl,
69
+ indexUrl: result.indexUrl,
70
+ stylesheetUrl: result.stylesheetUrl,
71
+ pages: result.pages,
72
+ validation: result.validation,
73
+ fileName: args.singleFileName,
74
+ };
75
+ if (args.outputMode === "html") {
76
+ rawData.html = result.html;
77
+ }
78
+
79
+ return {
80
+ rawData,
81
+ displayData: formatMarxistsOfflineBookDisplayData({
82
+ ...rawData,
83
+ htmlIncluded: args.outputMode === "html",
84
+ }),
85
+ };
86
+ }
87
+
88
+ export function formatMarxistsOfflineBookDisplayData(data: Record<string, unknown>): string {
89
+ const validation = (data.validation ?? {}) as any;
90
+ const pages = Array.isArray(data.pages) ? data.pages : [];
91
+ return [
92
+ "✅ 已生成离线 HTML",
93
+ `书名:${data.title || "未知"}`,
94
+ `章节数:${validation.pageCount ?? pages.length}`,
95
+ `大小:${validation.byteLength ?? "未知"} bytes`,
96
+ `资源内嵌:${validation.hasEmbeddedAssets ? "是" : "否"}`,
97
+ `残留网络 URL:${validation.hasNetworkUrls ? "是" : "否"}`,
98
+ data.fileUrl ? `文件:${data.fileUrl}` : undefined,
99
+ data.htmlIncluded ? "已在 rawData.html 返回完整 HTML。" : undefined,
100
+ ]
101
+ .filter(Boolean)
102
+ .join("\n");
103
+ }