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.
Files changed (70) hide show
  1. package/README.md +16 -2
  2. package/package.json +2 -1
  3. package/src/cli/ai/agent.js +1342 -1342
  4. package/src/cli/ai/context.js +431 -253
  5. package/src/cli/ai/context.test.js +376 -258
  6. package/src/cli/ai/mcp.js +323 -27
  7. package/src/cli/ai/mcp.test.js +202 -2
  8. package/src/cli/ai/runtime.test.js +7 -4
  9. package/src/cli/ai/turn.js +166 -166
  10. package/src/config.js +906 -871
  11. package/src/ui/public/assets/{AgentEngineSelector-D6rlSfMD.js → AgentEngineSelector-B829U5mr.js} +1 -1
  12. package/src/ui/public/assets/AgentView-r3_ClUmr.js +1 -0
  13. package/src/ui/public/assets/{AgentView-h2YnbB7J.css → AgentView-uHf-KMCH.css} +1 -1
  14. package/src/ui/public/assets/{AppVersionBadge-BnPsn1X5.js → AppVersionBadge-NtghusOI.js} +2 -2
  15. package/src/ui/public/assets/{BranchSelector-D30GJwUl.js → BranchSelector-D0claXAs.js} +1 -1
  16. package/src/ui/public/assets/{CommitForm-Dvg-RUZI.js → CommitForm-BIq6-sqk.js} +1 -1
  17. package/src/ui/public/assets/{CommonDialog-BzLau2RJ.js → CommonDialog-NqZURH2F.js} +1 -1
  18. package/src/ui/public/assets/EditorView-0T5LR5Hz.css +1 -0
  19. package/src/ui/public/assets/EditorView-Cny0lDhG.js +1 -0
  20. package/src/ui/public/assets/{FlowExecutionViewer-VL-rUElj.js → FlowExecutionViewer-DxDKzmaP.js} +1 -1
  21. package/src/ui/public/assets/{FlowOrchestrationWorkspace-BPNlHRVu.js → FlowOrchestrationWorkspace-B3CAJZ-U.js} +1 -1
  22. package/src/ui/public/assets/{LogList-COwGjQl6.js → LogList-D5sS4Il3.js} +1 -1
  23. package/src/ui/public/assets/{MindmapView-BVuqPD_H.js → MindmapView-6bc0EAJM.js} +1 -1
  24. package/src/ui/public/assets/{MonitorView-hqe-4xd0.js → MonitorView-BnUV4RaV.js} +1 -1
  25. package/src/ui/public/assets/{ProjectStartupButton-RBc-DJBR.js → ProjectStartupButton-Cbw079DB.js} +1 -1
  26. package/src/ui/public/assets/RecentDirectoriesChat-BCZsJR7n.js +1 -0
  27. package/src/ui/public/assets/RecentDirectoriesChat-THkqWEtO.css +1 -0
  28. package/src/ui/public/assets/{RemoteManagerDialog-D6Rbjchl.js → RemoteManagerDialog-BWWPEnSd.js} +1 -1
  29. package/src/ui/public/assets/{RemoteRepoCard-l0NpXgvB.js → RemoteRepoCard-BhA3-4PZ.js} +1 -1
  30. package/src/ui/public/assets/{SourceMapView-CeVystt0.js → SourceMapView-DHDmNzSh.js} +1 -1
  31. package/src/ui/public/assets/{SvgIcon-B-xDJQA1.js → SvgIcon-DcVasCdo.js} +1 -1
  32. package/src/ui/public/assets/{UserInputNode-DJglU68l.js → UserInputNode-B313MadX.js} +1 -1
  33. package/src/ui/public/assets/WorkbenchView-NCE_bk5a.js +20 -0
  34. package/src/ui/public/assets/WorkbenchView-rscs76HF.css +1 -0
  35. package/src/ui/public/assets/_plugin-vue_export-helper-Bsx6Zj3t.js +19 -0
  36. package/src/ui/public/assets/agentConversations-BAhGYmeY.js +8 -0
  37. package/src/ui/public/assets/{configStore-CMlW8sMa.js → configStore-DQSKV2_Y.js} +1 -1
  38. package/src/ui/public/assets/{dagre-DyI0XLXY.js → dagre-BmYeUlEa.js} +3 -3
  39. package/src/ui/public/assets/{element-plus-DrCk0pcV.js → element-plus-DTtm3XCd.js} +1 -1
  40. package/src/ui/public/assets/{flow-mindmap-Cx5RNYN9.js → flow-mindmap-JxOspgwx.js} +1 -1
  41. package/src/ui/public/assets/{index-u9LyUFkr.css → index-BgN6Z2X9.css} +1 -1
  42. package/src/ui/public/assets/{index-BQxIFopZ.js → index-DQuqmPuL.js} +9 -9
  43. package/src/ui/public/assets/{monaco-Dfcwm1aX.js → monaco-D3R9OM26.js} +1 -1
  44. package/src/ui/public/assets/{office-docx-C0unxEAm.js → office-docx-CO7mIaWO.js} +1 -1
  45. package/src/ui/public/assets/{office-excel-DPdyr_DJ.js → office-excel-gA4KEkK4.js} +1 -1
  46. package/src/ui/public/assets/{office-pptx-eojGAs9D.js → office-pptx-CQK4l_1Q.js} +1 -1
  47. package/src/ui/public/assets/{vendor-yntih_em.js → vendor-O17eRefg.js} +222 -222
  48. package/src/ui/public/assets/vendor-U3ZXMgeO.css +1 -0
  49. package/src/ui/public/assets/{vue-flow-De8mr_Fv.js → vue-flow-BG6ZF1It.js} +1 -1
  50. package/src/ui/public/index.html +13 -13
  51. package/src/ui/server/routes/config.js +1321 -1314
  52. package/src/ui/server/routes/workbench/agentChat.js +594 -571
  53. package/src/ui/server/routes/workbench/agentChatShared.test.js +302 -233
  54. package/src/ui/server/routes/workbench/agentMarketplace.js +65 -30
  55. package/src/ui/server/routes/workbench/agentMarketplace.test.js +28 -4
  56. package/src/ui/server/routes/workbench/agentRoutes.js +631 -631
  57. package/src/ui/server/routes/workbench/liveJobs.js +4 -0
  58. package/src/ui/server/routes/workbench/liveJobs.test.js +11 -0
  59. package/src/ui/server/routes/workbench/taskRunner.js +44 -7
  60. package/src/ui/server/routes/workbench/taskRunner.thinking.test.js +115 -0
  61. package/src/ui/public/assets/AgentView-BFRGoIVb.js +0 -1
  62. package/src/ui/public/assets/EditorView-8Rb4n-Wh.css +0 -1
  63. package/src/ui/public/assets/EditorView-D4ZQutRw.js +0 -1
  64. package/src/ui/public/assets/RecentDirectoriesChat-BxbpKrXc.css +0 -1
  65. package/src/ui/public/assets/RecentDirectoriesChat-DFljcYFH.js +0 -1
  66. package/src/ui/public/assets/WorkbenchView-BSQ87CDi.css +0 -1
  67. package/src/ui/public/assets/WorkbenchView-BpH5kNht.js +0 -20
  68. package/src/ui/public/assets/_plugin-vue_export-helper-Dz1ARW9a.js +0 -19
  69. package/src/ui/public/assets/agentConversations-B8nY45V4.js +0 -7
  70. 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
- command: server?.command || '',
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
- // 只有 remote 端点的 server:用 mcp-remote 桥接成 stdio,
882
- // 否则 g ai 的 stdio 客户端根本连不上。这是生态里的通行做法。
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 && !bridge) {
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', primary], { cwd, timeout: NPM_TIMEOUT_MS, windowsHide: true });
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 = isStdio
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
- // 值为空的键跳过 —— 留给用户以后手改配置文件,而不是写一个空值进去让 server 起不来。
922
- const env = {};
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: item.name || id,
935
- description: clampText(item.description || '', 400),
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: missingEnv,
941
- package: isStdio ? packageName : bridge,
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: item.name || id, target, file: paths.file, envKeys: missingEnv, warning: installWarning };
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: 只有远程端点时用 mcp-remote 桥接,而不是报错或装出一个无效配置', async () => {
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: { id: 'remote-only', name: 'Remote', remoteUrl: 'https://example.com/mcp', transport: 'remote' },
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.command, 'npx');
266
- assert.deepEqual(server.args, ['-y', 'mcp-remote', 'https://example.com/mcp']);
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', {