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.
- package/README.md +25 -3
- package/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/hybridRecordStore.ts +30 -9
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +14 -4
- package/agent-runtime/localToolPolicy.ts +22 -10
- package/agent-runtime/localWorkspaceTools.ts +140 -374
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +133 -0
- package/agent-runtime/types.ts +47 -0
- package/agentAliases.ts +1 -1
- package/agentCommandSupport.ts +77 -0
- package/agentInternalCommandEntries.ts +34 -0
- package/agentListCommands.ts +123 -0
- package/agentListHelpers.ts +187 -0
- package/agentMachineCommands.ts +402 -0
- package/agentRecordCommands.ts +141 -0
- package/agentRecordHelpers.ts +395 -0
- package/agentRunCommand.ts +75 -3
- package/agentRuntimeCommands.ts +7 -773
- package/ai/agent/_executeModel.ts +29 -3
- package/ai/agent/buildSystemPrompt.ts +17 -0
- package/ai/agent/cliChatClient.ts +38 -4
- package/ai/agent/cliExecutor.ts +304 -25
- package/ai/agent/createAgentSchema.ts +2 -2
- package/ai/agent/guidedCreation/assistant.ts +82 -0
- package/ai/agent/guidedCreation/capabilities.ts +84 -0
- package/ai/agent/guidedCreation/draft.ts +62 -0
- package/ai/agent/guidedCreation/types.ts +38 -0
- package/ai/agent/machineRunPermissions.ts +21 -3
- package/ai/agent/runtimeGuidance.ts +1 -1
- package/ai/agent/server/fetchPublicAgents.ts +6 -1
- package/ai/agent/streamAgentChatTurn.ts +593 -36
- package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
- package/ai/ai.locale.ts +14 -2
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
- package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
- package/ai/llm/getPricing.ts +14 -0
- package/ai/llm/kimi.ts +4 -1
- package/ai/llm/modelAvatar.ts +1 -0
- package/ai/llm/providers.ts +4 -1
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/taskRun/taskRunProtocol.ts +25 -10
- package/ai/token/modelUsageQuery.ts +17 -0
- package/ai/tools/agent/agentTools.ts +38 -0
- package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/checkEnvTool.ts +52 -20
- package/ai/tools/convertMarxistsBookTool.ts +103 -0
- package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
- package/ai/tools/index.ts +24 -14
- package/ai/tools/marxistsOfflineBook.ts +253 -0
- package/ai/tools/modelUsageTools.ts +9 -2
- package/ai/tools/readTool.ts +31 -0
- package/ai/tools/toolApiClient.ts +35 -1
- package/ai/tools/toolNameAliases.ts +13 -0
- package/cliCommandAdapters.ts +28 -0
- package/cliCommandDispatch.ts +66 -0
- package/cliCommandFactories.ts +34 -0
- package/cliCommandTypes.ts +49 -0
- package/cliEnvHelpers.ts +44 -0
- package/client/agentRun.test.ts +330 -24
- package/client/agentRun.ts +117 -35
- package/client/hybridRecordStore.test.ts +42 -0
- package/client/hybridRecordStore.ts +11 -0
- package/client/localRuntimeAdapter.test.ts +191 -21
- package/client/localRuntimeAdapter.ts +99 -10
- package/client/localRuntimeDryRun.test.ts +5 -2
- package/client/taskRunPrompt.ts +1 -0
- package/commandRegistry.ts +11 -61
- package/connector-experimental/capabilities.ts +1 -0
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/database/server/coreDataProxy.ts +43 -26
- package/index.ts +54 -191
- package/internalCommandEntries.ts +12 -0
- package/machineCommands.ts +688 -64
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +22 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/runtimeDoctorCommands.ts +118 -0
- package/scriptCommandEntries.ts +71 -0
- package/systemInternalCommandEntries.ts +71 -0
- package/tableCommands.ts +42 -5
- package/workflowInternalCommandEntries.ts +11 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { DEFAULT_LOCAL_API_ORIGIN } from "core/localOrigins";
|
|
2
|
-
import { callToolApi } from "./toolApiClient";
|
|
1
|
+
import { DEFAULT_LOCAL_API_ORIGIN } from "core/localOrigins";
|
|
2
|
+
import { callToolApi } from "./toolApiClient";
|
|
3
|
+
import { ToolResultError } from "./toolResultError";
|
|
3
4
|
|
|
4
5
|
type ExecShellKind = "auto" | "bash" | "powershell";
|
|
5
6
|
|
|
6
|
-
type
|
|
7
|
+
type ExecShellRequestArgs = {
|
|
7
8
|
command?: string;
|
|
8
9
|
cwd?: string;
|
|
9
10
|
interactive?: boolean;
|
|
@@ -14,58 +15,7 @@ type ExecBashRequestArgs = {
|
|
|
14
15
|
close?: boolean;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
export const
|
|
18
|
-
name: "execBash",
|
|
19
|
-
description:
|
|
20
|
-
"兼容旧 agent 的 shell 执行工具。底层会调用后端 shell 执行接口;在 Windows 上可能自动回退到 PowerShell。环境不明确时,先调用 checkEnv({check:'context'}) 判断当前平台与可用 shell。支持交互式 session:先用 interactive=true 启动,再通过 sessionId + input 继续写入 stdin,或用 sessionId 单独轮询输出。仅用于本地开发环境。对 rm 等危险命令默认拦截,需显式传 unsafe: true 才会执行。不要用它执行 rg/grep 代码搜索;代码搜索请改用 codeSearch。",
|
|
21
|
-
parameters: {
|
|
22
|
-
type: "object",
|
|
23
|
-
properties: {
|
|
24
|
-
command: {
|
|
25
|
-
type: "string",
|
|
26
|
-
description:
|
|
27
|
-
"要执行的 shell 命令,例如: 'git status --short', 'bun test --watch=false'。",
|
|
28
|
-
},
|
|
29
|
-
cwd: {
|
|
30
|
-
type: "string",
|
|
31
|
-
description:
|
|
32
|
-
"可选:命令执行的工作目录(相对于 diff-server 所在机器),默认使用后端的 process.cwd()。",
|
|
33
|
-
},
|
|
34
|
-
unsafe: {
|
|
35
|
-
type: "boolean",
|
|
36
|
-
description:
|
|
37
|
-
"可选:是否允许执行被判定为危险的命令(例如 rm -rf)。默认 false;当为 false 且命令被识别为危险时,会直接拦截而不真正执行。",
|
|
38
|
-
},
|
|
39
|
-
interactive: {
|
|
40
|
-
type: "boolean",
|
|
41
|
-
description:
|
|
42
|
-
"可选:是否以交互式 session 启动命令。为 true 时接口会立即返回 sessionId,后续可继续写 stdin 或轮询输出。",
|
|
43
|
-
},
|
|
44
|
-
pty: {
|
|
45
|
-
type: "boolean",
|
|
46
|
-
description:
|
|
47
|
-
"可选:是否为交互式 session 分配伪终端(PTY)。适合 python -i、bash、node 等真正依赖终端语义的 REPL。",
|
|
48
|
-
},
|
|
49
|
-
sessionId: {
|
|
50
|
-
type: "string",
|
|
51
|
-
description:
|
|
52
|
-
"可选:已存在的交互式 sessionId。传入后可继续写入 input、查询最新输出或关闭该 session。",
|
|
53
|
-
},
|
|
54
|
-
input: {
|
|
55
|
-
type: "string",
|
|
56
|
-
description:
|
|
57
|
-
"可选:要写入 session stdin 的文本。通常需要自己补换行,例如 'y\\n' 或 'exit\\n'。",
|
|
58
|
-
},
|
|
59
|
-
close: {
|
|
60
|
-
type: "boolean",
|
|
61
|
-
description:
|
|
62
|
-
"可选:是否关闭指定 session。通常与 sessionId 搭配使用。",
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const execShellFunctionSchema = {
|
|
18
|
+
export const execShellFunctionSchema = {
|
|
69
19
|
name: "execShell",
|
|
70
20
|
description:
|
|
71
21
|
"跨平台 shell 执行工具。会在 Windows 上优先使用 PowerShell,在 Linux/macOS 上使用 bash。环境不明确时,先调用 checkEnv({check:'context'}) 判断当前平台与可用 shell。支持交互式 session:先用 interactive=true 启动,再通过 sessionId + input 继续写入 stdin,或用 sessionId 单独轮询输出。仅用于本地开发环境。对 rm 等危险命令默认拦截,需显式传 unsafe: true 才会执行。不要用它执行 rg/grep 代码搜索;代码搜索请改用 codeSearch。",
|
|
@@ -143,9 +93,9 @@ function isDangerousCommand(rawCommand: string): boolean {
|
|
|
143
93
|
}
|
|
144
94
|
|
|
145
95
|
async function requestExec(
|
|
146
|
-
args:
|
|
96
|
+
args: ExecShellRequestArgs,
|
|
147
97
|
thunkApi?: any,
|
|
148
|
-
endpoint = "/api/exec-
|
|
98
|
+
endpoint = "/api/exec-shell",
|
|
149
99
|
context?: { agentKey?: string },
|
|
150
100
|
): Promise<any> {
|
|
151
101
|
const payload = Object.fromEntries(
|
|
@@ -163,12 +113,16 @@ async function requestExec(
|
|
|
163
113
|
(typeof process !== "undefined" &&
|
|
164
114
|
(process as any).env?.DIFF_SERVER_ORIGIN) ||
|
|
165
115
|
(typeof window !== "undefined" ? window.location.origin : DEFAULT_LOCAL_API_ORIGIN);
|
|
166
|
-
const url = `${baseUrl}${endpoint}`;
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
headers
|
|
170
|
-
|
|
171
|
-
|
|
116
|
+
const url = `${baseUrl}${endpoint}`;
|
|
117
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
118
|
+
if (typeof window !== "undefined" && (window as any).__NOLO_DESKTOP__) {
|
|
119
|
+
headers["X-Nolo-Desktop-Tool"] = "1";
|
|
120
|
+
}
|
|
121
|
+
const res = await fetch(url, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers,
|
|
124
|
+
body: JSON.stringify(payload),
|
|
125
|
+
});
|
|
172
126
|
|
|
173
127
|
const text = await res.text();
|
|
174
128
|
try {
|
|
@@ -178,7 +132,7 @@ async function requestExec(
|
|
|
178
132
|
}
|
|
179
133
|
}
|
|
180
134
|
|
|
181
|
-
function formatExecResult(prefixLabel: string, args: any, json: any, trimmedCommand: string, sessionId?: string) {
|
|
135
|
+
function formatExecResult(prefixLabel: string, args: any, json: any, trimmedCommand: string, sessionId?: string) {
|
|
182
136
|
const prefix = json?.ok === false || json?.error ? `[${prefixLabel} 失败]` : `[${prefixLabel} 成功]`;
|
|
183
137
|
const stdout = json.stdout ?? "";
|
|
184
138
|
const stderr = json.stderr ?? "";
|
|
@@ -219,10 +173,20 @@ function formatExecResult(prefixLabel: string, args: any, json: any, trimmedComm
|
|
|
219
173
|
...json,
|
|
220
174
|
},
|
|
221
175
|
displayData: displayParts.join("\n"),
|
|
222
|
-
};
|
|
223
|
-
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function throwExecToolError(toolName: "execShell", message: string, rawData?: any): never {
|
|
180
|
+
const displayData = `${toolName} 调用失败:${message}`;
|
|
181
|
+
throw new ToolResultError(displayData, {
|
|
182
|
+
code: "EXEC_TOOL_FAILED",
|
|
183
|
+
rawData: rawData ?? { error: displayData },
|
|
184
|
+
displayData,
|
|
185
|
+
retryable: true,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
224
188
|
|
|
225
|
-
function buildBlockedResponse(toolName: "
|
|
189
|
+
function buildBlockedResponse(toolName: "execShell", trimmedCommand: string, cwd?: string, shell?: ExecShellKind) {
|
|
226
190
|
const lines: string[] = [];
|
|
227
191
|
lines.push(`[${toolName} 拦截] 检测到潜在危险命令,默认不执行。`);
|
|
228
192
|
lines.push(`command: ${trimmedCommand}`);
|
|
@@ -247,57 +211,7 @@ function buildBlockedResponse(toolName: "execBash" | "execShell", trimmedCommand
|
|
|
247
211
|
};
|
|
248
212
|
}
|
|
249
213
|
|
|
250
|
-
export const
|
|
251
|
-
thunkApi: any,
|
|
252
|
-
args: { command: string; cwd?: string; pty?: boolean }
|
|
253
|
-
) =>
|
|
254
|
-
requestExec(
|
|
255
|
-
{
|
|
256
|
-
command: args.command,
|
|
257
|
-
cwd: args.cwd,
|
|
258
|
-
interactive: true,
|
|
259
|
-
pty: args.pty,
|
|
260
|
-
},
|
|
261
|
-
thunkApi,
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
export const writeExecBashSession = (
|
|
265
|
-
thunkApi: any,
|
|
266
|
-
args: { sessionId: string; input: string }
|
|
267
|
-
) =>
|
|
268
|
-
requestExec(
|
|
269
|
-
{
|
|
270
|
-
sessionId: args.sessionId,
|
|
271
|
-
input: args.input,
|
|
272
|
-
},
|
|
273
|
-
thunkApi,
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
export const readExecBashSession = (
|
|
277
|
-
thunkApi: any,
|
|
278
|
-
args: { sessionId: string }
|
|
279
|
-
) =>
|
|
280
|
-
requestExec(
|
|
281
|
-
{
|
|
282
|
-
sessionId: args.sessionId,
|
|
283
|
-
},
|
|
284
|
-
thunkApi,
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
export const closeExecBashSession = (
|
|
288
|
-
thunkApi: any,
|
|
289
|
-
args: { sessionId: string; input?: string }
|
|
290
|
-
) =>
|
|
291
|
-
requestExec(
|
|
292
|
-
{
|
|
293
|
-
sessionId: args.sessionId,
|
|
294
|
-
input: args.input,
|
|
295
|
-
close: true,
|
|
296
|
-
},
|
|
297
|
-
thunkApi,
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
export const startExecShellSession = (
|
|
214
|
+
export const startExecShellSession = (
|
|
301
215
|
thunkApi: any,
|
|
302
216
|
args: { command: string; cwd?: string; pty?: boolean; shell?: ExecShellKind }
|
|
303
217
|
) =>
|
|
@@ -310,43 +224,8 @@ export const startExecShellSession = (
|
|
|
310
224
|
shell: args.shell,
|
|
311
225
|
},
|
|
312
226
|
thunkApi,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
export async function execBashFunc(args: any, thunkApi?: any, context?: { agentKey?: string }): Promise<{
|
|
317
|
-
rawData: any;
|
|
318
|
-
displayData: string;
|
|
319
|
-
}> {
|
|
320
|
-
const { command, cwd, unsafe, interactive, pty, sessionId, input, close } = args || {};
|
|
321
|
-
const isSessionFollowup = typeof sessionId === "string" && sessionId.trim().length > 0;
|
|
322
|
-
|
|
323
|
-
if (!isSessionFollowup && (!command || typeof command !== "string")) {
|
|
324
|
-
const msg = "execBash 需要提供 command,或提供有效的 sessionId 来继续已有交互式 session。";
|
|
325
|
-
return { rawData: { error: msg }, displayData: msg };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const trimmedCommand = typeof command === "string" ? command.trim() : "";
|
|
329
|
-
if (!isSessionFollowup && !unsafe && isDangerousCommand(trimmedCommand)) {
|
|
330
|
-
return buildBlockedResponse("execBash", trimmedCommand, cwd);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
try {
|
|
334
|
-
const json = await requestExec({
|
|
335
|
-
...(trimmedCommand ? { command: trimmedCommand } : {}),
|
|
336
|
-
...(cwd ? { cwd } : {}),
|
|
337
|
-
...(interactive ? { interactive: true } : {}),
|
|
338
|
-
...(pty ? { pty: true } : {}),
|
|
339
|
-
...(isSessionFollowup ? { sessionId } : {}),
|
|
340
|
-
...(typeof input === "string" ? { input } : {}),
|
|
341
|
-
...(close ? { close: true } : {}),
|
|
342
|
-
}, thunkApi, "/api/exec-bash", context);
|
|
343
|
-
return formatExecResult("execBash", { cwd }, json, trimmedCommand, sessionId);
|
|
344
|
-
} catch (error: any) {
|
|
345
|
-
const msg = `execBash 调用失败:${error?.message || String(error)}`;
|
|
346
|
-
return { rawData: { error: msg }, displayData: msg };
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
227
|
+
);
|
|
228
|
+
|
|
350
229
|
export async function execShellFunc(args: any, thunkApi?: any, context?: { agentKey?: string }): Promise<{
|
|
351
230
|
rawData: any;
|
|
352
231
|
displayData: string;
|
|
@@ -375,9 +254,13 @@ export async function execShellFunc(args: any, thunkApi?: any, context?: { agent
|
|
|
375
254
|
...(typeof input === "string" ? { input } : {}),
|
|
376
255
|
...(close ? { close: true } : {}),
|
|
377
256
|
}, thunkApi, "/api/exec-shell", context);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
257
|
+
if (json?.ok === false || json?.error) {
|
|
258
|
+
const message = typeof json?.error === "string" ? json.error : JSON.stringify(json?.error ?? json);
|
|
259
|
+
throwExecToolError("execShell", message, { command: trimmedCommand || null, ...json });
|
|
260
|
+
}
|
|
261
|
+
return formatExecResult("execShell", { cwd, shell }, json, trimmedCommand, sessionId);
|
|
262
|
+
} catch (error: any) {
|
|
263
|
+
if (error instanceof ToolResultError) throw error;
|
|
264
|
+
throwExecToolError("execShell", error?.message || String(error));
|
|
265
|
+
}
|
|
266
|
+
}
|
package/ai/tools/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
createWorkflowFunc,
|
|
23
23
|
} from "./createWorkflowTool";
|
|
24
24
|
import { delayFunctionSchema, delayFunc } from "./delayTool";
|
|
25
|
+
import { canonicalizeToolName } from "./toolNameAliases";
|
|
25
26
|
import {
|
|
26
27
|
completeDialogGoalFunc,
|
|
27
28
|
completeDialogGoalFunctionSchema,
|
|
@@ -160,6 +161,10 @@ import {
|
|
|
160
161
|
fetchWebpageFunctionSchema,
|
|
161
162
|
fetchWebpageFunc,
|
|
162
163
|
} from "./fetchWebpageTool";
|
|
164
|
+
import {
|
|
165
|
+
convertMarxistsBookToOfflineHtmlFunctionSchema,
|
|
166
|
+
convertMarxistsBookToOfflineHtmlFunc,
|
|
167
|
+
} from "./convertMarxistsBookTool";
|
|
163
168
|
import {
|
|
164
169
|
readXPostFunctionSchema,
|
|
165
170
|
readXPostFunc,
|
|
@@ -281,7 +286,7 @@ import {
|
|
|
281
286
|
rememberMemoryFunc,
|
|
282
287
|
rememberMemoryFunctionSchema,
|
|
283
288
|
} from "./rememberMemoryTool";
|
|
284
|
-
import {
|
|
289
|
+
import { execShellFunctionSchema, execShellFunc } from "./execShellTool";
|
|
285
290
|
import { checkEnvFunctionSchema, checkEnvFunc } from "./checkEnvTool";
|
|
286
291
|
|
|
287
292
|
import { agentToolDefinitions } from "./agent/agentTools";
|
|
@@ -1464,6 +1469,23 @@ const baseToolDefinitions: ToolDefinition[] = [
|
|
|
1464
1469
|
},
|
|
1465
1470
|
behavior: "data",
|
|
1466
1471
|
},
|
|
1472
|
+
{
|
|
1473
|
+
id: "convertMarxistsBookToOfflineHtml",
|
|
1474
|
+
schema: convertMarxistsBookToOfflineHtmlFunctionSchema,
|
|
1475
|
+
executor: convertMarxistsBookToOfflineHtmlFunc,
|
|
1476
|
+
description: {
|
|
1477
|
+
name: "convertMarxistsBookToOfflineHtml",
|
|
1478
|
+
description:
|
|
1479
|
+
"将 Marxists.org 中文旧式书籍页面转换成保留原 CSS、背景和排版的离线单 HTML。",
|
|
1480
|
+
category: "网络与智能",
|
|
1481
|
+
},
|
|
1482
|
+
behavior: "data",
|
|
1483
|
+
capability: "web_access",
|
|
1484
|
+
riskLevel: "low",
|
|
1485
|
+
costLevel: "medium",
|
|
1486
|
+
defaultConsent: "auto",
|
|
1487
|
+
cancelable: true,
|
|
1488
|
+
},
|
|
1467
1489
|
{
|
|
1468
1490
|
id: "readXPost",
|
|
1469
1491
|
schema: readXPostFunctionSchema,
|
|
@@ -1837,18 +1859,6 @@ const baseToolDefinitions: ToolDefinition[] = [
|
|
|
1837
1859
|
behavior: "data",
|
|
1838
1860
|
uiGroup: "general",
|
|
1839
1861
|
},
|
|
1840
|
-
{
|
|
1841
|
-
id: "execBash",
|
|
1842
|
-
schema: execBashFunctionSchema,
|
|
1843
|
-
executor: execBashFunc,
|
|
1844
|
-
description: {
|
|
1845
|
-
name: "exec_bash",
|
|
1846
|
-
description:
|
|
1847
|
-
"通过后端 diff-server 的 /api/exec-bash 接口执行一条 bash 命令,仅用于本地开发调试。",
|
|
1848
|
-
category: "网络与智能",
|
|
1849
|
-
},
|
|
1850
|
-
behavior: "action",
|
|
1851
|
-
},
|
|
1852
1862
|
{
|
|
1853
1863
|
id: "execShell",
|
|
1854
1864
|
schema: execShellFunctionSchema,
|
|
@@ -2070,7 +2080,7 @@ export const findToolExecutor = (
|
|
|
2070
2080
|
executor: ToolDefinition["executor"];
|
|
2071
2081
|
canonicalName: string;
|
|
2072
2082
|
} => {
|
|
2073
|
-
const normalizedRawName = normalizeToolName(rawName);
|
|
2083
|
+
const normalizedRawName = normalizeToolName(canonicalizeToolName(rawName));
|
|
2074
2084
|
const canonicalName = Object.keys(toolExecutors).find(
|
|
2075
2085
|
(key) => normalizeToolName(key) === normalizedRawName
|
|
2076
2086
|
);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
export type MarxistsOfflineBookPage = {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type MarxistsOfflineBookResult = {
|
|
8
|
+
title: string;
|
|
9
|
+
startUrl: string;
|
|
10
|
+
indexUrl: string;
|
|
11
|
+
stylesheetUrl: string | null;
|
|
12
|
+
pages: MarxistsOfflineBookPage[];
|
|
13
|
+
html: string;
|
|
14
|
+
validation: {
|
|
15
|
+
pageCount: number;
|
|
16
|
+
hasEmbeddedAssets: boolean;
|
|
17
|
+
hasNetworkUrls: boolean;
|
|
18
|
+
hasUtf8Meta: boolean;
|
|
19
|
+
byteLength: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ConvertMarxistsBookArgs = {
|
|
24
|
+
startUrl: string;
|
|
25
|
+
encoding?: string;
|
|
26
|
+
fetchImpl?: typeof fetch;
|
|
27
|
+
maxPages?: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const DEFAULT_ENCODING = "gb2312";
|
|
31
|
+
const USER_AGENT =
|
|
32
|
+
"Mozilla/5.0 (compatible; NoloMarxistsOfflineBook/1.0; +https://nolo.chat)";
|
|
33
|
+
|
|
34
|
+
const normalizeBookBaseUrl = (startUrl: string): URL => {
|
|
35
|
+
const parsed = new URL(startUrl);
|
|
36
|
+
if (!/^https?:$/i.test(parsed.protocol)) {
|
|
37
|
+
throw new Error("startUrl must be an http or https URL.");
|
|
38
|
+
}
|
|
39
|
+
return new URL("./", parsed);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const decodeBytes = (bytes: ArrayBuffer, encoding: string): string => {
|
|
43
|
+
try {
|
|
44
|
+
return new TextDecoder(encoding).decode(bytes);
|
|
45
|
+
} catch {
|
|
46
|
+
return new TextDecoder(DEFAULT_ENCODING).decode(bytes);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const fetchBytes = async (
|
|
51
|
+
url: string,
|
|
52
|
+
fetchImpl: typeof fetch,
|
|
53
|
+
): Promise<ArrayBuffer> => {
|
|
54
|
+
const response = await fetchImpl(url, {
|
|
55
|
+
headers: {
|
|
56
|
+
"user-agent": USER_AGENT,
|
|
57
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
return response.arrayBuffer();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const extractTitle = (html: string): string => {
|
|
67
|
+
return (
|
|
68
|
+
html.match(/<title>([\s\S]*?)<\/title>/i)?.[1]
|
|
69
|
+
?.replace(/<[^>]+>/g, " ")
|
|
70
|
+
.replace(/\s+/g, " ")
|
|
71
|
+
.trim() || ""
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const extractBody = (html: string): string => {
|
|
76
|
+
return html.match(/<body[^>]*>([\s\S]*?)<\/body>/i)?.[1] ?? html;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const discoverMarxistsBookPageNames = (
|
|
80
|
+
indexHtml: string,
|
|
81
|
+
maxPages = 80,
|
|
82
|
+
): string[] => {
|
|
83
|
+
const pageNames = [...indexHtml.matchAll(/href=["']?([^"' >#]+\.htm)(#[^"' >]*)?["']?/gi)]
|
|
84
|
+
.map((match) => match[1])
|
|
85
|
+
.filter((href) => !href.includes("/") && !href.startsWith(".."))
|
|
86
|
+
.map((href) => href.trim())
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.sort();
|
|
89
|
+
|
|
90
|
+
const uniqueNames = Array.from(new Set(["index.htm", ...pageNames]));
|
|
91
|
+
if (uniqueNames.length <= 1) {
|
|
92
|
+
throw new Error("No same-directory .htm chapter links were found in index.htm.");
|
|
93
|
+
}
|
|
94
|
+
if (uniqueNames.length > maxPages) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Refusing to download ${uniqueNames.length} pages; maxPages is ${maxPages}.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return uniqueNames;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const extractStylesheetHref = (html: string): string | null => {
|
|
103
|
+
return (
|
|
104
|
+
html.match(/<link[^>]+href=["']([^"']+\.css)["'][^>]*>/i)?.[1] ?? null
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const guessMimeType = (url: string): string => {
|
|
109
|
+
const pathname = new URL(url).pathname.toLowerCase();
|
|
110
|
+
if (pathname.endsWith(".gif")) return "image/gif";
|
|
111
|
+
if (pathname.endsWith(".png")) return "image/png";
|
|
112
|
+
if (pathname.endsWith(".jpg") || pathname.endsWith(".jpeg")) return "image/jpeg";
|
|
113
|
+
if (pathname.endsWith(".webp")) return "image/webp";
|
|
114
|
+
if (pathname.endsWith(".css")) return "text/css";
|
|
115
|
+
return "application/octet-stream";
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const inlineCssUrlAssets = async (args: {
|
|
119
|
+
css: string;
|
|
120
|
+
cssUrl: string;
|
|
121
|
+
fetchImpl: typeof fetch;
|
|
122
|
+
}): Promise<string> => {
|
|
123
|
+
const replacements = new Map<string, string>();
|
|
124
|
+
const matches = [...args.css.matchAll(/url\((['"]?)([^)'"]+)\1\)/gi)];
|
|
125
|
+
|
|
126
|
+
for (const match of matches) {
|
|
127
|
+
const rawRef = match[2].trim();
|
|
128
|
+
if (!rawRef || /^(data:|https?:)/i.test(rawRef)) continue;
|
|
129
|
+
const assetUrl = new URL(rawRef, args.cssUrl).toString();
|
|
130
|
+
if (!replacements.has(rawRef)) {
|
|
131
|
+
const bytes = await fetchBytes(assetUrl, args.fetchImpl);
|
|
132
|
+
const base64 = Buffer.from(bytes).toString("base64");
|
|
133
|
+
replacements.set(
|
|
134
|
+
rawRef,
|
|
135
|
+
`data:${guessMimeType(assetUrl)};base64,${base64}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return args.css.replace(/url\((['"]?)([^)'"]+)\1\)/gi, (full, quote, rawRef) => {
|
|
141
|
+
const replacement = replacements.get(String(rawRef).trim());
|
|
142
|
+
return replacement ? `url('${replacement}')` : full;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const rewriteLocalChapterLinks = (body: string): string => {
|
|
147
|
+
return body.replace(
|
|
148
|
+
/href=["']([^"']+\.htm)(#[^"']*)?["']/gi,
|
|
149
|
+
(_full, href: string, hash: string | undefined) => {
|
|
150
|
+
const anchor = href.replace(/\.htm$/i, "");
|
|
151
|
+
return `href="#${anchor}${hash ? `-${hash.replace(/^#/, "")}` : ""}"`;
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const buildSingleHtml = (args: {
|
|
157
|
+
title: string;
|
|
158
|
+
css: string;
|
|
159
|
+
pages: Array<MarxistsOfflineBookPage & { body: string }>;
|
|
160
|
+
}): string => {
|
|
161
|
+
const sections = args.pages.map((page) => {
|
|
162
|
+
const anchor = page.name.replace(/\.htm$/i, "");
|
|
163
|
+
return `<section class="chapter" id="${anchor}"><div class="chapter-source">${page.title}</div>${rewriteLocalChapterLinks(page.body)}</section>`;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return `<!doctype html>
|
|
167
|
+
<html lang="zh-CN">
|
|
168
|
+
<head>
|
|
169
|
+
<meta charset="utf-8">
|
|
170
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
171
|
+
<title>${args.title}</title>
|
|
172
|
+
<style>
|
|
173
|
+
${args.css}
|
|
174
|
+
body { margin: 0 10px; }
|
|
175
|
+
.table1 { max-width: 960px; width: min(960px, calc(100vw - 20px)); }
|
|
176
|
+
.chapter { page-break-after: always; break-after: page; }
|
|
177
|
+
.chapter-source { display: none; }
|
|
178
|
+
@media screen and (max-width: 700px) {
|
|
179
|
+
html { margin-left: 8px !important; margin-right: 8px !important; }
|
|
180
|
+
body { margin-left: 8px !important; margin-right: 8px !important; }
|
|
181
|
+
.table1 { width: 100% !important; max-width: 100% !important; }
|
|
182
|
+
table { max-width: 100% !important; }
|
|
183
|
+
}
|
|
184
|
+
@media print {
|
|
185
|
+
.chapter { page-break-after: always; }
|
|
186
|
+
}
|
|
187
|
+
</style>
|
|
188
|
+
</head>
|
|
189
|
+
<body id="top">
|
|
190
|
+
${sections.join("\n")}
|
|
191
|
+
</body>
|
|
192
|
+
</html>`;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export async function convertMarxistsBookToOfflineHtml(
|
|
196
|
+
args: ConvertMarxistsBookArgs,
|
|
197
|
+
): Promise<MarxistsOfflineBookResult> {
|
|
198
|
+
const startUrl = args.startUrl?.trim();
|
|
199
|
+
if (!startUrl) throw new Error("startUrl is required.");
|
|
200
|
+
|
|
201
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
202
|
+
const encoding = args.encoding || DEFAULT_ENCODING;
|
|
203
|
+
const bookBaseUrl = normalizeBookBaseUrl(startUrl);
|
|
204
|
+
const indexUrl = new URL("index.htm", bookBaseUrl).toString();
|
|
205
|
+
const indexHtml = decodeBytes(await fetchBytes(indexUrl, fetchImpl), encoding);
|
|
206
|
+
const pageNames = discoverMarxistsBookPageNames(indexHtml, args.maxPages);
|
|
207
|
+
|
|
208
|
+
const pages: Array<MarxistsOfflineBookPage & { body: string }> = [];
|
|
209
|
+
for (const name of pageNames) {
|
|
210
|
+
const pageUrl = new URL(name, bookBaseUrl).toString();
|
|
211
|
+
const html = decodeBytes(await fetchBytes(pageUrl, fetchImpl), encoding);
|
|
212
|
+
pages.push({
|
|
213
|
+
name,
|
|
214
|
+
url: pageUrl,
|
|
215
|
+
title: extractTitle(html),
|
|
216
|
+
body: extractBody(html),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const stylesheetHref =
|
|
221
|
+
extractStylesheetHref(indexHtml) ||
|
|
222
|
+
pages.map((page) => extractStylesheetHref(page.body)).find(Boolean) ||
|
|
223
|
+
"../../MIA01.css";
|
|
224
|
+
const stylesheetUrl = stylesheetHref
|
|
225
|
+
? new URL(stylesheetHref, indexUrl).toString()
|
|
226
|
+
: null;
|
|
227
|
+
const rawCss = stylesheetUrl
|
|
228
|
+
? decodeBytes(await fetchBytes(stylesheetUrl, fetchImpl), encoding)
|
|
229
|
+
: "";
|
|
230
|
+
const css = stylesheetUrl
|
|
231
|
+
? await inlineCssUrlAssets({ css: rawCss, cssUrl: stylesheetUrl, fetchImpl })
|
|
232
|
+
: rawCss;
|
|
233
|
+
|
|
234
|
+
const title = extractTitle(indexHtml) || pages[0]?.title || "Offline Book";
|
|
235
|
+
const html = buildSingleHtml({ title, css, pages });
|
|
236
|
+
const byteLength = new TextEncoder().encode(html).byteLength;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
title,
|
|
240
|
+
startUrl,
|
|
241
|
+
indexUrl,
|
|
242
|
+
stylesheetUrl,
|
|
243
|
+
pages: pages.map(({ body: _body, ...page }) => page),
|
|
244
|
+
html,
|
|
245
|
+
validation: {
|
|
246
|
+
pageCount: pages.length,
|
|
247
|
+
hasEmbeddedAssets: html.includes("data:image/"),
|
|
248
|
+
hasNetworkUrls: /https?:\/\//i.test(html),
|
|
249
|
+
hasUtf8Meta: html.includes('<meta charset="utf-8">'),
|
|
250
|
+
byteLength,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -52,11 +52,13 @@ export const queryModelUsageFunctionSchema = {
|
|
|
52
52
|
},
|
|
53
53
|
thresholdCredits: {
|
|
54
54
|
type: "number",
|
|
55
|
-
description:
|
|
55
|
+
description:
|
|
56
|
+
"可选阈值,单位 credits;返回 threshold.exceeded 与 0-100 的 threshold.usedPercent。",
|
|
56
57
|
},
|
|
57
58
|
thresholdUsd: {
|
|
58
59
|
type: "number",
|
|
59
|
-
description:
|
|
60
|
+
description:
|
|
61
|
+
"可选阈值,单位 USD;会按 creditsPerUsd 换算,并返回 0-100 的 threshold.usedPercent。",
|
|
60
62
|
},
|
|
61
63
|
},
|
|
62
64
|
},
|
|
@@ -179,6 +181,11 @@ export const notifyUserFunctionSchema = {
|
|
|
179
181
|
type: "string",
|
|
180
182
|
description: "可选跳转链接。",
|
|
181
183
|
},
|
|
184
|
+
targetUserId: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description:
|
|
187
|
+
"可选目标用户 ID。默认通知当前用户;只有受信任的系统 agent 或管理员可以通知其他用户。",
|
|
188
|
+
},
|
|
182
189
|
},
|
|
183
190
|
required: ["title", "message"],
|
|
184
191
|
},
|
package/ai/tools/readTool.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { isTableMetaKey, rowKey, isAgentKey } from "database/keys";
|
|
|
9
9
|
import { DataType } from "create/types";
|
|
10
10
|
import { TableMeta } from "render/table/types";
|
|
11
11
|
import { fetchAndSerializeTable } from "render/table/utils/tableSerialization";
|
|
12
|
+
import { readFileFunc } from "./readFileTool";
|
|
12
13
|
|
|
13
14
|
// ---- Types ----
|
|
14
15
|
|
|
@@ -24,6 +25,13 @@ export type ReadArgs = {
|
|
|
24
25
|
* - true:使用 readAndWaitAction,等待远程与本地决策后返回“权威”结果
|
|
25
26
|
*/
|
|
26
27
|
waitRemote?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Backward-compatible escape hatch for models that call read with a local
|
|
31
|
+
* file path even when the readFile tool is available.
|
|
32
|
+
*/
|
|
33
|
+
filePath?: string;
|
|
34
|
+
path?: string;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
37
|
// ---- 工具 Schema,供 LLM 调用 ----
|
|
@@ -68,6 +76,29 @@ export async function readFunc(
|
|
|
68
76
|
context?: { parentMessageId?: string; signal?: AbortSignal }
|
|
69
77
|
): Promise<{ rawData: any; displayData?: string }> {
|
|
70
78
|
const { dbKey, waitRemote = false } = args || {};
|
|
79
|
+
const localFilePath =
|
|
80
|
+
typeof args?.filePath === "string"
|
|
81
|
+
? args.filePath
|
|
82
|
+
: typeof args?.path === "string"
|
|
83
|
+
? args.path
|
|
84
|
+
: "";
|
|
85
|
+
|
|
86
|
+
if (!dbKey && localFilePath) {
|
|
87
|
+
const result = await readFileFunc(
|
|
88
|
+
{
|
|
89
|
+
filePath: localFilePath,
|
|
90
|
+
startLine: (args as any).startLine,
|
|
91
|
+
endLine: (args as any).endLine,
|
|
92
|
+
},
|
|
93
|
+
thunkApi,
|
|
94
|
+
context
|
|
95
|
+
);
|
|
96
|
+
return {
|
|
97
|
+
rawData: result,
|
|
98
|
+
displayData:
|
|
99
|
+
typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
71
102
|
|
|
72
103
|
if (!dbKey || typeof dbKey !== "string") {
|
|
73
104
|
throw new Error("read 工具需要提供字符串类型的 id(dbKey)。");
|