zen-gitsync 2.17.46 → 2.17.48
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 +16 -2
- package/package.json +2 -1
- package/src/cli/ai/agent.js +1342 -1342
- package/src/cli/ai/context.js +431 -253
- package/src/cli/ai/context.test.js +376 -258
- package/src/cli/ai/mcp.js +323 -27
- package/src/cli/ai/mcp.test.js +202 -2
- package/src/cli/ai/runtime.test.js +7 -4
- package/src/cli/ai/turn.js +166 -166
- package/src/config.js +906 -871
- package/src/ui/public/assets/{AgentEngineSelector-D6rlSfMD.js → AgentEngineSelector-B829U5mr.js} +1 -1
- package/src/ui/public/assets/AgentView-r3_ClUmr.js +1 -0
- package/src/ui/public/assets/{AgentView-h2YnbB7J.css → AgentView-uHf-KMCH.css} +1 -1
- package/src/ui/public/assets/{AppVersionBadge-BnPsn1X5.js → AppVersionBadge-NtghusOI.js} +2 -2
- package/src/ui/public/assets/{BranchSelector-D30GJwUl.js → BranchSelector-D0claXAs.js} +1 -1
- package/src/ui/public/assets/{CommitForm-Dvg-RUZI.js → CommitForm-BIq6-sqk.js} +1 -1
- package/src/ui/public/assets/{CommonDialog-BzLau2RJ.js → CommonDialog-NqZURH2F.js} +1 -1
- package/src/ui/public/assets/EditorView-0T5LR5Hz.css +1 -0
- package/src/ui/public/assets/EditorView-Cny0lDhG.js +1 -0
- package/src/ui/public/assets/{FlowExecutionViewer-VL-rUElj.js → FlowExecutionViewer-DxDKzmaP.js} +1 -1
- package/src/ui/public/assets/{FlowOrchestrationWorkspace-BPNlHRVu.js → FlowOrchestrationWorkspace-B3CAJZ-U.js} +1 -1
- package/src/ui/public/assets/{LogList-COwGjQl6.js → LogList-D5sS4Il3.js} +1 -1
- package/src/ui/public/assets/{MindmapView-BVuqPD_H.js → MindmapView-6bc0EAJM.js} +1 -1
- package/src/ui/public/assets/{MonitorView-hqe-4xd0.js → MonitorView-BnUV4RaV.js} +1 -1
- package/src/ui/public/assets/{ProjectStartupButton-RBc-DJBR.js → ProjectStartupButton-Cbw079DB.js} +1 -1
- package/src/ui/public/assets/RecentDirectoriesChat-BCZsJR7n.js +1 -0
- package/src/ui/public/assets/RecentDirectoriesChat-THkqWEtO.css +1 -0
- package/src/ui/public/assets/{RemoteManagerDialog-D6Rbjchl.js → RemoteManagerDialog-BWWPEnSd.js} +1 -1
- package/src/ui/public/assets/{RemoteRepoCard-l0NpXgvB.js → RemoteRepoCard-BhA3-4PZ.js} +1 -1
- package/src/ui/public/assets/{SourceMapView-CeVystt0.js → SourceMapView-DHDmNzSh.js} +1 -1
- package/src/ui/public/assets/{SvgIcon-B-xDJQA1.js → SvgIcon-DcVasCdo.js} +1 -1
- package/src/ui/public/assets/{UserInputNode-DJglU68l.js → UserInputNode-B313MadX.js} +1 -1
- package/src/ui/public/assets/WorkbenchView-NCE_bk5a.js +20 -0
- package/src/ui/public/assets/WorkbenchView-rscs76HF.css +1 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-Bsx6Zj3t.js +19 -0
- package/src/ui/public/assets/agentConversations-BAhGYmeY.js +8 -0
- package/src/ui/public/assets/{configStore-CMlW8sMa.js → configStore-DQSKV2_Y.js} +1 -1
- package/src/ui/public/assets/{dagre-DyI0XLXY.js → dagre-BmYeUlEa.js} +3 -3
- package/src/ui/public/assets/{element-plus-DrCk0pcV.js → element-plus-DTtm3XCd.js} +1 -1
- package/src/ui/public/assets/{flow-mindmap-Cx5RNYN9.js → flow-mindmap-JxOspgwx.js} +1 -1
- package/src/ui/public/assets/{index-u9LyUFkr.css → index-BgN6Z2X9.css} +1 -1
- package/src/ui/public/assets/{index-BQxIFopZ.js → index-DQuqmPuL.js} +9 -9
- package/src/ui/public/assets/{monaco-Dfcwm1aX.js → monaco-D3R9OM26.js} +1 -1
- package/src/ui/public/assets/{office-docx-C0unxEAm.js → office-docx-CO7mIaWO.js} +1 -1
- package/src/ui/public/assets/{office-excel-DPdyr_DJ.js → office-excel-gA4KEkK4.js} +1 -1
- package/src/ui/public/assets/{office-pptx-eojGAs9D.js → office-pptx-CQK4l_1Q.js} +1 -1
- package/src/ui/public/assets/{vendor-yntih_em.js → vendor-O17eRefg.js} +222 -222
- package/src/ui/public/assets/vendor-U3ZXMgeO.css +1 -0
- package/src/ui/public/assets/{vue-flow-De8mr_Fv.js → vue-flow-BG6ZF1It.js} +1 -1
- package/src/ui/public/index.html +13 -13
- package/src/ui/server/routes/config.js +1321 -1314
- package/src/ui/server/routes/workbench/agentChat.js +594 -571
- package/src/ui/server/routes/workbench/agentChatShared.test.js +302 -233
- package/src/ui/server/routes/workbench/agentMarketplace.js +65 -30
- package/src/ui/server/routes/workbench/agentMarketplace.test.js +28 -4
- package/src/ui/server/routes/workbench/agentRoutes.js +631 -631
- package/src/ui/server/routes/workbench/liveJobs.js +4 -0
- package/src/ui/server/routes/workbench/liveJobs.test.js +11 -0
- package/src/ui/server/routes/workbench/taskRunner.js +44 -7
- package/src/ui/server/routes/workbench/taskRunner.thinking.test.js +115 -0
- package/src/ui/public/assets/AgentView-BFRGoIVb.js +0 -1
- package/src/ui/public/assets/EditorView-8Rb4n-Wh.css +0 -1
- package/src/ui/public/assets/EditorView-D4ZQutRw.js +0 -1
- package/src/ui/public/assets/RecentDirectoriesChat-BxbpKrXc.css +0 -1
- package/src/ui/public/assets/RecentDirectoriesChat-DFljcYFH.js +0 -1
- package/src/ui/public/assets/WorkbenchView-BSQ87CDi.css +0 -1
- package/src/ui/public/assets/WorkbenchView-BpH5kNht.js +0 -20
- package/src/ui/public/assets/_plugin-vue_export-helper-Dz1ARW9a.js +0 -19
- package/src/ui/public/assets/agentConversations-B8nY45V4.js +0 -7
- package/src/ui/public/assets/vendor-CP-WuGZG.css +0 -1
|
@@ -704,7 +704,8 @@ async function installedMcpsIn(file, target) {
|
|
|
704
704
|
name: clampText(server?.name || id, 80),
|
|
705
705
|
description: clampText(server?.description || '', 400),
|
|
706
706
|
package: server?.package || '',
|
|
707
|
-
|
|
707
|
+
// 手写的 url 型(http 传输)没有 command,退回显示 url —— 否则列表里这一项看起来是空的
|
|
708
|
+
command: server?.command || server?.url || '',
|
|
708
709
|
args: Array.isArray(server?.args) ? server.args : [],
|
|
709
710
|
envKeys: Object.keys(server?.env || {}),
|
|
710
711
|
file,
|
|
@@ -878,11 +879,10 @@ async function installSkill({ item, target, cwd }) {
|
|
|
878
879
|
async function installMcp({ item, target, cwd }) {
|
|
879
880
|
const packageName = String(item.package || '').trim();
|
|
880
881
|
const isStdio = !!packageName;
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const bridge = !isStdio && item.remoteUrl ? 'mcp-remote' : '';
|
|
882
|
+
const remoteUrl = String(item.remoteUrl || '').trim();
|
|
883
|
+
const isRemote = !isStdio && /^https?:\/\//i.test(remoteUrl);
|
|
884
884
|
|
|
885
|
-
if (!isStdio && !
|
|
885
|
+
if (!isStdio && !isRemote) {
|
|
886
886
|
throw new HttpError(422, '该 MCP 服务没有提供 npm 包或远程端点,暂时无法一键安装');
|
|
887
887
|
}
|
|
888
888
|
if (isStdio && !SAFE_PACKAGE.test(packageName)) throw new HttpError(400, 'MCP npm 包名不合法');
|
|
@@ -896,6 +896,54 @@ async function installMcp({ item, target, cwd }) {
|
|
|
896
896
|
throw new HttpError(409, `该 MCP 服务已安装在${paths.label}`);
|
|
897
897
|
}
|
|
898
898
|
|
|
899
|
+
// 安装时顺手收集的键值(弹窗里用户填的)。只收合法的标识符键,
|
|
900
|
+
// 值为空的键跳过 —— 留给用户以后手改配置文件,而不是写一个空值进去让 server 起不来。
|
|
901
|
+
const filled = {};
|
|
902
|
+
if (item.env && typeof item.env === 'object') {
|
|
903
|
+
for (const [key, value] of Object.entries(item.env)) {
|
|
904
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(String(key))) continue;
|
|
905
|
+
const text = String(value ?? '').trim();
|
|
906
|
+
if (text) filled[key] = clampText(text, 2000);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// 还缺哪些必填键,记下来给 UI 提示"装好了但还差配置"
|
|
910
|
+
const missingKeys = (Array.isArray(item.envKeys) ? item.envKeys : []).filter(key => !filled[key]);
|
|
911
|
+
const name = item.name || id;
|
|
912
|
+
const description = clampText(item.description || '', 400);
|
|
913
|
+
|
|
914
|
+
// ── 远程端点:直接写 http 配置,g ai 自己的 Streamable HTTP 客户端直连 ──
|
|
915
|
+
//
|
|
916
|
+
// 早先这里是桥成 stdio(npx -y mcp-remote <url>)。桥本身没错,但**鉴权传不进去**:
|
|
917
|
+
// mcp-remote 认的是它自己的 --header 约定,不是我们收集的那组键值 ——
|
|
918
|
+
// 表现就是"装好了但用不了"。g ai 现已原生支持 Streamable HTTP,直接落 url + headers
|
|
919
|
+
// 更短、更可控,也少一层随包升级漂移的中间件。
|
|
920
|
+
if (isRemote) {
|
|
921
|
+
const headers = {};
|
|
922
|
+
if (item.headers && typeof item.headers === 'object') {
|
|
923
|
+
for (const [key, value] of Object.entries(item.headers)) {
|
|
924
|
+
if (!key || value === undefined || value === null) continue;
|
|
925
|
+
headers[String(key)] = clampText(String(value), 2000);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
// 弹窗收集的键值对远程服务来说就是请求头(没有子进程,env 无处可落)
|
|
929
|
+
Object.assign(headers, filled);
|
|
930
|
+
|
|
931
|
+
config.mcpServers[id] = {
|
|
932
|
+
name,
|
|
933
|
+
description,
|
|
934
|
+
type: 'http',
|
|
935
|
+
url: remoteUrl,
|
|
936
|
+
headers,
|
|
937
|
+
transport: 'remote',
|
|
938
|
+
requiredEnv: missingKeys,
|
|
939
|
+
};
|
|
940
|
+
await fs.mkdir(path.dirname(paths.file), { recursive: true });
|
|
941
|
+
await fs.writeFile(paths.file, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
942
|
+
return { id, type: 'mcp', name, target, file: paths.file, envKeys: missingKeys, warning: '' };
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// ── npm 包:stdio ──
|
|
946
|
+
//
|
|
899
947
|
// 项目级顺手 npm install 一次:把版本钉进 package.json,启动也更快。
|
|
900
948
|
// 失败不阻断 —— npx 运行时仍会按需下载,只是慢一点。
|
|
901
949
|
//
|
|
@@ -903,49 +951,36 @@ async function installMcp({ item, target, cwd }) {
|
|
|
903
951
|
// 装到 ~/.zen-gitsync/ai/node_modules 里 npx 根本解析不到,等于白装一份;
|
|
904
952
|
// npx 自己的缓存本来就是按用户全局共享的,首次启动下载一次即可。
|
|
905
953
|
let installWarning = '';
|
|
906
|
-
const primary = isStdio ? packageName : bridge;
|
|
907
954
|
if (target === 'project') {
|
|
908
955
|
try {
|
|
909
|
-
await execFileAsync(npmBinary(), ['install', '--save-dev',
|
|
956
|
+
await execFileAsync(npmBinary(), ['install', '--save-dev', packageName], { cwd, timeout: NPM_TIMEOUT_MS, windowsHide: true });
|
|
910
957
|
} catch (err) {
|
|
911
958
|
installWarning = `npm install 未成功(${clampText(err.message, 200)}),已写入配置,首次启动时会由 npx 按需下载`;
|
|
912
959
|
}
|
|
913
960
|
}
|
|
914
961
|
|
|
915
|
-
const args =
|
|
916
|
-
? (Array.isArray(item.args) && item.args.length ? item.args : ['-y', packageName])
|
|
917
|
-
: ['-y', bridge, String(item.remoteUrl)];
|
|
962
|
+
const args = Array.isArray(item.args) && item.args.length ? item.args : ['-y', packageName];
|
|
918
963
|
const replaceProject = value => String(value).replaceAll('${project}', cwd || process.cwd());
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
if (item.env && typeof item.env === 'object') {
|
|
924
|
-
for (const [key, value] of Object.entries(item.env)) {
|
|
925
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(key))) continue;
|
|
926
|
-
const text = String(value ?? '').trim();
|
|
927
|
-
if (text) env[key] = clampText(text, 2000);
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
// 还缺哪些必填环境变量,记下来给 UI 提示"装好了但还差配置"
|
|
931
|
-
const missingEnv = (Array.isArray(item.envKeys) ? item.envKeys : []).filter(key => !env[key]);
|
|
964
|
+
// 环境变量名比请求头名严(不接受连字符),stdio 这条路再筛一遍
|
|
965
|
+
const stdioEnv = Object.fromEntries(
|
|
966
|
+
Object.entries(filled).filter(([key]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)),
|
|
967
|
+
);
|
|
932
968
|
|
|
933
969
|
config.mcpServers[id] = {
|
|
934
|
-
name
|
|
935
|
-
description
|
|
970
|
+
name,
|
|
971
|
+
description,
|
|
936
972
|
command: 'npx',
|
|
937
973
|
args: args.map(replaceProject),
|
|
938
|
-
env,
|
|
974
|
+
env: stdioEnv,
|
|
939
975
|
// 记下来给 UI 提示"这个 server 需要哪些环境变量",也便于卸载时回溯
|
|
940
|
-
requiredEnv:
|
|
941
|
-
package:
|
|
942
|
-
...(item.transport === 'remote' ? { transport: 'remote', remoteUrl: item.remoteUrl } : {}),
|
|
976
|
+
requiredEnv: missingKeys,
|
|
977
|
+
package: packageName,
|
|
943
978
|
};
|
|
944
979
|
|
|
945
980
|
await fs.mkdir(path.dirname(paths.file), { recursive: true });
|
|
946
981
|
await fs.writeFile(paths.file, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
947
982
|
|
|
948
|
-
return { id, type: 'mcp', name
|
|
983
|
+
return { id, type: 'mcp', name, target, file: paths.file, envKeys: missingKeys, warning: installWarning };
|
|
949
984
|
}
|
|
950
985
|
|
|
951
986
|
/**
|
|
@@ -249,24 +249,48 @@ test('install mcp: 非法包名被拒绝,且不写出配置文件', async () =>
|
|
|
249
249
|
if (!before) assert.equal(await exists(PROJECT_MCP), false);
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
-
test('install mcp:
|
|
252
|
+
test('install mcp: 只有远程端点时直接写 http 配置(url + headers),不再绕 mcp-remote', async () => {
|
|
253
253
|
const { call } = harness();
|
|
254
254
|
const { status } = await call('POST /api/agent/marketplace/install', {
|
|
255
255
|
body: {
|
|
256
256
|
type: 'mcp',
|
|
257
257
|
target: 'global',
|
|
258
258
|
cwd: sandboxProject,
|
|
259
|
-
item: {
|
|
259
|
+
item: {
|
|
260
|
+
id: 'remote-only',
|
|
261
|
+
name: 'Remote',
|
|
262
|
+
remoteUrl: 'https://example.com/mcp',
|
|
263
|
+
transport: 'remote',
|
|
264
|
+
env: { Authorization: 'Bearer t0ken' },
|
|
265
|
+
},
|
|
260
266
|
},
|
|
261
267
|
});
|
|
262
268
|
assert.equal(status, 200);
|
|
263
269
|
const config = JSON.parse(await fs.readFile(AI_MCP_FILE, 'utf8'));
|
|
264
270
|
const server = config.mcpServers['remote-only'];
|
|
265
|
-
assert.equal(server.
|
|
266
|
-
assert.
|
|
271
|
+
assert.equal(server.type, 'http');
|
|
272
|
+
assert.equal(server.url, 'https://example.com/mcp');
|
|
273
|
+
// 弹窗里填的键值对远程服务就是请求头(g ai 的 http 客户端只认 headers)
|
|
274
|
+
assert.equal(server.headers.Authorization, 'Bearer t0ken');
|
|
275
|
+
assert.equal(server.command, undefined, 'http 型不该再落 command,否则会被当成 stdio 去 spawn');
|
|
267
276
|
assert.equal(server.transport, 'remote');
|
|
268
277
|
});
|
|
269
278
|
|
|
279
|
+
test('install mcp: 远程端点不是 http(s) 时拒绝,不写出半成品配置', async () => {
|
|
280
|
+
const { call } = harness();
|
|
281
|
+
const before = await exists(AI_MCP_FILE);
|
|
282
|
+
const { status } = await call('POST /api/agent/marketplace/install', {
|
|
283
|
+
body: {
|
|
284
|
+
type: 'mcp',
|
|
285
|
+
target: 'global',
|
|
286
|
+
cwd: sandboxProject,
|
|
287
|
+
item: { id: 'bad-remote', name: 'Bad', remoteUrl: 'ftp://example.com/mcp', transport: 'remote' },
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
assert.equal(status, 422);
|
|
291
|
+
if (!before) assert.equal(await exists(AI_MCP_FILE), false);
|
|
292
|
+
});
|
|
293
|
+
|
|
270
294
|
test('install mcp: 既没有包也没有远程端点时拒绝', async () => {
|
|
271
295
|
const { call } = harness();
|
|
272
296
|
const { status } = await call('POST /api/agent/marketplace/install', {
|