jinzd-ai-cli 0.4.210 → 0.4.211
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 +99 -1
- package/README.zh-CN.md +40 -0
- package/dist/{batch-MFPYHOIL.js → batch-DE4RXKZD.js} +2 -2
- package/dist/{chunk-E2ZWWE6Q.js → chunk-6NS6643Y.js} +1 -1
- package/dist/{chunk-NWP7C6CC.js → chunk-BE6ERF7M.js} +1 -1
- package/dist/{chunk-7JWT3KXO.js → chunk-C3OU2OPF.js} +2 -2
- package/dist/{chunk-GRJNQJA5.js → chunk-E5XCM4A6.js} +1 -1
- package/dist/{chunk-DJ35G5VO.js → chunk-JBWA73GK.js} +1 -1
- package/dist/{chunk-P6MTFCXB.js → chunk-UOROWTGG.js} +37 -3
- package/dist/{chunk-NEPFADHX.js → chunk-ZOPYREL5.js} +312 -38
- package/dist/{chunk-HZQVX7VF.js → chunk-ZY2N2N6T.js} +1 -1
- package/dist/{ci-QLJUDLMY.js → ci-XMUEX526.js} +2 -2
- package/dist/{constants-47OR72MV.js → constants-AWTIQIWG.js} +1 -1
- package/dist/{doctor-cli-32COMA5K.js → doctor-cli-SSI6ETFT.js} +4 -4
- package/dist/electron-server.js +486 -113
- package/dist/{hub-3NWJUCLK.js → hub-INUJND2G.js} +1 -1
- package/dist/index.js +34 -17
- package/dist/{run-tests-UT7RZ7Y3.js → run-tests-3MHWUF43.js} +2 -2
- package/dist/{run-tests-NT2UIUVB.js → run-tests-PACN4UYX.js} +1 -1
- package/dist/{server-AIHVMLNU.js → server-7USZJJAH.js} +4 -4
- package/dist/{server-E5D54DIZ.js → server-BTSKOPQI.js} +96 -27
- package/dist/{task-orchestrator-BFDSTSOH.js → task-orchestrator-OV4O25MX.js} +4 -4
- package/dist/{usage-P4B2RZKL.js → usage-T2P6FTE7.js} +2 -2
- package/dist/web/client/app.js +1 -1
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
VERSION,
|
|
38
38
|
buildUserIdentityPrompt,
|
|
39
39
|
runTestsTool
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-ZY2N2N6T.js";
|
|
41
41
|
import {
|
|
42
42
|
hasSemanticIndex,
|
|
43
43
|
semanticSearch
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
import express from "express";
|
|
61
61
|
import { createServer } from "http";
|
|
62
62
|
import { WebSocketServer } from "ws";
|
|
63
|
-
import { join as join19, dirname as dirname6, resolve as
|
|
63
|
+
import { join as join19, dirname as dirname6, resolve as resolve8, relative as relative4, sep as sep3 } from "path";
|
|
64
64
|
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
|
|
65
65
|
import { networkInterfaces } from "os";
|
|
66
66
|
|
|
@@ -213,7 +213,41 @@ var ConfigSchema = z.object({
|
|
|
213
213
|
preToolExecution: z.string().optional(),
|
|
214
214
|
postToolExecution: z.string().optional()
|
|
215
215
|
}).optional(),
|
|
216
|
-
//
|
|
216
|
+
// 网络访问治理(v0.4.211+):默认关闭以保持兼容;开启后统一治理 web/search/MCP/shell 网络出口。
|
|
217
|
+
networkPolicy: z.object({
|
|
218
|
+
enabled: z.boolean().default(false),
|
|
219
|
+
defaultAction: z.enum(["allow", "confirm", "deny"]).default("confirm"),
|
|
220
|
+
allowDomains: z.array(z.string()).default([]),
|
|
221
|
+
denyDomains: z.array(z.string()).default([]),
|
|
222
|
+
allowPorts: z.array(z.number().int().min(1).max(65535)).default([]),
|
|
223
|
+
denyPorts: z.array(z.number().int().min(1).max(65535)).default([]),
|
|
224
|
+
allowPrivateNetwork: z.boolean().default(false),
|
|
225
|
+
tools: z.object({
|
|
226
|
+
web_fetch: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
227
|
+
web_search: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
228
|
+
google_search: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
229
|
+
shell: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
230
|
+
mcp: z.enum(["allow", "confirm", "deny"]).optional()
|
|
231
|
+
}).default({})
|
|
232
|
+
}).default({ enabled: false, defaultAction: "confirm", allowDomains: [], denyDomains: [], allowPorts: [], denyPorts: [], allowPrivateNetwork: false, tools: {} }),
|
|
233
|
+
// 权限 Profile(v0.4.211+):legacy 保持旧行为;其余 profile 提供更清晰的安全边界。
|
|
234
|
+
defaultPermissionProfile: z.enum(["legacy", "read-only", "workspace-write", "danger-full-access"]).default("legacy"),
|
|
235
|
+
allowedPermissionProfiles: z.array(z.string()).default(["legacy", "read-only", "workspace-write", "danger-full-access"]),
|
|
236
|
+
permissionProfiles: z.record(z.object({
|
|
237
|
+
extends: z.enum(["legacy", "read-only", "workspace-write", "danger-full-access"]).optional(),
|
|
238
|
+
defaultAction: z.enum(["auto-approve", "deny", "confirm"]).optional(),
|
|
239
|
+
workspaceRoots: z.array(z.string()).optional(),
|
|
240
|
+
allowTemp: z.boolean().optional(),
|
|
241
|
+
rules: z.array(z.object({
|
|
242
|
+
tool: z.string(),
|
|
243
|
+
action: z.enum(["auto-approve", "deny", "confirm"]),
|
|
244
|
+
when: z.object({
|
|
245
|
+
dangerLevel: z.enum(["safe", "write", "destructive"]).optional(),
|
|
246
|
+
pathPattern: z.string().optional()
|
|
247
|
+
}).optional()
|
|
248
|
+
})).optional()
|
|
249
|
+
})).default({}),
|
|
250
|
+
// 工具权限规则(按顺序匹配第一个生效;作为当前权限 Profile 内的细粒度规则)
|
|
217
251
|
permissionRules: z.array(z.object({
|
|
218
252
|
tool: z.string(),
|
|
219
253
|
action: z.enum(["auto-approve", "deny", "confirm"]),
|
|
@@ -222,7 +256,7 @@ var ConfigSchema = z.object({
|
|
|
222
256
|
pathPattern: z.string().optional()
|
|
223
257
|
}).optional()
|
|
224
258
|
})).default([]),
|
|
225
|
-
//
|
|
259
|
+
// 无规则匹配时的默认权限动作(legacy profile 兼容旧行为)
|
|
226
260
|
defaultPermission: z.enum(["auto-approve", "deny", "confirm"]).default("confirm"),
|
|
227
261
|
// 自动上下文压缩开关
|
|
228
262
|
// 当对话估算 token 数超过模型 contextWindow 的 80% 时,自动触发 compact 压缩旧消息
|
|
@@ -5733,6 +5767,7 @@ import { dirname as dirname3 } from "path";
|
|
|
5733
5767
|
// src/tools/executor.ts
|
|
5734
5768
|
import chalk3 from "chalk";
|
|
5735
5769
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
5770
|
+
import { tmpdir } from "os";
|
|
5736
5771
|
|
|
5737
5772
|
// src/core/readline-internal.ts
|
|
5738
5773
|
function rlInternal(rl) {
|
|
@@ -5967,6 +6002,7 @@ function runHook(template, vars) {
|
|
|
5967
6002
|
}
|
|
5968
6003
|
|
|
5969
6004
|
// src/tools/permissions.ts
|
|
6005
|
+
import { isAbsolute, resolve as resolve3 } from "path";
|
|
5970
6006
|
function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
|
|
5971
6007
|
for (const rule of rules) {
|
|
5972
6008
|
if (rule.tool !== "*" && rule.tool !== toolName) continue;
|
|
@@ -5987,6 +6023,225 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
|
|
|
5987
6023
|
}
|
|
5988
6024
|
return defaultAction;
|
|
5989
6025
|
}
|
|
6026
|
+
var BUILTIN_PROFILE_NAMES = /* @__PURE__ */ new Set(["legacy", "read-only", "workspace-write", "danger-full-access"]);
|
|
6027
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
6028
|
+
"read_file",
|
|
6029
|
+
"list_dir",
|
|
6030
|
+
"grep_files",
|
|
6031
|
+
"glob_files",
|
|
6032
|
+
"find_symbol",
|
|
6033
|
+
"get_outline",
|
|
6034
|
+
"find_references",
|
|
6035
|
+
"search_code",
|
|
6036
|
+
"git_status",
|
|
6037
|
+
"git_diff",
|
|
6038
|
+
"git_log",
|
|
6039
|
+
"task_list",
|
|
6040
|
+
"web_fetch",
|
|
6041
|
+
"web_search",
|
|
6042
|
+
"google_search",
|
|
6043
|
+
"ask_user",
|
|
6044
|
+
"write_todos",
|
|
6045
|
+
"recall_memory"
|
|
6046
|
+
]);
|
|
6047
|
+
function normalizePathForPermission(value) {
|
|
6048
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
6049
|
+
}
|
|
6050
|
+
function isInsidePath(root, target) {
|
|
6051
|
+
const absoluteTarget = isAbsolute(target) ? target : resolve3(root, target);
|
|
6052
|
+
const r = normalizePathForPermission(resolve3(root));
|
|
6053
|
+
const t = normalizePathForPermission(absoluteTarget);
|
|
6054
|
+
return t === r || t.startsWith(`${r}/`);
|
|
6055
|
+
}
|
|
6056
|
+
function extractPathArg(toolName, args) {
|
|
6057
|
+
const raw = args["path"] ?? args["filePath"] ?? args["notebook_path"];
|
|
6058
|
+
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
6059
|
+
if (toolName === "save_last_response") {
|
|
6060
|
+
const output = args["outputPath"] ?? args["file"] ?? args["path"];
|
|
6061
|
+
if (typeof output === "string" && output.trim()) return output.trim();
|
|
6062
|
+
}
|
|
6063
|
+
return null;
|
|
6064
|
+
}
|
|
6065
|
+
function isExplicitFileWriteTool(toolName) {
|
|
6066
|
+
return toolName === "write_file" || toolName === "edit_file" || toolName === "notebook_edit" || toolName === "save_last_response";
|
|
6067
|
+
}
|
|
6068
|
+
function resolveProfileName(ctx) {
|
|
6069
|
+
const requested = ctx?.profileName ?? "legacy";
|
|
6070
|
+
if (ctx?.allowedProfiles && ctx.allowedProfiles.length > 0 && !ctx.allowedProfiles.includes(requested)) {
|
|
6071
|
+
return "legacy";
|
|
6072
|
+
}
|
|
6073
|
+
if (BUILTIN_PROFILE_NAMES.has(requested)) return requested;
|
|
6074
|
+
const ext = ctx?.profile?.extends;
|
|
6075
|
+
return ext && BUILTIN_PROFILE_NAMES.has(ext) ? ext : "legacy";
|
|
6076
|
+
}
|
|
6077
|
+
function getProfileRules(ctx) {
|
|
6078
|
+
return ctx?.profile?.rules ?? [];
|
|
6079
|
+
}
|
|
6080
|
+
function getProfileDefaultAction(name, ctx) {
|
|
6081
|
+
if (ctx?.profile?.defaultAction) return ctx.profile.defaultAction;
|
|
6082
|
+
if (name === "danger-full-access") return "auto-approve";
|
|
6083
|
+
return "confirm";
|
|
6084
|
+
}
|
|
6085
|
+
function getWorkspaceRoots(ctx) {
|
|
6086
|
+
const roots = [ctx?.workspaceRoot, ...ctx?.profile?.workspaceRoots ?? []].filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
6087
|
+
return roots;
|
|
6088
|
+
}
|
|
6089
|
+
function getAllowedWriteRoots(ctx) {
|
|
6090
|
+
const roots = [...getWorkspaceRoots(ctx)];
|
|
6091
|
+
const allowTemp = ctx?.profile?.allowTemp !== false;
|
|
6092
|
+
if (allowTemp) roots.push(...ctx?.tempDirs ?? []);
|
|
6093
|
+
return roots.filter((v) => v.trim().length > 0);
|
|
6094
|
+
}
|
|
6095
|
+
function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
|
|
6096
|
+
if (profileName === "legacy") return null;
|
|
6097
|
+
if (profileName === "read-only") {
|
|
6098
|
+
const mcpReadOnly = toolName.startsWith("mcp__") && dangerLevel === "safe";
|
|
6099
|
+
if (dangerLevel !== "safe" || !READ_ONLY_TOOLS.has(toolName) && !mcpReadOnly) {
|
|
6100
|
+
return { action: "deny", reason: "blocked by read-only permission profile", profileName };
|
|
6101
|
+
}
|
|
6102
|
+
return null;
|
|
6103
|
+
}
|
|
6104
|
+
if (profileName === "workspace-write") {
|
|
6105
|
+
if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools always require confirmation", profileName };
|
|
6106
|
+
if (dangerLevel === "write" && isExplicitFileWriteTool(toolName)) {
|
|
6107
|
+
const target = extractPathArg(toolName, args);
|
|
6108
|
+
if (!target) return { action: "confirm", reason: "file write target is not explicit", profileName };
|
|
6109
|
+
const allowedRoots = getAllowedWriteRoots(ctx);
|
|
6110
|
+
if (allowedRoots.length === 0 || !allowedRoots.some((root) => isInsidePath(root, target))) {
|
|
6111
|
+
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName };
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
return null;
|
|
6115
|
+
}
|
|
6116
|
+
if (profileName === "danger-full-access") {
|
|
6117
|
+
if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName };
|
|
6118
|
+
return null;
|
|
6119
|
+
}
|
|
6120
|
+
return null;
|
|
6121
|
+
}
|
|
6122
|
+
function checkPermissionWithProfile(toolName, args, dangerLevel, rules, defaultAction = "confirm", profileContext) {
|
|
6123
|
+
const profileName = resolveProfileName(profileContext);
|
|
6124
|
+
const hard = profileHardDecision(profileName, toolName, args, dangerLevel, profileContext);
|
|
6125
|
+
if (hard?.action === "deny") return hard;
|
|
6126
|
+
const combinedRules = [...getProfileRules(profileContext), ...rules];
|
|
6127
|
+
const profileDefault = profileName === "legacy" ? defaultAction : getProfileDefaultAction(profileName, profileContext);
|
|
6128
|
+
const action = checkPermission(toolName, args, dangerLevel, combinedRules, profileDefault);
|
|
6129
|
+
if (hard?.action === "confirm" && action === "auto-approve") return hard;
|
|
6130
|
+
return { action, profileName };
|
|
6131
|
+
}
|
|
6132
|
+
function formatPermissionProfileWarning(profileName) {
|
|
6133
|
+
if (profileName === "danger-full-access") {
|
|
6134
|
+
return "\u26A0 danger-full-access permission profile is active. Write tools may auto-run; destructive tools still require confirmation.";
|
|
6135
|
+
}
|
|
6136
|
+
if (profileName === "read-only") return "Read-only permission profile is active. Write and destructive tools are blocked.";
|
|
6137
|
+
if (profileName === "workspace-write") return "Workspace-write permission profile is active. Writes outside workspace/temp roots are blocked.";
|
|
6138
|
+
return null;
|
|
6139
|
+
}
|
|
6140
|
+
var NETWORK_TOOL_HOSTS = {
|
|
6141
|
+
web_search: ["cn.bing.com", "www.google.com"],
|
|
6142
|
+
google_search: ["www.googleapis.com"]
|
|
6143
|
+
};
|
|
6144
|
+
var NETWORK_TOOL_PORTS = {
|
|
6145
|
+
web_search: [443],
|
|
6146
|
+
google_search: [443]
|
|
6147
|
+
};
|
|
6148
|
+
function networkActionToPermission(action) {
|
|
6149
|
+
if (action === "allow") return null;
|
|
6150
|
+
return action;
|
|
6151
|
+
}
|
|
6152
|
+
function domainMatches(pattern, host) {
|
|
6153
|
+
const p = pattern.trim().toLowerCase().replace(/^\*\./, "");
|
|
6154
|
+
const h = host.trim().toLowerCase().replace(/:\d+$/, "");
|
|
6155
|
+
return h === p || h.endsWith(`.${p}`);
|
|
6156
|
+
}
|
|
6157
|
+
function isPrivateNetworkHost(host) {
|
|
6158
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6159
|
+
if (h === "" || h === "localhost") return true;
|
|
6160
|
+
if (/^(127|10)\./.test(h)) return true;
|
|
6161
|
+
if (/^192\.168\./.test(h)) return true;
|
|
6162
|
+
if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(h)) return true;
|
|
6163
|
+
if (/^169\.254\./.test(h)) return true;
|
|
6164
|
+
if (h === "::1" || h === "::") return true;
|
|
6165
|
+
if (/^f[cd][0-9a-f]*:/i.test(h) || /^fe[89ab][0-9a-f]*:/i.test(h)) return true;
|
|
6166
|
+
return false;
|
|
6167
|
+
}
|
|
6168
|
+
function extractNetworkHosts(toolName, args) {
|
|
6169
|
+
if (toolName === "web_fetch") {
|
|
6170
|
+
const raw = String(args["url"] ?? "").trim();
|
|
6171
|
+
try {
|
|
6172
|
+
return raw ? [new URL(raw).hostname] : [];
|
|
6173
|
+
} catch {
|
|
6174
|
+
return [];
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
return NETWORK_TOOL_HOSTS[toolName] ?? [];
|
|
6178
|
+
}
|
|
6179
|
+
function extractNetworkPorts(toolName, args) {
|
|
6180
|
+
if (toolName === "web_fetch") {
|
|
6181
|
+
const raw = String(args["url"] ?? "").trim();
|
|
6182
|
+
try {
|
|
6183
|
+
if (!raw) return [];
|
|
6184
|
+
const url = new URL(raw);
|
|
6185
|
+
if (url.port) return [Number(url.port)];
|
|
6186
|
+
return [url.protocol === "http:" ? 80 : 443];
|
|
6187
|
+
} catch {
|
|
6188
|
+
return [];
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
return NETWORK_TOOL_PORTS[toolName] ?? [];
|
|
6192
|
+
}
|
|
6193
|
+
function isShellNetworkCommand(command) {
|
|
6194
|
+
return /\b(curl|wget|aria2c|Invoke-WebRequest|Invoke-RestMethod|iwr|irm|ssh|scp|sftp|ftp|telnet|nc|ncat|netcat)\b/i.test(command) || /\b(git\s+(clone|fetch|pull|push)|npm\s+(install|publish|view)|pnpm\s+(add|install)|yarn\s+(add|install)|pip\s+install|python\s+-m\s+pip\s+install)\b/i.test(command);
|
|
6195
|
+
}
|
|
6196
|
+
function networkToolAction(toolName, policy) {
|
|
6197
|
+
if (toolName === "web_fetch") return policy.tools?.web_fetch ?? policy.defaultAction ?? "confirm";
|
|
6198
|
+
if (toolName === "web_search") return policy.tools?.web_search ?? policy.defaultAction ?? "confirm";
|
|
6199
|
+
if (toolName === "google_search") return policy.tools?.google_search ?? policy.defaultAction ?? "confirm";
|
|
6200
|
+
if (toolName.startsWith("mcp__")) return policy.tools?.mcp ?? policy.defaultAction ?? "confirm";
|
|
6201
|
+
return null;
|
|
6202
|
+
}
|
|
6203
|
+
function checkNetworkPolicy(toolName, args, policy) {
|
|
6204
|
+
if (!policy?.enabled) return null;
|
|
6205
|
+
let action = networkToolAction(toolName, policy);
|
|
6206
|
+
let reason = "network access requires confirmation";
|
|
6207
|
+
if (toolName === "bash") {
|
|
6208
|
+
const command = String(args["command"] ?? "");
|
|
6209
|
+
if (!isShellNetworkCommand(command)) return null;
|
|
6210
|
+
action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
|
|
6211
|
+
reason = "shell command may access the network";
|
|
6212
|
+
}
|
|
6213
|
+
if (!action) return null;
|
|
6214
|
+
const hosts = extractNetworkHosts(toolName, args);
|
|
6215
|
+
for (const host of hosts) {
|
|
6216
|
+
if (policy.denyDomains?.some((pattern) => domainMatches(pattern, host))) {
|
|
6217
|
+
return { action: "deny", reason: `network host "${host}" is denied by networkPolicy`, profileName: "networkPolicy" };
|
|
6218
|
+
}
|
|
6219
|
+
if (!policy.allowPrivateNetwork && isPrivateNetworkHost(host)) {
|
|
6220
|
+
return { action: "deny", reason: `network host "${host}" is private/internal`, profileName: "networkPolicy" };
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
if (hosts.length > 0 && policy.allowDomains && policy.allowDomains.length > 0) {
|
|
6224
|
+
const allAllowed = hosts.every((host) => policy.allowDomains.some((pattern) => domainMatches(pattern, host)));
|
|
6225
|
+
if (!allAllowed) {
|
|
6226
|
+
return { action: "deny", reason: `network host is not in networkPolicy.allowDomains`, profileName: "networkPolicy" };
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
const ports = extractNetworkPorts(toolName, args);
|
|
6230
|
+
for (const port of ports) {
|
|
6231
|
+
if (policy.denyPorts?.includes(port)) {
|
|
6232
|
+
return { action: "deny", reason: `network port ${port} is denied by networkPolicy`, profileName: "networkPolicy" };
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
if (ports.length > 0 && policy.allowPorts && policy.allowPorts.length > 0) {
|
|
6236
|
+
const allPortsAllowed = ports.every((port) => policy.allowPorts.includes(port));
|
|
6237
|
+
if (!allPortsAllowed) {
|
|
6238
|
+
return { action: "deny", reason: `network port is not in networkPolicy.allowPorts`, profileName: "networkPolicy" };
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
const permissionAction = networkActionToPermission(action);
|
|
6242
|
+
if (!permissionAction) return null;
|
|
6243
|
+
return { action: permissionAction, reason, profileName: "networkPolicy" };
|
|
6244
|
+
}
|
|
5990
6245
|
|
|
5991
6246
|
// src/tools/truncate.ts
|
|
5992
6247
|
var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
@@ -6289,11 +6544,21 @@ var ToolExecutor = class {
|
|
|
6289
6544
|
/** 权限规则(可选) */
|
|
6290
6545
|
permissionRules = [];
|
|
6291
6546
|
defaultPermission = "confirm";
|
|
6547
|
+
permissionProfileName = "legacy";
|
|
6548
|
+
permissionProfile;
|
|
6549
|
+
allowedPermissionProfiles = [];
|
|
6550
|
+
workspaceRoot = process.cwd();
|
|
6551
|
+
networkPolicy;
|
|
6292
6552
|
/** 注入 hooks 和 permission rules 配置 */
|
|
6293
6553
|
setConfig(opts) {
|
|
6294
6554
|
this.hookConfig = opts.hookConfig;
|
|
6295
6555
|
if (opts.permissionRules) this.permissionRules = opts.permissionRules;
|
|
6296
6556
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
6557
|
+
if (opts.permissionProfileName) this.permissionProfileName = opts.permissionProfileName;
|
|
6558
|
+
if (opts.permissionProfile) this.permissionProfile = opts.permissionProfile;
|
|
6559
|
+
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
6560
|
+
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
6561
|
+
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
6297
6562
|
}
|
|
6298
6563
|
async execute(call) {
|
|
6299
6564
|
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
@@ -6308,31 +6573,69 @@ var ToolExecutor = class {
|
|
|
6308
6573
|
};
|
|
6309
6574
|
}
|
|
6310
6575
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
6576
|
+
let toolCallAlreadyPrinted = false;
|
|
6311
6577
|
runHook(this.hookConfig?.preToolExecution, {
|
|
6312
6578
|
tool: call.name,
|
|
6313
6579
|
dangerLevel,
|
|
6314
6580
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
6315
6581
|
});
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6582
|
+
const permission = checkPermissionWithProfile(
|
|
6583
|
+
call.name,
|
|
6584
|
+
call.arguments,
|
|
6585
|
+
dangerLevel,
|
|
6586
|
+
this.permissionRules,
|
|
6587
|
+
this.defaultPermission,
|
|
6588
|
+
{
|
|
6589
|
+
profileName: this.permissionProfileName,
|
|
6590
|
+
profile: this.permissionProfile,
|
|
6591
|
+
allowedProfiles: this.allowedPermissionProfiles,
|
|
6592
|
+
workspaceRoot: this.workspaceRoot,
|
|
6593
|
+
tempDirs: [tmpdir()]
|
|
6320
6594
|
}
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
}
|
|
6595
|
+
);
|
|
6596
|
+
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
6597
|
+
if (networkPermission?.action === "deny") {
|
|
6598
|
+
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
6599
|
+
return {
|
|
6600
|
+
callId: call.id,
|
|
6601
|
+
content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
|
|
6602
|
+
isError: true
|
|
6603
|
+
};
|
|
6604
|
+
}
|
|
6605
|
+
if (permission.action === "deny") {
|
|
6606
|
+
const reason = permission.reason ? ` (${permission.reason})` : "";
|
|
6607
|
+
return {
|
|
6608
|
+
callId: call.id,
|
|
6609
|
+
content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
|
|
6610
|
+
isError: true
|
|
6611
|
+
};
|
|
6612
|
+
}
|
|
6613
|
+
if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
|
|
6614
|
+
this.printToolCall(call);
|
|
6615
|
+
try {
|
|
6616
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
6617
|
+
const content = truncateOutput(rawContent, call.name);
|
|
6618
|
+
const wasTruncated = content !== rawContent;
|
|
6619
|
+
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6620
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
6621
|
+
return { callId: call.id, content, isError: false };
|
|
6622
|
+
} catch (err) {
|
|
6623
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6624
|
+
this.printToolResult(call.name, message, true, false);
|
|
6625
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
6626
|
+
return { callId: call.id, content: message, isError: true };
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
|
|
6630
|
+
this.printToolCall(call);
|
|
6631
|
+
toolCallAlreadyPrinted = true;
|
|
6632
|
+
const confirmed = await this.confirm(call, "write");
|
|
6633
|
+
if (!confirmed) {
|
|
6634
|
+
return {
|
|
6635
|
+
callId: call.id,
|
|
6636
|
+
content: `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`,
|
|
6637
|
+
isError: true
|
|
6638
|
+
};
|
|
6336
6639
|
}
|
|
6337
6640
|
}
|
|
6338
6641
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
@@ -6422,6 +6725,9 @@ var ToolExecutor = class {
|
|
|
6422
6725
|
* 然后让用户 approve all / reject all / 选择性 approve。
|
|
6423
6726
|
*/
|
|
6424
6727
|
async executeBatchFileWrites(calls) {
|
|
6728
|
+
if (this.permissionProfileName !== "legacy" || this.permissionRules.length > 0) {
|
|
6729
|
+
return Promise.all(calls.map((call) => this.execute(call)));
|
|
6730
|
+
}
|
|
6425
6731
|
console.log();
|
|
6426
6732
|
console.log(theme.heading(`\u270E Batch file writes (${calls.length} files):`));
|
|
6427
6733
|
console.log(theme.dim("\u2500".repeat(50)));
|
|
@@ -6495,7 +6801,7 @@ var ToolExecutor = class {
|
|
|
6495
6801
|
rl.resume();
|
|
6496
6802
|
process.stdout.write(prompt);
|
|
6497
6803
|
this.confirming = true;
|
|
6498
|
-
return new Promise((
|
|
6804
|
+
return new Promise((resolve9) => {
|
|
6499
6805
|
let completed = false;
|
|
6500
6806
|
const cleanup = (result) => {
|
|
6501
6807
|
if (completed) return;
|
|
@@ -6505,7 +6811,7 @@ var ToolExecutor = class {
|
|
|
6505
6811
|
rl.pause();
|
|
6506
6812
|
rlAny.output = savedOutput;
|
|
6507
6813
|
this.confirming = false;
|
|
6508
|
-
|
|
6814
|
+
resolve9(result);
|
|
6509
6815
|
};
|
|
6510
6816
|
const onLine = (line) => {
|
|
6511
6817
|
const trimmed = line.trim();
|
|
@@ -6679,7 +6985,7 @@ var ToolExecutor = class {
|
|
|
6679
6985
|
rl.resume();
|
|
6680
6986
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
6681
6987
|
this.confirming = true;
|
|
6682
|
-
return new Promise((
|
|
6988
|
+
return new Promise((resolve9) => {
|
|
6683
6989
|
let completed = false;
|
|
6684
6990
|
const cleanup = (answer) => {
|
|
6685
6991
|
if (completed) return;
|
|
@@ -6689,7 +6995,7 @@ var ToolExecutor = class {
|
|
|
6689
6995
|
rl.pause();
|
|
6690
6996
|
rlAny.output = savedOutput;
|
|
6691
6997
|
this.confirming = false;
|
|
6692
|
-
|
|
6998
|
+
resolve9(answer === "y");
|
|
6693
6999
|
};
|
|
6694
7000
|
const onLine = (line) => {
|
|
6695
7001
|
const trimmed = line.trim();
|
|
@@ -6717,11 +7023,11 @@ var ToolExecutor = class {
|
|
|
6717
7023
|
};
|
|
6718
7024
|
|
|
6719
7025
|
// src/tools/sensitive-paths.ts
|
|
6720
|
-
import { resolve as
|
|
7026
|
+
import { resolve as resolve4, sep as sep2, basename as basename2 } from "path";
|
|
6721
7027
|
import { homedir as homedir4 } from "os";
|
|
6722
7028
|
var home = homedir4();
|
|
6723
7029
|
function norm(p) {
|
|
6724
|
-
const abs =
|
|
7030
|
+
const abs = resolve4(p);
|
|
6725
7031
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
6726
7032
|
}
|
|
6727
7033
|
function homeRel(p) {
|
|
@@ -7938,7 +8244,7 @@ var runInteractiveTool = {
|
|
|
7938
8244
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
7939
8245
|
};
|
|
7940
8246
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
7941
|
-
return new Promise((
|
|
8247
|
+
return new Promise((resolve9) => {
|
|
7942
8248
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
7943
8249
|
cwd: process.cwd(),
|
|
7944
8250
|
env,
|
|
@@ -7971,22 +8277,22 @@ var runInteractiveTool = {
|
|
|
7971
8277
|
setTimeout(writeNextLine, 400);
|
|
7972
8278
|
const timer = setTimeout(() => {
|
|
7973
8279
|
child.kill();
|
|
7974
|
-
|
|
8280
|
+
resolve9(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
7975
8281
|
${buildOutput(stdout, stderr)}`);
|
|
7976
8282
|
}, timeout);
|
|
7977
8283
|
child.on("close", (code) => {
|
|
7978
8284
|
clearTimeout(timer);
|
|
7979
8285
|
const output = buildOutput(stdout, stderr);
|
|
7980
8286
|
if (code !== 0 && code !== null) {
|
|
7981
|
-
|
|
8287
|
+
resolve9(`${prefixWarnings}Exit code ${code}:
|
|
7982
8288
|
${output}`);
|
|
7983
8289
|
} else {
|
|
7984
|
-
|
|
8290
|
+
resolve9(`${prefixWarnings}${output || "(no output)"}`);
|
|
7985
8291
|
}
|
|
7986
8292
|
});
|
|
7987
8293
|
child.on("error", (err) => {
|
|
7988
8294
|
clearTimeout(timer);
|
|
7989
|
-
|
|
8295
|
+
resolve9(
|
|
7990
8296
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
7991
8297
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
7992
8298
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -8738,7 +9044,7 @@ function promptUser(rl, question) {
|
|
|
8738
9044
|
console.log();
|
|
8739
9045
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
8740
9046
|
process.stdout.write(chalk4.cyan("> "));
|
|
8741
|
-
return new Promise((
|
|
9047
|
+
return new Promise((resolve9) => {
|
|
8742
9048
|
let completed = false;
|
|
8743
9049
|
const cleanup = (answer) => {
|
|
8744
9050
|
if (completed) return;
|
|
@@ -8748,7 +9054,7 @@ function promptUser(rl, question) {
|
|
|
8748
9054
|
rl.pause();
|
|
8749
9055
|
rlAny.output = savedOutput;
|
|
8750
9056
|
askUserContext.prompting = false;
|
|
8751
|
-
|
|
9057
|
+
resolve9(answer);
|
|
8752
9058
|
};
|
|
8753
9059
|
const onLine = (line) => {
|
|
8754
9060
|
cleanup(line);
|
|
@@ -9759,7 +10065,7 @@ ${commitOutput.trim()}`;
|
|
|
9759
10065
|
// src/tools/builtin/notebook-edit.ts
|
|
9760
10066
|
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
|
|
9761
10067
|
import { writeFile } from "fs/promises";
|
|
9762
|
-
import { resolve as
|
|
10068
|
+
import { resolve as resolve5, extname as extname2 } from "path";
|
|
9763
10069
|
var notebookEditTool = {
|
|
9764
10070
|
definition: {
|
|
9765
10071
|
name: "notebook_edit",
|
|
@@ -9808,7 +10114,7 @@ var notebookEditTool = {
|
|
|
9808
10114
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
9809
10115
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
9810
10116
|
}
|
|
9811
|
-
const absPath =
|
|
10117
|
+
const absPath = resolve5(filePath);
|
|
9812
10118
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
9813
10119
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
9814
10120
|
}
|
|
@@ -10587,7 +10893,7 @@ var McpClient = class {
|
|
|
10587
10893
|
// 内部方法:JSON-RPC 通信
|
|
10588
10894
|
// ══════════════════════════════════════════════════════════════════
|
|
10589
10895
|
sendRequest(method, params) {
|
|
10590
|
-
return new Promise((
|
|
10896
|
+
return new Promise((resolve9, reject) => {
|
|
10591
10897
|
if (!this.process?.stdin?.writable) {
|
|
10592
10898
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
10593
10899
|
}
|
|
@@ -10611,7 +10917,7 @@ var McpClient = class {
|
|
|
10611
10917
|
this.pendingRequests.set(id, {
|
|
10612
10918
|
resolve: (result) => {
|
|
10613
10919
|
cleanup();
|
|
10614
|
-
|
|
10920
|
+
resolve9(result);
|
|
10615
10921
|
},
|
|
10616
10922
|
reject: (error) => {
|
|
10617
10923
|
cleanup();
|
|
@@ -10688,13 +10994,13 @@ var McpClient = class {
|
|
|
10688
10994
|
}
|
|
10689
10995
|
/** Promise 超时包装 */
|
|
10690
10996
|
withTimeout(promise, ms, label) {
|
|
10691
|
-
return new Promise((
|
|
10997
|
+
return new Promise((resolve9, reject) => {
|
|
10692
10998
|
const timer = setTimeout(() => {
|
|
10693
10999
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
10694
11000
|
}, ms);
|
|
10695
11001
|
promise.then((val) => {
|
|
10696
11002
|
clearTimeout(timer);
|
|
10697
|
-
|
|
11003
|
+
resolve9(val);
|
|
10698
11004
|
}).catch((err) => {
|
|
10699
11005
|
clearTimeout(timer);
|
|
10700
11006
|
reject(err);
|
|
@@ -11126,6 +11432,7 @@ var SkillManager = class {
|
|
|
11126
11432
|
|
|
11127
11433
|
// src/web/tool-executor-web.ts
|
|
11128
11434
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11435
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
11129
11436
|
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
|
|
11130
11437
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
11131
11438
|
constructor(registry, ws) {
|
|
@@ -11137,6 +11444,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11137
11444
|
hookConfig;
|
|
11138
11445
|
permissionRules = [];
|
|
11139
11446
|
defaultPermission = "confirm";
|
|
11447
|
+
permissionProfileName = "legacy";
|
|
11448
|
+
permissionProfile;
|
|
11449
|
+
allowedPermissionProfiles = [];
|
|
11450
|
+
workspaceRoot = process.cwd();
|
|
11451
|
+
networkPolicy;
|
|
11140
11452
|
/** Pending confirm promises keyed by requestId */
|
|
11141
11453
|
pendingConfirms = /* @__PURE__ */ new Map();
|
|
11142
11454
|
/** Pending batch confirm promises */
|
|
@@ -11171,6 +11483,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11171
11483
|
this.hookConfig = opts.hookConfig;
|
|
11172
11484
|
if (opts.permissionRules) this.permissionRules = opts.permissionRules;
|
|
11173
11485
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
11486
|
+
if (opts.permissionProfileName) this.permissionProfileName = opts.permissionProfileName;
|
|
11487
|
+
if (opts.permissionProfile) this.permissionProfile = opts.permissionProfile;
|
|
11488
|
+
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
11489
|
+
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
11490
|
+
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
11174
11491
|
}
|
|
11175
11492
|
/** Clear M7 timeout timer for a requestId */
|
|
11176
11493
|
clearPendingTimer(requestId) {
|
|
@@ -11182,33 +11499,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11182
11499
|
}
|
|
11183
11500
|
/** Resolve a pending confirm from client response */
|
|
11184
11501
|
resolveConfirm(requestId, approved) {
|
|
11185
|
-
const
|
|
11186
|
-
if (
|
|
11502
|
+
const resolve9 = this.pendingConfirms.get(requestId);
|
|
11503
|
+
if (resolve9) {
|
|
11187
11504
|
this.clearPendingTimer(requestId);
|
|
11188
11505
|
this.pendingConfirms.delete(requestId);
|
|
11189
11506
|
this.confirming = false;
|
|
11190
|
-
|
|
11507
|
+
resolve9(approved);
|
|
11191
11508
|
}
|
|
11192
11509
|
}
|
|
11193
11510
|
/** Resolve a pending batch confirm from client response */
|
|
11194
11511
|
resolveBatchConfirm(requestId, decision) {
|
|
11195
|
-
const
|
|
11196
|
-
if (
|
|
11512
|
+
const resolve9 = this.pendingBatchConfirms.get(requestId);
|
|
11513
|
+
if (resolve9) {
|
|
11197
11514
|
this.clearPendingTimer(requestId);
|
|
11198
11515
|
this.pendingBatchConfirms.delete(requestId);
|
|
11199
11516
|
this.confirming = false;
|
|
11200
11517
|
if (decision === "all" || decision === "none") {
|
|
11201
|
-
|
|
11518
|
+
resolve9(decision);
|
|
11202
11519
|
} else {
|
|
11203
|
-
|
|
11520
|
+
resolve9(new Set(decision));
|
|
11204
11521
|
}
|
|
11205
11522
|
}
|
|
11206
11523
|
}
|
|
11207
11524
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
11208
11525
|
cancelAll() {
|
|
11209
|
-
for (const
|
|
11526
|
+
for (const resolve9 of this.pendingConfirms.values()) resolve9(false);
|
|
11210
11527
|
this.pendingConfirms.clear();
|
|
11211
|
-
for (const
|
|
11528
|
+
for (const resolve9 of this.pendingBatchConfirms.values()) resolve9("none");
|
|
11212
11529
|
this.pendingBatchConfirms.clear();
|
|
11213
11530
|
this.confirming = false;
|
|
11214
11531
|
}
|
|
@@ -11219,6 +11536,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11219
11536
|
}
|
|
11220
11537
|
sendToolCallStart(call) {
|
|
11221
11538
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
11539
|
+
let toolCallStarted = false;
|
|
11222
11540
|
const startTime = Date.now();
|
|
11223
11541
|
this.toolStartTimes.set(call.id, startTime);
|
|
11224
11542
|
const msg = {
|
|
@@ -11284,8 +11602,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11284
11602
|
diff: this.getDiffPreview(call)
|
|
11285
11603
|
};
|
|
11286
11604
|
this.send(msg);
|
|
11287
|
-
return new Promise((
|
|
11288
|
-
this.pendingConfirms.set(requestId,
|
|
11605
|
+
return new Promise((resolve9) => {
|
|
11606
|
+
this.pendingConfirms.set(requestId, resolve9);
|
|
11289
11607
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11290
11608
|
if (this.pendingConfirms.has(requestId)) {
|
|
11291
11609
|
this.resolveConfirm(requestId, false);
|
|
@@ -11309,8 +11627,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11309
11627
|
files
|
|
11310
11628
|
};
|
|
11311
11629
|
this.send(msg);
|
|
11312
|
-
return new Promise((
|
|
11313
|
-
this.pendingBatchConfirms.set(requestId,
|
|
11630
|
+
return new Promise((resolve9) => {
|
|
11631
|
+
this.pendingBatchConfirms.set(requestId, resolve9);
|
|
11314
11632
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11315
11633
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
11316
11634
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -11327,30 +11645,66 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11327
11645
|
return { callId: call.id, content: `Unknown tool: ${call.name}`, isError: true };
|
|
11328
11646
|
}
|
|
11329
11647
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
11648
|
+
let toolCallStarted = false;
|
|
11330
11649
|
runHook(this.hookConfig?.preToolExecution, {
|
|
11331
11650
|
tool: call.name,
|
|
11332
11651
|
dangerLevel,
|
|
11333
11652
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
11334
11653
|
});
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11654
|
+
const permission = checkPermissionWithProfile(
|
|
11655
|
+
call.name,
|
|
11656
|
+
call.arguments,
|
|
11657
|
+
dangerLevel,
|
|
11658
|
+
this.permissionRules,
|
|
11659
|
+
this.defaultPermission,
|
|
11660
|
+
{
|
|
11661
|
+
profileName: this.permissionProfileName,
|
|
11662
|
+
profile: this.permissionProfile,
|
|
11663
|
+
allowedProfiles: this.allowedPermissionProfiles,
|
|
11664
|
+
workspaceRoot: this.workspaceRoot,
|
|
11665
|
+
tempDirs: [tmpdir2()]
|
|
11339
11666
|
}
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11667
|
+
);
|
|
11668
|
+
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
11669
|
+
if (networkPermission?.action === "deny") {
|
|
11670
|
+
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
11671
|
+
return {
|
|
11672
|
+
callId: call.id,
|
|
11673
|
+
content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
|
|
11674
|
+
isError: true
|
|
11675
|
+
};
|
|
11676
|
+
}
|
|
11677
|
+
if (permission.action === "deny") {
|
|
11678
|
+
const reason = permission.reason ? ` (${permission.reason})` : "";
|
|
11679
|
+
return {
|
|
11680
|
+
callId: call.id,
|
|
11681
|
+
content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
|
|
11682
|
+
isError: true
|
|
11683
|
+
};
|
|
11684
|
+
}
|
|
11685
|
+
if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
|
|
11686
|
+
this.sendToolCallStart(call);
|
|
11687
|
+
try {
|
|
11688
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
11689
|
+
const content = truncateOutput(rawContent, call.name);
|
|
11690
|
+
this.sendToolCallResult(call, rawContent, false);
|
|
11691
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
11692
|
+
return { callId: call.id, content, isError: false };
|
|
11693
|
+
} catch (err) {
|
|
11694
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11695
|
+
this.sendToolCallResult(call, message, true);
|
|
11696
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
11697
|
+
return { callId: call.id, content: message, isError: true };
|
|
11698
|
+
}
|
|
11699
|
+
}
|
|
11700
|
+
if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
|
|
11701
|
+
this.sendToolCallStart(call);
|
|
11702
|
+
toolCallStarted = true;
|
|
11703
|
+
const confirmed = await this.confirm(call, "write");
|
|
11704
|
+
if (!confirmed) {
|
|
11705
|
+
const rejectionMsg = `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`;
|
|
11706
|
+
this.sendToolCallResult(call, rejectionMsg, true);
|
|
11707
|
+
return { callId: call.id, content: rejectionMsg, isError: true };
|
|
11354
11708
|
}
|
|
11355
11709
|
}
|
|
11356
11710
|
this.sendToolCallStart(call);
|
|
@@ -11409,6 +11763,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11409
11763
|
return results;
|
|
11410
11764
|
}
|
|
11411
11765
|
async executeBatchFileWrites(items) {
|
|
11766
|
+
if (this.permissionProfileName !== "legacy" || this.permissionRules.length > 0) {
|
|
11767
|
+
return Promise.all(items.map(({ call }) => this.execute(call)));
|
|
11768
|
+
}
|
|
11412
11769
|
const calls = items.map((i) => i.call);
|
|
11413
11770
|
const decision = this.sessionAutoApprove ? "all" : await this.batchConfirm(calls);
|
|
11414
11771
|
const results = new Array(calls.length);
|
|
@@ -11737,7 +12094,7 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
11737
12094
|
|
|
11738
12095
|
// src/core/context-files.ts
|
|
11739
12096
|
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
11740
|
-
import { join as join13, relative as relative3, resolve as
|
|
12097
|
+
import { join as join13, relative as relative3, resolve as resolve6 } from "path";
|
|
11741
12098
|
function uniqueNonEmpty(values) {
|
|
11742
12099
|
const seen = /* @__PURE__ */ new Set();
|
|
11743
12100
|
const out = [];
|
|
@@ -11760,7 +12117,7 @@ function displayPath(filePath, cwd) {
|
|
|
11760
12117
|
return filePath;
|
|
11761
12118
|
}
|
|
11762
12119
|
function isInsideOrEqual(parent, child) {
|
|
11763
|
-
const rel = relative3(
|
|
12120
|
+
const rel = relative3(resolve6(parent), resolve6(child));
|
|
11764
12121
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
11765
12122
|
}
|
|
11766
12123
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
@@ -11823,7 +12180,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
11823
12180
|
return { layer: null, skipped };
|
|
11824
12181
|
}
|
|
11825
12182
|
function loadContextFiles(options) {
|
|
11826
|
-
const cwd =
|
|
12183
|
+
const cwd = resolve6(options.cwd);
|
|
11827
12184
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
11828
12185
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
11829
12186
|
const skipped = [];
|
|
@@ -11831,7 +12188,7 @@ function loadContextFiles(options) {
|
|
|
11831
12188
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11832
12189
|
}
|
|
11833
12190
|
if (options.setting && options.setting !== "auto") {
|
|
11834
|
-
const filePath =
|
|
12191
|
+
const filePath = resolve6(cwd, String(options.setting));
|
|
11835
12192
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
11836
12193
|
skipped.push({
|
|
11837
12194
|
level: "single",
|
|
@@ -11875,7 +12232,7 @@ function loadContextFiles(options) {
|
|
|
11875
12232
|
skipped.push(...result.skipped);
|
|
11876
12233
|
if (result.layer) layers.push(result.layer);
|
|
11877
12234
|
}
|
|
11878
|
-
if (
|
|
12235
|
+
if (resolve6(options.cwd) !== resolve6(options.projectRoot)) {
|
|
11879
12236
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
11880
12237
|
skipped.push(...result.skipped);
|
|
11881
12238
|
if (result.layer) layers.push(result.layer);
|
|
@@ -11894,7 +12251,7 @@ function loadContextFiles(options) {
|
|
|
11894
12251
|
|
|
11895
12252
|
// src/web/session-handler.ts
|
|
11896
12253
|
import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
|
|
11897
|
-
import { join as join17, resolve as
|
|
12254
|
+
import { join as join17, resolve as resolve7, dirname as dirname5 } from "path";
|
|
11898
12255
|
|
|
11899
12256
|
// src/tools/git-context.ts
|
|
11900
12257
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -12953,7 +13310,22 @@ var SessionHandler = class {
|
|
|
12953
13310
|
const hooks = this.config.get("hooks");
|
|
12954
13311
|
const permissionRules = this.config.get("permissionRules");
|
|
12955
13312
|
const defaultPermission = this.config.get("defaultPermission");
|
|
12956
|
-
this.
|
|
13313
|
+
const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
|
|
13314
|
+
const permissionProfiles = this.config.get("permissionProfiles") ?? {};
|
|
13315
|
+
this.toolExecutor.setConfig({
|
|
13316
|
+
hookConfig: hooks,
|
|
13317
|
+
permissionRules,
|
|
13318
|
+
defaultPermission,
|
|
13319
|
+
permissionProfileName,
|
|
13320
|
+
permissionProfile: permissionProfiles[permissionProfileName],
|
|
13321
|
+
allowedPermissionProfiles: this.config.get("allowedPermissionProfiles") ?? [],
|
|
13322
|
+
workspaceRoot: process.cwd(),
|
|
13323
|
+
networkPolicy: this.config.get("networkPolicy")
|
|
13324
|
+
});
|
|
13325
|
+
const permissionProfileWarning = formatPermissionProfileWarning(permissionProfileName);
|
|
13326
|
+
if (permissionProfileWarning) {
|
|
13327
|
+
this.send({ type: "info", message: permissionProfileWarning });
|
|
13328
|
+
}
|
|
12957
13329
|
this.sendStatus();
|
|
12958
13330
|
askUserContext.rl = null;
|
|
12959
13331
|
askUserContext.prompting = false;
|
|
@@ -12988,6 +13360,7 @@ var SessionHandler = class {
|
|
|
12988
13360
|
messageCount: this.sessions.current?.messages.length ?? 0,
|
|
12989
13361
|
planMode: this.planMode,
|
|
12990
13362
|
thinkingMode: this.runtimeThinking ?? false,
|
|
13363
|
+
permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
|
|
12991
13364
|
tokenUsage: { ...this.sessionTokenUsage },
|
|
12992
13365
|
costUsd,
|
|
12993
13366
|
providers: providerList,
|
|
@@ -13016,10 +13389,10 @@ var SessionHandler = class {
|
|
|
13016
13389
|
return;
|
|
13017
13390
|
}
|
|
13018
13391
|
case "ask_user_response": {
|
|
13019
|
-
const
|
|
13020
|
-
if (
|
|
13392
|
+
const resolve9 = this.pendingAskUser.get(msg.requestId);
|
|
13393
|
+
if (resolve9) {
|
|
13021
13394
|
this.pendingAskUser.delete(msg.requestId);
|
|
13022
|
-
|
|
13395
|
+
resolve9(msg.answer);
|
|
13023
13396
|
}
|
|
13024
13397
|
return;
|
|
13025
13398
|
}
|
|
@@ -13030,10 +13403,10 @@ var SessionHandler = class {
|
|
|
13030
13403
|
case "memory_rebuild":
|
|
13031
13404
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
13032
13405
|
case "auto_pause_response": {
|
|
13033
|
-
const
|
|
13034
|
-
if (
|
|
13406
|
+
const resolve9 = this.pendingAutoPause.get(msg.requestId);
|
|
13407
|
+
if (resolve9) {
|
|
13035
13408
|
this.pendingAutoPause.delete(msg.requestId);
|
|
13036
|
-
|
|
13409
|
+
resolve9({ action: msg.action, message: msg.message });
|
|
13037
13410
|
}
|
|
13038
13411
|
return;
|
|
13039
13412
|
}
|
|
@@ -13048,10 +13421,10 @@ var SessionHandler = class {
|
|
|
13048
13421
|
this.hubOrchestrator?.abort();
|
|
13049
13422
|
return;
|
|
13050
13423
|
case "hub_steer": {
|
|
13051
|
-
const
|
|
13052
|
-
if (
|
|
13424
|
+
const resolve9 = this.pendingHubReview.get(msg.requestId);
|
|
13425
|
+
if (resolve9) {
|
|
13053
13426
|
this.pendingHubReview.delete(msg.requestId);
|
|
13054
|
-
|
|
13427
|
+
resolve9({ action: msg.action, message: msg.message });
|
|
13055
13428
|
}
|
|
13056
13429
|
return;
|
|
13057
13430
|
}
|
|
@@ -13063,9 +13436,9 @@ var SessionHandler = class {
|
|
|
13063
13436
|
onDisconnect() {
|
|
13064
13437
|
this.toolExecutor.cancelAll();
|
|
13065
13438
|
if (this.abortController) this.abortController.abort();
|
|
13066
|
-
for (const
|
|
13439
|
+
for (const resolve9 of this.pendingAskUser.values()) resolve9(null);
|
|
13067
13440
|
this.pendingAskUser.clear();
|
|
13068
|
-
for (const
|
|
13441
|
+
for (const resolve9 of this.pendingAutoPause.values()) resolve9({ action: "stop" });
|
|
13069
13442
|
this.pendingAutoPause.clear();
|
|
13070
13443
|
this.saveIfNeeded();
|
|
13071
13444
|
}
|
|
@@ -13199,9 +13572,9 @@ var SessionHandler = class {
|
|
|
13199
13572
|
this.hubOrchestrator = orchestrator;
|
|
13200
13573
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
13201
13574
|
if (config.humanSteer) {
|
|
13202
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
13575
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve9) => {
|
|
13203
13576
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13204
|
-
this.pendingHubReview.set(requestId,
|
|
13577
|
+
this.pendingHubReview.set(requestId, resolve9);
|
|
13205
13578
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
13206
13579
|
});
|
|
13207
13580
|
}
|
|
@@ -13538,8 +13911,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
13538
13911
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
13539
13912
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
13540
13913
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13541
|
-
const pauseResp = await new Promise((
|
|
13542
|
-
this.pendingAutoPause.set(requestId,
|
|
13914
|
+
const pauseResp = await new Promise((resolve9) => {
|
|
13915
|
+
this.pendingAutoPause.set(requestId, resolve9);
|
|
13543
13916
|
this.send({
|
|
13544
13917
|
type: "auto_pause_request",
|
|
13545
13918
|
requestId,
|
|
@@ -13718,8 +14091,8 @@ ${summaryContent}`,
|
|
|
13718
14091
|
}
|
|
13719
14092
|
if (chunk.done) break;
|
|
13720
14093
|
}
|
|
13721
|
-
await new Promise((
|
|
13722
|
-
fileStream.end((err) => err ? reject(err) :
|
|
14094
|
+
await new Promise((resolve9, reject) => {
|
|
14095
|
+
fileStream.end((err) => err ? reject(err) : resolve9());
|
|
13723
14096
|
});
|
|
13724
14097
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
13725
14098
|
if (verdict.kind === "reject") {
|
|
@@ -13745,7 +14118,7 @@ ${summaryContent}`,
|
|
|
13745
14118
|
} catch (err) {
|
|
13746
14119
|
if (fileStream) {
|
|
13747
14120
|
try {
|
|
13748
|
-
await new Promise((
|
|
14121
|
+
await new Promise((resolve9) => fileStream.end(() => resolve9()));
|
|
13749
14122
|
} catch {
|
|
13750
14123
|
}
|
|
13751
14124
|
}
|
|
@@ -14378,7 +14751,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14378
14751
|
break;
|
|
14379
14752
|
}
|
|
14380
14753
|
const sub = args[0]?.toLowerCase();
|
|
14381
|
-
const
|
|
14754
|
+
const resolve9 = (ref) => {
|
|
14382
14755
|
const r = session.resolveBranchRef(ref);
|
|
14383
14756
|
if (r.ok) return r.id;
|
|
14384
14757
|
if (r.reason === "ambiguous") {
|
|
@@ -14432,7 +14805,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14432
14805
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
14433
14806
|
break;
|
|
14434
14807
|
}
|
|
14435
|
-
const id =
|
|
14808
|
+
const id = resolve9(ref);
|
|
14436
14809
|
if (!id) break;
|
|
14437
14810
|
const ok = session.switchBranch(id);
|
|
14438
14811
|
if (ok) {
|
|
@@ -14452,7 +14825,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14452
14825
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
14453
14826
|
break;
|
|
14454
14827
|
}
|
|
14455
|
-
const id =
|
|
14828
|
+
const id = resolve9(ref);
|
|
14456
14829
|
if (!id) break;
|
|
14457
14830
|
const ok = session.deleteBranch(id);
|
|
14458
14831
|
if (ok) {
|
|
@@ -14471,7 +14844,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14471
14844
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
14472
14845
|
break;
|
|
14473
14846
|
}
|
|
14474
|
-
const id =
|
|
14847
|
+
const id = resolve9(ref);
|
|
14475
14848
|
if (!id) break;
|
|
14476
14849
|
const ok = session.renameBranch(id, title);
|
|
14477
14850
|
if (ok) {
|
|
@@ -14489,7 +14862,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14489
14862
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
14490
14863
|
break;
|
|
14491
14864
|
}
|
|
14492
|
-
const id =
|
|
14865
|
+
const id = resolve9(ref);
|
|
14493
14866
|
if (!id) break;
|
|
14494
14867
|
const d = session.diffBranches(id);
|
|
14495
14868
|
if (!d) {
|
|
@@ -14531,7 +14904,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14531
14904
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
14532
14905
|
break;
|
|
14533
14906
|
}
|
|
14534
|
-
const id =
|
|
14907
|
+
const id = resolve9(ref);
|
|
14535
14908
|
if (!id) break;
|
|
14536
14909
|
const idx = parseInt(idxStr, 10);
|
|
14537
14910
|
if (Number.isNaN(idx)) {
|
|
@@ -14793,7 +15166,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14793
15166
|
case "test": {
|
|
14794
15167
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
14795
15168
|
try {
|
|
14796
|
-
const { executeTests } = await import("./run-tests-
|
|
15169
|
+
const { executeTests } = await import("./run-tests-PACN4UYX.js");
|
|
14797
15170
|
const argStr = args.join(" ").trim();
|
|
14798
15171
|
let testArgs = {};
|
|
14799
15172
|
if (argStr) {
|
|
@@ -15044,7 +15417,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
15044
15417
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
15045
15418
|
break;
|
|
15046
15419
|
}
|
|
15047
|
-
const dirPath =
|
|
15420
|
+
const dirPath = resolve7(sub);
|
|
15048
15421
|
if (!existsSync23(dirPath)) {
|
|
15049
15422
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
15050
15423
|
break;
|
|
@@ -15816,7 +16189,7 @@ function getModuleDir() {
|
|
|
15816
16189
|
}
|
|
15817
16190
|
} catch {
|
|
15818
16191
|
}
|
|
15819
|
-
return
|
|
16192
|
+
return resolve8(".");
|
|
15820
16193
|
}
|
|
15821
16194
|
async function startWebServer(options = {}) {
|
|
15822
16195
|
const port = options.port ?? 3e3;
|
|
@@ -16033,8 +16406,8 @@ async function startWebServer(options = {}) {
|
|
|
16033
16406
|
const prefix = req.query.prefix || "";
|
|
16034
16407
|
const targetDir = join19(cwd, prefix);
|
|
16035
16408
|
try {
|
|
16036
|
-
const canonicalTarget = realpathSync(
|
|
16037
|
-
const canonicalCwd = realpathSync(
|
|
16409
|
+
const canonicalTarget = realpathSync(resolve8(targetDir));
|
|
16410
|
+
const canonicalCwd = realpathSync(resolve8(cwd));
|
|
16038
16411
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
16039
16412
|
res.json({ files: [] });
|
|
16040
16413
|
return;
|
|
@@ -16113,10 +16486,10 @@ async function startWebServer(options = {}) {
|
|
|
16113
16486
|
return;
|
|
16114
16487
|
}
|
|
16115
16488
|
const cwd = process.cwd();
|
|
16116
|
-
const fullPath =
|
|
16489
|
+
const fullPath = resolve8(join19(cwd, filePath));
|
|
16117
16490
|
try {
|
|
16118
16491
|
const canonicalFull = realpathSync(fullPath);
|
|
16119
|
-
const canonicalCwd = realpathSync(
|
|
16492
|
+
const canonicalCwd = realpathSync(resolve8(cwd));
|
|
16120
16493
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
16121
16494
|
res.json({ error: "Access denied" });
|
|
16122
16495
|
return;
|
|
@@ -16335,7 +16708,7 @@ async function startWebServer(options = {}) {
|
|
|
16335
16708
|
});
|
|
16336
16709
|
const MAX_PORT_ATTEMPTS = 10;
|
|
16337
16710
|
let actualPort = port;
|
|
16338
|
-
const result = await new Promise((
|
|
16711
|
+
const result = await new Promise((resolve9, reject) => {
|
|
16339
16712
|
const tryListen = (attempt) => {
|
|
16340
16713
|
server.once("error", (err) => {
|
|
16341
16714
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -16366,7 +16739,7 @@ async function startWebServer(options = {}) {
|
|
|
16366
16739
|
}
|
|
16367
16740
|
console.log(` Press Ctrl+C to stop
|
|
16368
16741
|
`);
|
|
16369
|
-
|
|
16742
|
+
resolve9({ port: actualPort, host, url });
|
|
16370
16743
|
});
|
|
16371
16744
|
};
|
|
16372
16745
|
tryListen(1);
|