@roll-agent/core 0.8.0 → 0.10.0

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 (33) hide show
  1. package/dist/cli/commands/agent-add.js +1 -1
  2. package/dist/cli/commands/agent-install.js +1 -1
  3. package/dist/cli/commands/browser-clear-data.js +1 -1
  4. package/dist/cli/commands/browser-stop.d.ts +81 -0
  5. package/dist/cli/commands/browser-stop.js +1 -0
  6. package/dist/cli/commands/browser.js +1 -1
  7. package/dist/cli/commands/config-explain.d.ts +1 -0
  8. package/dist/cli/commands/config-explain.js +1 -0
  9. package/dist/cli/commands/config-guidance.d.ts +109 -0
  10. package/dist/cli/commands/config-guidance.js +1 -0
  11. package/dist/cli/commands/config-prompts.d.ts +37 -0
  12. package/dist/cli/commands/config-prompts.js +1 -0
  13. package/dist/cli/commands/config-setup.d.ts +3 -0
  14. package/dist/cli/commands/config-setup.js +1 -0
  15. package/dist/cli/commands/config.js +1 -1
  16. package/dist/cli/commands/update.js +1 -1
  17. package/dist/cli/utils/package-manager.d.ts +49 -0
  18. package/dist/cli/utils/package-manager.js +1 -1
  19. package/dist/cli/utils/update-checker.d.ts +6 -0
  20. package/dist/cli/utils/update-checker.js +1 -1
  21. package/dist/config/browser-inspection.d.ts +1 -1
  22. package/dist/config/browser-inspection.js +1 -1
  23. package/dist/config/defaults.d.ts +9 -0
  24. package/dist/config/defaults.js +1 -1
  25. package/dist/config/key-codec.js +1 -1
  26. package/dist/config/loader.d.ts +12 -0
  27. package/dist/config/loader.js +1 -1
  28. package/dist/config/runtime-env.js +1 -1
  29. package/dist/config/schema.d.ts +84 -0
  30. package/dist/config/schema.js +1 -1
  31. package/dist/registry/process-manager.js +1 -1
  32. package/dist/registry/runtime-setup.js +1 -1
  33. package/package.json +2 -1
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r,mkdirSync as i}from"node:fs";import{execFile as o}from"node:child_process";import{promisify as n}from"node:util";import{inspectAgentEnvRequirements as s}from"../../config/helpers.js";import{loadAgentsConfig as a}from"../../config/loader.js";import{discoverAgent as p}from"../../registry/discovery.js";import{writeRemoteSkillManifest as c}from"../../registry/manifest.js";import{AgentStore as d}from"../../registry/store.js";import{log as m}from"../utils/output.js";import{createInstallCommand as l,detectInstallCommand as g,formatPackageManagerCommand as f,formatPackageManagerError as u,runPackageManager as h}from"../utils/package-manager.js";const y=n(o);function $(e){return e.startsWith("https://")||e.startsWith("http://")||e.startsWith("git@")||e.endsWith(".git")}function v(e){return(e.split("/").pop()??e).replace(/\.git$/,"")}export default e({meta:{description:"注册本地目录、Git 仓库或远程 MCP endpoint"},args:{path:{type:"positional",description:"本地 Agent 目录或 Git 仓库 URL;远程 endpoint 请用 --remote",required:!1},remote:{type:"string",description:"远程 MCP endpoint;需要同时提供 --name 和 --description"},name:{type:"string",description:"远程 Agent 名称(仅 --remote 时使用)"},description:{type:"string",description:"远程 Agent 描述(仅 --remote 时使用)"}},async run({args:e}){const{agentsConfig:o}=a();let n;if(e.remote){if(!e.name||!e.description)return m.error("远程注册需要同时提供 --name 和 --description"),void(process.exitCode=1);n=c({dataDir:o.dataDir,name:e.name,description:e.description,endpoint:e.remote})}else{if(!e.path)return m.error("请提供本地路径、Git URL,或使用 --remote <endpoint> 注册远程 Agent"),void(process.exitCode=1);if($(e.path)){const s=v(e.path),a=t(o.dataDir,"repos",s);if(r(a)){m.info(`仓库目录已存在,拉取最新代码: ${a}`);try{await y("git",["pull"],{cwd:a})}catch(e){return m.error(`git pull 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}}else{m.info(`克隆 ${e.path}...`);const n=t(o.dataDir,"repos");r(n)||i(n,{recursive:!0});try{await y("git",["clone",e.path,a]),m.success("克隆完成")}catch(e){return m.error(`git clone 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}}n=a}else if(n=t(e.path),!r(n))return m.error(`路径不存在: ${n}`),void(process.exitCode=1)}m.info("解析 SKILL.md...");const s=p(n);m.debug(`名称: ${s.skill.name}`),m.debug(`描述: ${s.skill.description}`),m.debug(`传输: ${s.transport.type}`);const L=t(n,"package.json"),A="1"===process.env.ROLL_SKIP_INSTALL;if(e.remote)m.info("远程 Agent 使用本地 manifest,无需安装依赖。");else if(r(L)&&!A){const e=g(n)??l("pnpm");m.info(`安装依赖 (${f(e)})...`);try{await h(e,{cwd:n}),m.success("依赖安装完成")}catch(t){return m.error(`依赖安装失败: ${u(e,t)}`),void(process.exitCode=1)}}else r(L)&&A&&m.warn("检测到 ROLL_SKIP_INSTALL=1,跳过依赖安装。");const j=e.remote?{type:"remote-manifest",endpoint:e.remote}:e.path&&$(e.path)?{type:"git",url:e.path}:{type:"local-path",path:n},w=new d(o.dataDir),C={skill:s.skill,transport:s.transport,runtime:s.runtime,installPath:n,registeredAt:(new Date).toISOString(),status:"idle",source:j,...s.skillBody.length>0?{skillBody:s.skillBody}:{}};try{w.add(C),m.success(`Agent "${s.skill.name}" 注册成功`),k(s.skill.name,s.skill.env,o.env)}catch(e){m.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function k(e,t,r){const i=s(e,t,r);if(i)return i.missingRequired.length>0?(m.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),void m.info(`请在 roll.config.yaml agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(m.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),m.info(`建议将这些项写入 roll.config.yaml agents.env.${e}。`)))}
1
+ import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r,mkdirSync as i}from"node:fs";import{execFile as o}from"node:child_process";import{promisify as n}from"node:util";import{inspectAgentEnvRequirements as s}from"../../config/helpers.js";import{loadAgentsConfig as a}from"../../config/loader.js";import{discoverAgent as p}from"../../registry/discovery.js";import{writeRemoteSkillManifest as c}from"../../registry/manifest.js";import{AgentStore as d}from"../../registry/store.js";import{log as l}from"../utils/output.js";import{createInstallCommand as m,detectInstallCommand as g,formatPackageManagerCommand as f,formatPackageManagerError as u,runPackageManager as h}from"../utils/package-manager.js";const y=n(o);function $(e){return e.startsWith("https://")||e.startsWith("http://")||e.startsWith("git@")||e.endsWith(".git")}function v(e){return(e.split("/").pop()??e).replace(/\.git$/,"")}export default e({meta:{description:"注册本地目录、Git 仓库或远程 MCP endpoint"},args:{path:{type:"positional",description:"本地 Agent 目录或 Git 仓库 URL;远程 endpoint 请用 --remote",required:!1},remote:{type:"string",description:"远程 MCP endpoint;需要同时提供 --name 和 --description"},name:{type:"string",description:"远程 Agent 名称(仅 --remote 时使用)"},description:{type:"string",description:"远程 Agent 描述(仅 --remote 时使用)"}},async run({args:e}){const{agentsConfig:o}=a();let n;if(e.remote){if(!e.name||!e.description)return l.error("远程注册需要同时提供 --name 和 --description"),void(process.exitCode=1);n=c({dataDir:o.dataDir,name:e.name,description:e.description,endpoint:e.remote})}else{if(!e.path)return l.error("请提供本地路径、Git URL,或使用 --remote <endpoint> 注册远程 Agent"),void(process.exitCode=1);if($(e.path)){const s=v(e.path),a=t(o.dataDir,"repos",s);if(r(a)){l.info(`仓库目录已存在,拉取最新代码: ${a}`);try{await y("git",["pull"],{cwd:a})}catch(e){return l.error(`git pull 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}}else{l.info(`克隆 ${e.path}...`);const n=t(o.dataDir,"repos");r(n)||i(n,{recursive:!0});try{await y("git",["clone",e.path,a]),l.success("克隆完成")}catch(e){return l.error(`git clone 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}}n=a}else if(n=t(e.path),!r(n))return l.error(`路径不存在: ${n}`),void(process.exitCode=1)}l.info("解析 SKILL.md...");const s=p(n);l.debug(`名称: ${s.skill.name}`),l.debug(`描述: ${s.skill.description}`),l.debug(`传输: ${s.transport.type}`);const L=t(n,"package.json"),A="1"===process.env.ROLL_SKIP_INSTALL;if(e.remote)l.info("远程 Agent 使用本地 manifest,无需安装依赖。");else if(r(L)&&!A){const e=g(n)??m("pnpm");l.info(`安装依赖 (${f(e)})...`);try{await h(e,{cwd:n}),l.success("依赖安装完成")}catch(t){return l.error(`依赖安装失败: ${u(e,t)}`),void(process.exitCode=1)}}else r(L)&&A&&l.warn("检测到 ROLL_SKIP_INSTALL=1,跳过依赖安装。");const j=e.remote?{type:"remote-manifest",endpoint:e.remote}:e.path&&$(e.path)?{type:"git",url:e.path}:{type:"local-path",path:n},w=new d(o.dataDir),C={skill:s.skill,transport:s.transport,runtime:s.runtime,installPath:n,registeredAt:(new Date).toISOString(),status:"idle",source:j,...s.skillBody.length>0?{skillBody:s.skillBody}:{}};try{w.add(C),l.success(`Agent "${s.skill.name}" 注册成功`),k(s.skill.name,s.skill.env,o.env)}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function k(e,t,r){const i=s(e,t,r);if(i)return i.missingRequired.length>0?(l.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),l.info(`运行 \`roll config setup agent ${e}\` 交互式配置。`),void l.info(`运行 \`roll config explain agents.env.${e}\` 查看配置说明。`)):void(i.processEnvOnlyRequired.length>0&&(l.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),l.info(`建议运行 \`roll config setup agent ${e}\` 持久写入 roll.config.yaml。`)))}
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as r,statSync as i}from"node:fs";import{resolve as s}from"node:path";import{getAgentEnv as n,inspectAgentEnvRequirements as o}from"../../config/helpers.js";import{loadAgentsConfig as a,loadConfig as l}from"../../config/loader.js";import{discoverAgent as m}from"../../registry/discovery.js";import{startAgent as p,stopAgentGracefully as c,waitForAgentReady as d}from"../../registry/process-manager.js";import{runAgentSetup as g}from"../../registry/runtime-setup.js";import{AgentStore as u}from"../../registry/store.js";import{parsePackageName as f,readInstalledPackageManifest as k,resolveInstalledPackageRoot as y,sanitizeInstallId as h}from"../../registry/source.js";import{log as w}from"../utils/output.js";import{formatPackageManagerError as v,runPackageManager as $}from"../utils/package-manager.js";function j(e){return e.startsWith("git@")||e.startsWith("git+")||e.startsWith("github:")||e.startsWith("gitlab:")||e.startsWith("bitbucket:")||e.endsWith(".git")}export default e({meta:{description:"安装已发布的 Agent npm 包并注册到本地"},args:{package:{type:"positional",description:"npm 包名、版本范围或 .tgz 路径(源码目录请用 roll agent add)",required:!0},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1},start:{type:"boolean",description:"安装后不自动启动 core-managed Agent",default:!0}},async run({args:e}){const{agentsConfig:o}=a(),S=e.package;if(j(S))return w.error(`Git URL 请使用 \`roll agent add ${S}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const C=s(S);if(t(C)&&i(C).isDirectory())return w.error(`本地源码目录请使用 \`roll agent add ${S}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const x=f(S),A=s(o.dataDir,"installed",h(x));t(A)||r(A,{recursive:!0}),w.info(`安装 ${S}...`);const D={command:"npm",args:["install","--prefix",A,S]};try{await $(D,{timeout:12e4})}catch(e){return w.error(`安装失败: ${v(D,e)}`),void(process.exitCode=1)}const W=y(A,x);if(!t(W))return w.error(`安装完成但未找到包目录: ${W}`),void(process.exitCode=1);const q=k(W);w.info("解析已安装 Agent 的 SKILL.md...");const B=m(W),R=new u(o.dataDir),E={skill:B.skill,transport:B.transport,runtime:B.runtime,installPath:W,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:q?.name??x,packageSpec:S,installDir:A,...q?.version?{installedVersion:q.version}:{}},...B.skillBody.length>0?{skillBody:B.skillBody}:{}};"core-managed"===E.runtime.ownership&&E.runtime.setup?.playwright&&!e["skip-browser-setup"]&&w.info(`即将安装浏览器运行时 (${E.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const L=await g(E,{skipBrowserSetup:e["skip-browser-setup"]});L.ok?L.skipped?w.info(L.message):w.success(L.message):(w.warn(`Agent setup 失败:${L.message}`),L.retryCommand&&w.info(`重试命令: ${L.retryCommand}`));const O=R.findByName(B.skill.name);try{const t="core-managed"===O?.runtime.ownership&&"online"===O.status;if("installed-package"===O?.source?.type?R.replace(O.skill.name,E):R.add(E),!L.ok)return R.updateStatus(B.skill.name,"error"),void(process.exitCode=1);if("core-managed"===E.runtime.ownership&&e.start){t&&await c(o.dataDir,E.skill.name),R.updateStatus(E.skill.name,"starting");let e=!1;try{p(E,o.dataDir,n(l().config,E.skill.name)),e=!0,await d(E,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),R.updateStatus(E.skill.name,"online")}catch(t){return e&&await c(o.dataDir,E.skill.name).catch(()=>{}),R.updateStatus(E.skill.name,"error"),w.error(`Agent "${B.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}w.success(`Agent "${B.skill.name}" 安装并注册成功`),b(B.skill.name,B.skill.env,o.env)}catch(e){w.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function b(e,t,r){const i=o(e,t,r);if(i)return i.missingRequired.length>0?(w.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),void w.info(`请在 roll.config.yaml agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(w.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),w.info(`建议将这些项写入 roll.config.yaml agents.env.${e}。`)))}
1
+ import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as r,statSync as i}from"node:fs";import{resolve as s}from"node:path";import{getAgentEnv as n,inspectAgentEnvRequirements as o}from"../../config/helpers.js";import{loadAgentsConfig as a,loadConfig as l,loadInstallConfig as p}from"../../config/loader.js";import{discoverAgent as m}from"../../registry/discovery.js";import{startAgent as c,stopAgentGracefully as g,waitForAgentReady as d}from"../../registry/process-manager.js";import{runAgentSetup as u}from"../../registry/runtime-setup.js";import{AgentStore as f}from"../../registry/store.js";import{parsePackageName as y,readInstalledPackageManifest as k,resolveInstalledPackageRoot as h,sanitizeInstallId as w}from"../../registry/source.js";import{log as $}from"../utils/output.js";import{buildNpmRetryPolicy as v,formatPackageManagerError as j,npmInstallNetworkArgs as C,runPackageManagerWithRetry as S}from"../utils/package-manager.js";function b(e){return C({...e.registry?{registry:e.registry}:{},fetchRetries:e.fetchRetries,preferOffline:e.preferOffline})}function x(e){return e.startsWith("git@")||e.startsWith("git+")||e.startsWith("github:")||e.startsWith("gitlab:")||e.startsWith("bitbucket:")||e.endsWith(".git")}export default e({meta:{description:"安装已发布的 Agent npm 包并注册到本地"},args:{package:{type:"positional",description:"npm 包名、版本范围或 .tgz 路径(源码目录请用 roll agent add)",required:!0},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1},start:{type:"boolean",description:"安装后不自动启动 core-managed Agent",default:!0}},async run({args:e}){const{agentsConfig:o}=a();let C;try{C=p().installConfig}catch(e){return $.error(`install 配置无效,已停止安装:${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}const R=e.package;if(x(R))return $.error(`Git URL 请使用 \`roll agent add ${R}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const D=s(R);if(t(D)&&i(D).isDirectory())return $.error(`本地源码目录请使用 \`roll agent add ${R}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const W=y(R),q=s(o.dataDir,"installed",w(W));t(q)||r(q,{recursive:!0}),C.registry&&$.info(`使用 npm registry: ${C.registry}(roll.config.yaml install.registry)`),$.info(`安装 ${R}...`);const B={command:"npm",args:["install","--prefix",q,R,...b(C)]};try{await S(B,{timeout:C.networkTimeoutMs},{...v(C.fetchRetries),onRetry:({attempt:e,delayMs:t})=>{$.warn(`安装遇到网络问题,${Math.round(t/1e3)}s 后重试(第 ${e+1} 次)...`)}})}catch(e){return $.error(`安装失败: ${j(B,e)}`),void(process.exitCode=1)}const E=h(q,W);if(!t(E))return $.error(`安装完成但未找到包目录: ${E}`),void(process.exitCode=1);const M=k(E);$.info("解析已安装 Agent 的 SKILL.md...");const O=m(E),L=new f(o.dataDir),T={skill:O.skill,transport:O.transport,runtime:O.runtime,installPath:E,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:M?.name??W,packageSpec:R,installDir:q,...M?.version?{installedVersion:M.version}:{}},...O.skillBody.length>0?{skillBody:O.skillBody}:{}};"core-managed"===T.runtime.ownership&&T.runtime.setup?.playwright&&!e["skip-browser-setup"]&&$.info(`即将安装浏览器运行时 (${T.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const I=await u(T,{skipBrowserSetup:e["skip-browser-setup"]});I.ok?I.skipped?$.info(I.message):$.success(I.message):($.warn(`Agent setup 失败:${I.message}`),I.retryCommand&&$.info(`重试命令: ${I.retryCommand}`));const N=L.findByName(O.skill.name);try{const t="core-managed"===N?.runtime.ownership&&"online"===N.status;if("installed-package"===N?.source?.type?L.replace(N.skill.name,T):L.add(T),!I.ok)return L.updateStatus(O.skill.name,"error"),void(process.exitCode=1);if("core-managed"===T.runtime.ownership&&e.start){t&&await g(o.dataDir,T.skill.name),L.updateStatus(T.skill.name,"starting");let e=!1;try{c(T,o.dataDir,n(l().config,T.skill.name)),e=!0,await d(T,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),L.updateStatus(T.skill.name,"online")}catch(t){return e&&await g(o.dataDir,T.skill.name).catch(()=>{}),L.updateStatus(T.skill.name,"error"),$.error(`Agent "${O.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}$.success(`Agent "${O.skill.name}" 安装并注册成功`),A(O.skill.name,O.skill.env,o.env)}catch(e){$.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function A(e,t,r){const i=o(e,t,r);if(i)return i.missingRequired.length>0?($.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),$.info(`运行 \`roll config setup agent ${e}\` 交互式配置。`),void $.info(`运行 \`roll config explain agents.env.${e}\` 查看配置说明。`)):void(i.processEnvOnlyRequired.length>0&&($.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),$.info(`建议运行 \`roll config setup agent ${e}\` 持久写入 roll.config.yaml。`)))}
@@ -1 +1 @@
1
- import{defineCommand as s}from"citty";import{existsSync as e,lstatSync as r,rmSync as o}from"node:fs";import{homedir as t}from"node:os";import{isAbsolute as n,join as i,parse as a,relative as l,resolve as c}from"node:path";import{loadConfig as f}from"../../config/loader.js";import{getAgentPid as u}from"../../registry/process-manager.js";import{log as d}from"../utils/output.js";const p="browser-use-agent",g=i(t(),".roll-agent","browser","sessions"),w=["profiles","sessions"];export default s({meta:{description:"清理 browser.instances 声明的 Chrome profile / Roll session 数据"},args:{instance:{type:"positional",description:"可选 browserInstance;不传则处理所有已声明实例",required:!1},profiles:{type:"boolean",description:"只清理 Chrome profile 目录(browser.instances[*].user-data-dir)",default:!1},sessions:{type:"boolean",description:"只清理 Roll session 快照目录(browser.instances[*].sessions-dir)",default:!1},all:{type:"boolean",description:"清理 profile + sessions;未指定 --profiles/--sessions 时默认就是 all",default:!1},yes:{type:"boolean",alias:"y",description:"确认执行删除;不加时只输出 dry-run 计划",default:!1},force:{type:"boolean",description:"browser-use-agent 仍在运行时也继续删除(不建议)",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){try{const{config:e}=f(),r=$(s.instance,"instance"),o=resolveBrowserDataKinds({all:!0===s.all,profiles:!0===s.profiles,sessions:!0===s.sessions}),t=buildBrowserDataClearPlan(e.browser,{...void 0!==r?{instanceId:r}:{},kinds:o});if(!0===s.yes){const r=u(e.agents.dataDir,p);if(void 0!==r&&!0!==s.force){const e=`${p} 仍在运行 (PID: ${String(r)}),请先运行 \`roll agent stop browser-use-agent\`,或确认风险后加 --force`;return s.json?console.log(JSON.stringify({ok:!1,message:e,targets:t},null,2)):d.error(e),void(process.exitCode=1)}const o=clearBrowserDataTargets(t,{protectedSubtreeRoots:[e.agents.dataDir]});return s.json?console.log(JSON.stringify({ok:o.every(s=>"failed"!==s.status),results:o},null,2)):v(o),void(o.some(s=>"failed"===s.status)&&(process.exitCode=1))}s.json?console.log(JSON.stringify({dryRun:!0,targets:t},null,2)):D(t)}catch(e){const r=e instanceof Error?e.message:String(e);s.json?console.log(JSON.stringify({ok:!1,message:r},null,2)):d.error(r),process.exitCode=1}}});export function resolveBrowserDataKinds(s){return s.all||!s.profiles&&!s.sessions?w:[...s.profiles?["profiles"]:[],...s.sessions?["sessions"]:[]]}export function buildBrowserDataClearPlan(s,e){if(0===Object.entries(s.instances).length)return[];return b(s,e.instanceId).flatMap(([s,r])=>{const o=[];return e.kinds.includes("profiles")&&o.push(y(s,"profiles",r.userDataDir)),e.kinds.includes("sessions")&&o.push(y(s,"sessions",r.sessionsDir??i(g,s))),o})}export function clearBrowserDataTargets(s,t={}){const n=new Set;return s.map(s=>{try{if(assertSafeBrowserDataPath(s.path,t),n.has(s.path))return{...s,status:"skipped",message:"同一路径已在本次命令中处理"};if(n.add(s.path),!e(s.path))return{...s,status:"missing"};const i=r(s.path);if(!i.isDirectory()&&!i.isSymbolicLink())throw new Error(`拒绝删除非目录路径: ${s.path}`);return o(s.path,{recursive:!0,force:!0}),{...s,exists:!1,status:"deleted"}}catch(e){return{...s,status:"failed",message:e instanceof Error?e.message:String(e)}}})}export function assertSafeBrowserDataPath(s,e={}){const r=c(s),o=a(r).root,n=c(t(),".roll-agent"),i=c(n,"browser"),l=[o,c(t()),c(process.cwd()),n,i,c(i,"profiles"),c(i,"sessions")];for(const s of l)if(m(r,s))throw new Error(`拒绝删除危险路径: ${r}`);for(const s of e.protectedSubtreeRoots??[])if(h(r,c(s)))throw new Error(`拒绝删除受保护数据目录下的路径: ${r}`)}function m(s,e){return h(e,s)}function h(s,e){const r=l(e,s);return""===r||!r.startsWith("..")&&!n(r)}function b(s,e){if(void 0===e)return Object.entries(s.instances);const r=s.instances[e];if(void 0===r){const r=Object.keys(s.instances);throw new Error(`browserInstance "${e}" 未声明`+(r.length>0?`;可用实例: ${r.join(", ")}`:""))}return[[e,r]]}function y(s,r,o){const t=c(o);return{instanceId:s,kind:r,path:t,exists:e(t)}}function $(s,e){if(void 0===s)return;if("string"!=typeof s)throw new Error(`${e} 必须是字符串`);const r=s.trim();return r.length>0?r:void 0}function D(s){if(0!==s.length){d.info("Browser data clear plan (dry-run, 未删除任何文件):");for(const e of s)d.info(S(e));d.info("确认无误后追加 --yes 执行删除;运行中的 browser-use-agent 建议先 stop。")}else d.warn("未配置 browser.instances,没有可清理的 browser 数据目录。")}function v(s){if(0===s.length)return void d.warn("未配置 browser.instances,没有可清理的 browser 数据目录。");for(const e of s){const s=`${S(e)} -> ${e.status}${void 0!==e.message?` (${e.message})`:""}`;"failed"===e.status?d.error(s):d.info(s)}0===s.filter(s=>"failed"===s.status).length&&d.success("Browser 数据清理完成。")}function S(s){return`${s.instanceId} ${s.kind}: ${s.path} [${s.exists?"exists":"missing"}]`}
1
+ import{defineCommand as s}from"citty";import{existsSync as e,lstatSync as r,rmSync as o}from"node:fs";import{homedir as t}from"node:os";import{isAbsolute as n,join as i,parse as a,relative as l,resolve as c}from"node:path";import{loadConfig as f}from"../../config/loader.js";import{getAgentPid as u}from"../../registry/process-manager.js";import{log as d}from"../utils/output.js";const p="browser-use-agent",g=i(t(),".roll-agent","browser","sessions"),w=["profiles","sessions"];export default s({meta:{description:"清理 browser.instances 声明的 Chrome profile / Roll session 数据"},args:{instance:{type:"positional",description:"可选 browserInstance;不传则处理所有已声明实例",required:!1},profiles:{type:"boolean",description:"只清理 Chrome profile 目录(browser.instances[*].user-data-dir)",default:!1},sessions:{type:"boolean",description:"只清理 Roll session 快照目录(browser.instances[*].sessions-dir)",default:!1},all:{type:"boolean",description:"清理 profile + sessions;未指定 --profiles/--sessions 时默认就是 all",default:!1},yes:{type:"boolean",alias:"y",description:"确认执行删除;不加时只输出 dry-run 计划",default:!1},force:{type:"boolean",description:"browser-use-agent 仍在运行时也继续删除(不建议)",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){try{const{config:e}=f(),r=$(s.instance,"instance"),o=resolveBrowserDataKinds({all:!0===s.all,profiles:!0===s.profiles,sessions:!0===s.sessions}),t=buildBrowserDataClearPlan(e.browser,{...void 0!==r?{instanceId:r}:{},kinds:o});if(!0===s.yes){const o=u(e.agents.dataDir,p);if(void 0!==o&&!0!==s.force){const e=void 0!==r?`roll browser stop ${r}`:"roll browser stop --all",n=`${p} 仍在运行 (PID: ${String(o)}),请先运行 \`${e}\`,或确认风险后加 --force`;return s.json?console.log(JSON.stringify({ok:!1,message:n,targets:t},null,2)):d.error(n),void(process.exitCode=1)}const n=clearBrowserDataTargets(t,{protectedSubtreeRoots:[e.agents.dataDir]});return s.json?console.log(JSON.stringify({ok:n.every(s=>"failed"!==s.status),results:n},null,2)):D(n),void(n.some(s=>"failed"===s.status)&&(process.exitCode=1))}s.json?console.log(JSON.stringify({dryRun:!0,targets:t},null,2)):v(t)}catch(e){const r=e instanceof Error?e.message:String(e);s.json?console.log(JSON.stringify({ok:!1,message:r},null,2)):d.error(r),process.exitCode=1}}});export function resolveBrowserDataKinds(s){return s.all||!s.profiles&&!s.sessions?w:[...s.profiles?["profiles"]:[],...s.sessions?["sessions"]:[]]}export function buildBrowserDataClearPlan(s,e){if(0===Object.entries(s.instances).length)return[];return h(s,e.instanceId).flatMap(([s,r])=>{const o=[];return e.kinds.includes("profiles")&&o.push(y(s,"profiles",r.userDataDir)),e.kinds.includes("sessions")&&o.push(y(s,"sessions",r.sessionsDir??i(g,s))),o})}export function clearBrowserDataTargets(s,t={}){const n=new Set;return s.map(s=>{try{if(assertSafeBrowserDataPath(s.path,t),n.has(s.path))return{...s,status:"skipped",message:"同一路径已在本次命令中处理"};if(n.add(s.path),!e(s.path))return{...s,status:"missing"};const i=r(s.path);if(!i.isDirectory()&&!i.isSymbolicLink())throw new Error(`拒绝删除非目录路径: ${s.path}`);return o(s.path,{recursive:!0,force:!0}),{...s,exists:!1,status:"deleted"}}catch(e){return{...s,status:"failed",message:e instanceof Error?e.message:String(e)}}})}export function assertSafeBrowserDataPath(s,e={}){const r=c(s),o=a(r).root,n=c(t(),".roll-agent"),i=c(n,"browser"),l=[o,c(t()),c(process.cwd()),n,i,c(i,"profiles"),c(i,"sessions")];for(const s of l)if(m(r,s))throw new Error(`拒绝删除危险路径: ${r}`);for(const s of e.protectedSubtreeRoots??[])if(b(r,c(s)))throw new Error(`拒绝删除受保护数据目录下的路径: ${r}`)}function m(s,e){return b(e,s)}function b(s,e){const r=l(e,s);return""===r||!r.startsWith("..")&&!n(r)}function h(s,e){if(void 0===e)return Object.entries(s.instances);const r=s.instances[e];if(void 0===r){const r=Object.keys(s.instances);throw new Error(`browserInstance "${e}" 未声明`+(r.length>0?`;可用实例: ${r.join(", ")}`:""))}return[[e,r]]}function y(s,r,o){const t=c(o);return{instanceId:s,kind:r,path:t,exists:e(t)}}function $(s,e){if(void 0===s)return;if("string"!=typeof s)throw new Error(`${e} 必须是字符串`);const r=s.trim();return r.length>0?r:void 0}function v(s){if(0!==s.length){d.info("Browser data clear plan (dry-run, 未删除任何文件):");for(const e of s)d.info(S(e));d.info("确认无误后追加 --yes 执行删除;运行中的 browser-use-agent 建议先 stop。")}else d.warn("未配置 browser.instances,没有可清理的 browser 数据目录。")}function D(s){if(0===s.length)return void d.warn("未配置 browser.instances,没有可清理的 browser 数据目录。");for(const e of s){const s=`${S(e)} -> ${e.status}${void 0!==e.message?` (${e.message})`:""}`;"failed"===e.status?d.error(s):d.info(s)}0===s.filter(s=>"failed"===s.status).length&&d.success("Browser 数据清理完成。")}function S(s){return`${s.instanceId} ${s.kind}: ${s.path} [${s.exists?"exists":"missing"}]`}
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+ import type { BrowserConfig } from "../../config/schema.ts";
3
+ export declare const BROWSER_STOP_COMMAND_DESCRIPTION = "\u5173\u95ED browser-use-agent \u5F53\u524D\u6258\u7BA1\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B\uFF1B\u4E0D\u4F1A\u505C\u6B62 browser-use-agent \u670D\u52A1\u8FDB\u7A0B\u3002";
4
+ export declare const BROWSER_STOP_INSTANCE_DESCRIPTION = "\u4E00\u4E2A\u6216\u591A\u4E2A browserInstance\uFF1B\u4E0D\u4F20\u65F6\u5FC5\u987B\u663E\u5F0F\u4F7F\u7528 --all\u3002";
5
+ export declare const BROWSER_STOP_ALL_DESCRIPTION = "\u5173\u95ED\u6240\u6709\u5DF2\u542F\u52A8\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B\uFF0C\u4F46\u4FDD\u7559 browser-use-agent \u8FDB\u7A0B\u7EE7\u7EED\u8FD0\u884C\uFF1B\u4E0D\u540C\u4E8E roll agent stop browser-use-agent\u3002";
6
+ export declare const BROWSER_USE_AGENT_NOT_RUNNING_MESSAGE = "browser-use-agent \u672A\u8FD0\u884C\uFF1B\u6CA1\u6709\u7531\u5F53\u524D agent \u6258\u7BA1\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B\u53EF\u5173\u95ED\u3002\u4E0D\u4F1A\u626B\u63CF\u6216\u67E5\u6740\u7CFB\u7EDF Chrome \u8FDB\u7A0B\u3002";
7
+ declare const BrowserStopToolResultSchema: z.ZodObject<{
8
+ ok: z.ZodBoolean;
9
+ stopped: z.ZodNumber;
10
+ results: z.ZodArray<z.ZodObject<{
11
+ browserInstance: z.ZodString;
12
+ status: z.ZodEnum<["stopped", "not_running", "not_found", "failed"]>;
13
+ mode: z.ZodOptional<z.ZodEnum<["managed-cdp", "remote-cdp", "existing-session"]>>;
14
+ message: z.ZodOptional<z.ZodString>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ status: "stopped" | "failed" | "not_running" | "not_found";
17
+ browserInstance: string;
18
+ message?: string | undefined;
19
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
20
+ }, {
21
+ status: "stopped" | "failed" | "not_running" | "not_found";
22
+ browserInstance: string;
23
+ message?: string | undefined;
24
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
25
+ }>, "many">;
26
+ }, "strip", z.ZodTypeAny, {
27
+ stopped: number;
28
+ ok: boolean;
29
+ results: {
30
+ status: "stopped" | "failed" | "not_running" | "not_found";
31
+ browserInstance: string;
32
+ message?: string | undefined;
33
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
34
+ }[];
35
+ }, {
36
+ stopped: number;
37
+ ok: boolean;
38
+ results: {
39
+ status: "stopped" | "failed" | "not_running" | "not_found";
40
+ browserInstance: string;
41
+ message?: string | undefined;
42
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
43
+ }[];
44
+ }>;
45
+ type BrowserStopToolResult = z.infer<typeof BrowserStopToolResultSchema>;
46
+ export interface BrowserStopRequest {
47
+ readonly all: boolean;
48
+ readonly instances: readonly string[];
49
+ readonly toolInput: Readonly<Record<string, unknown>>;
50
+ }
51
+ export interface BrowserStopJsonOutput extends BrowserStopToolResult {
52
+ readonly agentRunning: boolean;
53
+ readonly message?: string;
54
+ }
55
+ declare const _default: import("citty").CommandDef<{
56
+ instance: {
57
+ type: "positional";
58
+ description: string;
59
+ required: false;
60
+ };
61
+ all: {
62
+ type: "boolean";
63
+ description: string;
64
+ default: false;
65
+ };
66
+ json: {
67
+ type: "boolean";
68
+ description: string;
69
+ default: false;
70
+ };
71
+ }>;
72
+ export default _default;
73
+ export declare function resolveBrowserStopRequest(input: {
74
+ readonly rawArgs: readonly string[];
75
+ readonly all: boolean;
76
+ }): BrowserStopRequest;
77
+ export declare function parseBrowserStopInstances(rawArgs: readonly string[]): readonly string[];
78
+ export declare function parseBrowserStopToolResult(result: unknown): BrowserStopToolResult;
79
+ export declare function validateDeclaredBrowserStopInstances(browserConfig: BrowserConfig, request: BrowserStopRequest): void;
80
+ export declare function createBrowserStopAgentNotRunningOutput(): BrowserStopJsonOutput;
81
+ export declare function createBrowserStopTextLines(output: BrowserStopJsonOutput, request: BrowserStopRequest): readonly string[];
@@ -0,0 +1 @@
1
+ import{defineCommand as e}from"citty";import{z as t}from"zod";import{getAgentEnv as n}from"../../config/helpers.js";import{loadConfig as r}from"../../config/loader.js";import{McpClientManager as s}from"../../mcp/client-manager.js";import{getAgentPid as o}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{AgentStore as i}from"../../registry/store.js";import{formatMissingToolMessage as c,normalizeListedTools as l}from"../utils/agent-tools.js";import{extractTextContent as u,formatToolResultForJsonOutput as p,isToolErrorResult as g}from"../utils/tool-results.js";import{log as w}from"../utils/output.js";const f="browser-use-agent",m="browser_stop",d=new Set(["config"]);export const BROWSER_STOP_COMMAND_DESCRIPTION="关闭 browser-use-agent 当前托管的浏览器实例;不会停止 browser-use-agent 服务进程。";export const BROWSER_STOP_INSTANCE_DESCRIPTION="一个或多个 browserInstance;不传时必须显式使用 --all。";export const BROWSER_STOP_ALL_DESCRIPTION="关闭所有已启动的浏览器实例,但保留 browser-use-agent 进程继续运行;不同于 roll agent stop browser-use-agent。";export const BROWSER_USE_AGENT_NOT_RUNNING_MESSAGE="browser-use-agent 未运行;没有由当前 agent 托管的浏览器实例可关闭。不会扫描或查杀系统 Chrome 进程。";const S=t.enum(["stopped","not_running","not_found","failed"]),b=t.object({browserInstance:t.string(),status:S,mode:t.enum(["managed-cdp","remote-cdp","existing-session"]).optional(),message:t.string().optional()}),_=t.object({ok:t.boolean(),stopped:t.number().int().nonnegative(),results:t.array(b)});export default e({meta:{description:BROWSER_STOP_COMMAND_DESCRIPTION},args:{instance:{type:"positional",description:BROWSER_STOP_INSTANCE_DESCRIPTION,required:!1},all:{type:"boolean",description:BROWSER_STOP_ALL_DESCRIPTION,default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:e,rawArgs:t}){const o=!0===e.json,p=new s;try{const s=resolveBrowserStopRequest({rawArgs:t,all:!0===e.all}),{config:w}=r();validateDeclaredBrowserStopInstances(w.browser,s);const d=new i(w.agents.dataDir).findByName(f);if(void 0===d)throw new Error(`Agent "${f}" 未注册。使用 \`roll agent list\` 查看已注册 Agent。`);if(I(d,w.agents.dataDir)){return void E(createBrowserStopAgentNotRunningOutput(),s,o)}if("on-demand"===d.runtime.ownership)throw new Error(`${f} 不是常驻服务,无法执行实例级 browser runtime stop。`);const S=n(w,d.skill.name),b=a(d);let _;try{_=await p.connect(d.skill.name,b,d.installPath,{timeoutMs:5e3,...S?{env:S}:{}})}catch(e){if("external-managed"===d.runtime.ownership){return void E(createBrowserStopAgentNotRunningOutput(),s,o)}throw new Error(`${f} 正在运行但无法连接。`,{cause:e})}const R=l((await _.listTools()).tools);if(void 0===R.find(e=>e.name===m))throw new Error(c(d.skill.name,m,R));const N=await _.callTool({name:m,arguments:s.toolInput});if(g(N))throw new Error(u(N.content).join("\n")||"browser_stop 返回 isError=true");const O=parseBrowserStopToolResult(N);E({...O,agentRunning:!0},s,o),h(O.results)&&(process.exitCode=1)}catch(e){const t=e instanceof Error?e.message:String(e);o?console.log(JSON.stringify({ok:!1,message:t},null,2)):w.error(t),process.exitCode=1}finally{await p.disconnectAll()}}});export function resolveBrowserStopRequest(e){const t=parseBrowserStopInstances(e.rawArgs);if(e.all&&t.length>0)throw new Error("--all 不能和 browserInstance 位置参数同时使用。");if(!e.all&&0===t.length)throw new Error("请提供一个或多个 browserInstance,或显式使用 --all。");return e.all?{all:!0,instances:[],toolInput:{all:!0}}:{all:!1,instances:t,toolInput:1===t.length?{browserInstance:t[0]}:{browserInstances:t}}}export function parseBrowserStopInstances(e){const t=[];let n=0;for(;n<e.length;){const r=e[n];if(void 0===r){n+=1;continue}if("--"===r){t.push(...e.slice(n+1).map(e=>e.trim()).filter(Boolean));break}if(r.startsWith("--")){const e=r.slice(2).split("=",1)[0]??"";n+=d.has(e)&&!r.includes("=")?2:1;continue}if(r.startsWith("-")){n+=1;continue}const s=r.trim();s.length>0&&t.push(s),n+=1}return[...new Set(t)]}export function parseBrowserStopToolResult(e){return _.parse(p(e))}export function validateDeclaredBrowserStopInstances(e,t){if(t.all||0===t.instances.length)return;const n=Object.keys(e.instances);if(0===n.length)return;const r=t.instances.filter(t=>void 0===e.instances[t]);if(0!==r.length)throw new Error(`browserInstance ${r.map(e=>`"${e}"`).join(", ")} 未声明;可用实例: ${n.join(", ")}`)}export function createBrowserStopAgentNotRunningOutput(){return{ok:!0,agentRunning:!1,stopped:0,results:[],message:BROWSER_USE_AGENT_NOT_RUNNING_MESSAGE}}export function createBrowserStopTextLines(e,t){if(!e.agentRunning)return[e.message??BROWSER_USE_AGENT_NOT_RUNNING_MESSAGE];const n=[];t.all?n.push(e.stopped>0?"已关闭所有已启动 browser instances;browser-use-agent 仍在运行。":"没有已启动 browser instances 需要关闭;browser-use-agent 仍在运行。"):1===t.instances.length&&"stopped"===e.results[0]?.status?n.push(`已关闭 browser instance "${t.instances[0]}";browser-use-agent 仍在运行。`):n.push(`已处理 ${String(e.results.length)} 个 browser instances;已关闭 ${String(e.stopped)} 个;browser-use-agent 仍在运行。`);for(const t of e.results)"stopped"!==t.status&&n.push(R(t));return n}function E(e,t,n){if(n)return void console.log(JSON.stringify(e,null,2));const r=createBrowserStopTextLines(e,t);for(const t of r)e.ok?w.success(t):w.error(t)}function R(e){const t=void 0!==e.message?`:${e.message}`:"";switch(e.status){case"not_running":return`browser instance "${e.browserInstance}" 当前未运行。`;case"not_found":return`browser instance "${e.browserInstance}" 不存在${t}`;case"failed":return`browser instance "${e.browserInstance}" 关闭失败${t}`;case"stopped":return`已关闭 browser instance "${e.browserInstance}"。`}}function h(e){return e.some(e=>"not_found"===e.status||"failed"===e.status)}function I(e,t){return"core-managed"===e.runtime.ownership&&void 0===o(t,e.skill.name)}
@@ -1 +1 @@
1
- var t=this&&this.__rewriteRelativeImportExtension||function(t,e){return"string"==typeof t&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(t,r,s,n,o){return r?e?".jsx":".js":!s||n&&o?s+n+"."+o.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";const r=import.meta.url.endsWith(".ts")?"ts":"js";function s(e){const s=new URL(`./${e}.${r}`,import.meta.url).href;return import(t(s)).then(t=>t.default)}export default e({meta:{description:"管理浏览器运行时数据"},subCommands:{"clear-data":()=>s("browser-clear-data")}});
1
+ var t=this&&this.__rewriteRelativeImportExtension||function(t,e){return"string"==typeof t&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(t,r,s,o,n){return r?e?".jsx":".js":!s||o&&n?s+o+"."+n.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";const r=import.meta.url.endsWith(".ts")?"ts":"js";function s(e){const s=new URL(`./${e}.${r}`,import.meta.url).href;return import(t(s)).then(t=>t.default)}export default e({meta:{description:"管理浏览器运行时数据"},subCommands:{"clear-data":()=>s("browser-clear-data"),stop:()=>s("browser-stop")}});
@@ -0,0 +1 @@
1
+ export declare function explainConfig(path: string | undefined): void;
@@ -0,0 +1 @@
1
+ import{getAgentEnvFromAgentsConfig as e,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{loadAgentsConfig as o}from"../../config/loader.js";import{normalizeUserPath as t}from"../../config/key-codec.js";import{AgentStore as l}from"../../registry/store.js";import{findConfigGuidance as s,flattenAgentEnvDeclarations as r,listConfigGuidanceEntries as i}from"./config-guidance.js";export function explainConfig(e){if(void 0===e){console.log("可解释的配置路径:\n");for(const e of i())console.log(`- ${e.path}: ${e.title}`);return console.log("\n示例:"),console.log(" roll config explain install.registry"),void console.log(" roll config explain agents.env.browser-use-agent.REPLY_AUTHORITY_URL")}const n=g(e);if(n)return void console.log(n);const o=s(e);if(!o)return console.error(`✗ 未找到配置说明: ${e}`),console.error(" 运行 `roll config explain` 查看可用路径。"),void(process.exitCode=1);console.log(`${o.title}\n`),console.log(`路径: ${o.path}`),console.log(`用途: ${o.purpose}`),o.defaultBehavior&&console.log(`默认行为: ${o.defaultBehavior}`),o.example&&console.log(`示例:\n${o.example}`),o.setupCommand&&console.log(`配置向导: ${o.setupCommand}`)}function g(s){const i=t(s.split("."));if("agents"!==i[0]||"env"!==i[1])return;const g=i[2];if(void 0===g)return;const c=i[3],{agentsConfig:a}=o(),f=new l(a.dataDir).findByName(g);if(!f)return`Agent 环境变量\n\nAgent "${g}" 未注册。\n配置向导: roll config setup agent ${g}`;const u=r(f.skill.env);if(0===u.length)return`Agent 环境变量\n\nAgent "${g}" 未声明环境变量需求。`;if(void 0===c){const e=n(f.skill.name,f.skill.env,a.env),o=["Agent 环境变量","",`路径: agents.env.${g}`,`用途: 注入到 Agent "${g}" 进程的环境变量。`,`配置向导: roll config setup agent ${g}`,"","变量:"];for(const n of e?.items??u)o.push(`- ${n.name}: ${n.required?"必填":"可选"}${"source"in n?`,来源 ${n.source}`:""}`);return o.join("\n")}const p=u.find(e=>e.name===c);if(!p)return`Agent 环境变量\n\nAgent "${g}" 未声明环境变量 ${c}。\n配置向导: roll config setup agent ${g}`;const $=n(f.skill.name,f.skill.env,a.env),m=$?.items.find(e=>e.name===c),d=e(a,f.skill.name),v=[`Agent 环境变量: ${c}`,"",`路径: agents.env.${g}.${c}`,`Agent: ${g}`,"类型: "+(p.required?"必填":"可选")];return p.purpose&&v.push(`用途: ${p.purpose}`),p.default&&v.push(`默认值: ${p.default}`),p.example&&v.push(`示例: ${p.example}`),m&&v.push(`当前来源: ${m.source}`),d?.[c]&&v.push("当前 YAML: 已配置"),v.push(`配置向导: roll config setup agent ${g}`),v.join("\n")}
@@ -0,0 +1,109 @@
1
+ import type { AgentEnvDeclaration, AgentSkillEnvDeclarations } from "../../types/agent.ts";
2
+ export declare const INSTALL_SCENARIOS: readonly ["default-network", "china-dev", "private-registry", "advanced"];
3
+ export type InstallScenario = (typeof INSTALL_SCENARIOS)[number];
4
+ export interface ConfigGuidanceEntry {
5
+ readonly path: string;
6
+ readonly title: string;
7
+ readonly purpose: string;
8
+ readonly defaultBehavior?: string;
9
+ readonly example?: string;
10
+ readonly setupCommand?: string;
11
+ }
12
+ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
13
+ readonly path: "llm.default-provider";
14
+ readonly title: "默认 LLM Provider";
15
+ readonly purpose: "`roll ask` / `roll chat` 默认使用的模型提供商。";
16
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\u3002`;
17
+ readonly example: `llm:
18
+ default-provider: ${string}`;
19
+ readonly setupCommand: "roll config setup llm";
20
+ }, {
21
+ readonly path: "llm.default-model";
22
+ readonly title: "默认 LLM Model";
23
+ readonly purpose: "`roll ask` / `roll chat` 默认使用的模型名称。";
24
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\u3002`;
25
+ readonly example: `llm:
26
+ default-model: ${string}`;
27
+ readonly setupCommand: "roll config setup llm";
28
+ }, {
29
+ readonly path: "llm.providers.<provider>.api-key";
30
+ readonly title: "Provider API Key";
31
+ readonly purpose: string;
32
+ readonly example: string;
33
+ readonly setupCommand: "roll config setup llm";
34
+ }, {
35
+ readonly path: "llm.providers.<provider>.base-url";
36
+ readonly title: "Provider Base URL";
37
+ readonly purpose: "指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。";
38
+ readonly defaultBehavior: "不配置时使用 provider SDK 默认地址。";
39
+ readonly example: "llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1";
40
+ readonly setupCommand: "roll config setup llm";
41
+ }, {
42
+ readonly path: "install.registry";
43
+ readonly title: "npm Registry";
44
+ readonly purpose: "`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。";
45
+ readonly defaultBehavior: "不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。";
46
+ readonly example: "install:\n registry: https://registry.npmmirror.com";
47
+ readonly setupCommand: "roll config setup install";
48
+ }, {
49
+ readonly path: "install.fetch-retries";
50
+ readonly title: "npm Fetch Retries";
51
+ readonly purpose: "透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。";
52
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${number}\`\u3002`;
53
+ readonly example: `install:
54
+ fetch-retries: ${number}`;
55
+ readonly setupCommand: "roll config setup install";
56
+ }, {
57
+ readonly path: "install.prefer-offline";
58
+ readonly title: "Prefer Offline";
59
+ readonly purpose: "安装时是否附加 `--prefer-offline`。";
60
+ readonly defaultBehavior: "默认值为 `false`,避免更新时复用过期 npm 元数据。" | "默认值为 `true`,避免更新时复用过期 npm 元数据。";
61
+ readonly example: "install:\n prefer-offline: false" | "install:\n prefer-offline: true";
62
+ readonly setupCommand: "roll config setup install";
63
+ }, {
64
+ readonly path: "install.network-timeout-ms";
65
+ readonly title: "Install Network Timeout";
66
+ readonly purpose: "单次 npm install 命令的超时时间,单位毫秒。";
67
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${number}\`\uFF0C\u5373 120 \u79D2\u3002`;
68
+ readonly example: `install:
69
+ network-timeout-ms: ${number}`;
70
+ readonly setupCommand: "roll config setup install";
71
+ }, {
72
+ readonly path: "agents.data-dir";
73
+ readonly title: "Agent 数据目录";
74
+ readonly purpose: "Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。";
75
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\u3002`;
76
+ readonly example: `agents:
77
+ data-dir: ${string}`;
78
+ }, {
79
+ readonly path: "agents.env.<agent-name>";
80
+ readonly title: "Agent 环境变量";
81
+ readonly purpose: "按 Agent 名称声明注入到子 Agent 进程的环境变量。";
82
+ readonly defaultBehavior: "未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。";
83
+ readonly example: "agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com";
84
+ readonly setupCommand: "roll config setup agent <agent-name>";
85
+ }, {
86
+ readonly path: "ask.confirm-threshold";
87
+ readonly title: "Ask 确认阈值";
88
+ readonly purpose: "`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。";
89
+ readonly defaultBehavior: "未配置时使用内置路由默认行为。";
90
+ readonly example: "ask:\n confirm-threshold: 0.5";
91
+ }, {
92
+ readonly path: "browser.default-instance";
93
+ readonly title: "默认浏览器实例";
94
+ readonly purpose: "多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。";
95
+ readonly defaultBehavior: "不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。";
96
+ readonly example: "browser:\n default-instance: boss-a";
97
+ }, {
98
+ readonly path: "browser.instances";
99
+ readonly title: "浏览器实例声明";
100
+ readonly purpose: "声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。";
101
+ readonly defaultBehavior: "不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。";
102
+ readonly example: "browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a";
103
+ }];
104
+ export declare function listConfigGuidanceEntries(): readonly ConfigGuidanceEntry[];
105
+ export declare function findConfigGuidance(path: string): ConfigGuidanceEntry | undefined;
106
+ export declare function flattenAgentEnvDeclarations(declarations: AgentSkillEnvDeclarations | undefined): ReadonlyArray<AgentEnvDeclaration & {
107
+ readonly required: boolean;
108
+ }>;
109
+ export declare function isSecretEnvName(name: string): boolean;
@@ -0,0 +1 @@
1
+ import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as t}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=t(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>l(e.path,r))}function l(e,l){const r=t(e.split(".")).join(".");if(r===l)return!0;const a=r.split("."),n=l.split(".");return a.length===n.length&&a.every((e,t)=>e.startsWith("<")||e===n[t])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
@@ -0,0 +1,37 @@
1
+ export declare class ConfigSetupCancelledError extends Error {
2
+ constructor();
3
+ }
4
+ export interface PromptOption<Value extends string> {
5
+ readonly value: Value;
6
+ readonly label: string;
7
+ readonly hint?: string;
8
+ }
9
+ export interface ConfigPromptAdapter {
10
+ intro(title: string): void;
11
+ outro(message: string): void;
12
+ info(message: string): void;
13
+ warn(message: string): void;
14
+ select<Value extends string>(options: {
15
+ readonly message: string;
16
+ readonly options: readonly PromptOption<Value>[];
17
+ readonly initialValue?: Value;
18
+ }): Promise<Value>;
19
+ text(options: {
20
+ readonly message: string;
21
+ readonly placeholder?: string;
22
+ readonly defaultValue?: string;
23
+ readonly initialValue?: string;
24
+ readonly required?: boolean;
25
+ readonly validate?: (value: string) => string | undefined;
26
+ }): Promise<string>;
27
+ password(options: {
28
+ readonly message: string;
29
+ readonly required?: boolean;
30
+ readonly validate?: (value: string) => string | undefined;
31
+ }): Promise<string>;
32
+ confirm(options: {
33
+ readonly message: string;
34
+ readonly initialValue?: boolean;
35
+ }): Promise<boolean>;
36
+ }
37
+ export declare const clackPromptAdapter: ConfigPromptAdapter;
@@ -0,0 +1 @@
1
+ import{cancel as e,confirm as t,intro as a,isCancel as l,log as i,outro as u,password as o,select as r,text as n}from"@clack/prompts";const s=process.stderr;export class ConfigSetupCancelledError extends Error{constructor(){super("配置向导已取消")}}export const clackPromptAdapter={intro(e){a(e,{output:s})},outro(e){u(e,{output:s})},info(e){i.info(e,{output:s})},warn(e){i.warn(e,{output:s})},async select(e){const t=e.options.map(e=>e.hint?{value:e.value,label:e.label,hint:e.hint}:{value:e.value,label:e.label});return p(await r({message:e.message,options:t,output:s,...e.initialValue?{initialValue:e.initialValue}:{}}))},text:async e=>p(await n({message:e.message,output:s,...e.placeholder?{placeholder:e.placeholder}:{},...void 0!==e.defaultValue?{defaultValue:e.defaultValue}:{},...void 0!==e.initialValue?{initialValue:e.initialValue}:{},validate(t){const a=t??"";return e.required&&0===a.trim().length?"此项不能为空":e.validate?.(a)}})),password:async e=>p(await o({message:e.message,output:s,validate(t){const a=t??"";return e.required&&0===a.trim().length?"此项不能为空":e.validate?.(a)}})),confirm:async e=>p(await t({message:e.message,output:s,...void 0!==e.initialValue?{initialValue:e.initialValue}:{}}))};function p(t){if(l(t))throw e("已取消",{output:s}),new ConfigSetupCancelledError;return t}
@@ -0,0 +1,3 @@
1
+ import { type ConfigPromptAdapter } from "./config-prompts.ts";
2
+ export type ConfigSetupModule = "llm" | "install" | "agent";
3
+ export declare function runConfigSetup(moduleArg: string | undefined, valueArg: string | undefined, prompts?: ConfigPromptAdapter): Promise<void>;
@@ -0,0 +1 @@
1
+ import{existsSync as e,readFileSync as t,writeFileSync as r}from"node:fs";import{resolve as n}from"node:path";import{stringify as i}from"yaml";import{DEFAULT_CONFIG as a,DEFAULT_LLM_MODELS as o,DEFAULT_LLM_PROVIDER as l,LLM_PROVIDER_OPTIONS as s}from"../../config/defaults.js";import{inspectAgentEnvRequirements as c}from"../../config/helpers.js";import{loadAgentsConfig as u,parseConfigDocument as m,resolveConfigPath as f,validateConfigText as d}from"../../config/loader.js";import{encodePathToYaml as g}from"../../config/key-codec.js";import{AgentStore as p}from"../../registry/store.js";import{flattenAgentEnvDeclarations as v,isSecretEnvName as w}from"./config-guidance.js";import{ConfigSetupCancelledError as h,clackPromptAdapter as y}from"./config-prompts.js";const k=/\$\{[^}]+\}/u;export async function runConfigSetup(e,t,r=y){try{r.intro("Roll 配置向导");switch(await $(e,r)){case"llm":await b(r);break;case"install":await A(r);break;case"agent":await V(t,r)}}catch(e){if(e instanceof h)return void(process.exitCode=1);throw e}}async function $(e,t){if(void 0===e)return t.select({message:"要配置哪一类内容?",options:[{value:"llm",label:"LLM",hint:"默认模型和 provider key"},{value:"install",label:"Install network",hint:"npm registry、重试和超时"},{value:"agent",label:"Agent env",hint:"配置某个 Agent 需要的环境变量"}]});if("llm"===e||"install"===e||"agent"===e)return e;throw new Error(`未知 setup 模块: ${e}。可用: llm, install, agent`)}async function b(e){const t=await e.select({message:"选择默认 LLM provider",options:s.map(e=>({value:e,label:e,hint:o[e]})),initialValue:l}),r=await e.text({message:"默认 model",defaultValue:o[t],required:!0}),n=await e.password({message:`${t} API key(可直接输入真实 key,也可输入 \${ENV_VAR})`,required:!0}),i=await e.text({message:"自定义 base-url(可选,留空则不写)",required:!1}),a=q();I(a.document,["llm","defaultProvider"],t),I(a.document,["llm","defaultModel"],r),I(a.document,["llm","providers",t,"apiKey"],n),i.trim().length>0?I(a.document,["llm","providers",t,"baseUrl"],i.trim()):N(a.document,["llm","providers",t,"baseUrl"]),P(a),T(e,`${t} API key`,n),e.outro(`已配置 LLM: ${t}/${r}`)}async function A(e){const t=await e.select({message:"当前安装/更新网络更接近哪种场景?",options:[{value:"default-network",label:"默认网络 / 海外网络",hint:"不设置 registry"},{value:"china-dev",label:"国内开发机",hint:"使用 https://registry.npmmirror.com"},{value:"private-registry",label:"企业内网 / 私有 registry"},{value:"advanced",label:"高级自定义"}]}),r=q();switch(t){case"default-network":j(r.document),N(r.document,["install","registry"]);break;case"china-dev":j(r.document),I(r.document,["install","registry"],"https://registry.npmmirror.com");break;case"private-registry":{const t=await e.text({message:"私有 registry URL",placeholder:"https://registry.example.com",required:!0});j(r.document),I(r.document,["install","registry"],t.trim());break}case"advanced":await x(r.document,e)}P(r),e.outro("已配置 install 网络参数")}async function x(e,t){const r=await t.text({message:"npm registry(留空则不写)",placeholder:"https://registry.npmmirror.com"}),n=await t.text({message:"fetch-retries",defaultValue:"3",required:!0,validate:D(0,10)}),i=await t.confirm({message:"是否启用 prefer-offline?",initialValue:!1}),a=await t.text({message:"network-timeout-ms",defaultValue:"120000",required:!0,validate:D(1e4,Number.MAX_SAFE_INTEGER)});I(e,["install","fetchRetries"],Number.parseInt(n,10)),I(e,["install","preferOffline"],i),I(e,["install","networkTimeoutMs"],Number.parseInt(a,10)),r.trim().length>0?I(e,["install","registry"],r.trim()):N(e,["install","registry"])}async function V(e,t){const{agentsConfig:r}=u(),n=new p(r.dataDir),i=await S(n.list(),e,t),a=c(i.skill.name,i.skill.env,r.env);if(!a)throw new Error(`Agent "${i.skill.name}" 未声明环境变量需求。`);const o=q(),l={...r.env?.[i.skill.name]??{}},s=v(i.skill.env),m=new Map(a.items.map(e=>[e.name,e]));for(const e of s.filter(e=>e.required)){const r=m.get(e.name),n=await M({declaration:e,yamlValue:l[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(l[e.name]=n)}if(await t.confirm({message:"是否配置可选环境变量?",initialValue:!1}))for(const e of s.filter(e=>!e.required)){const r=m.get(e.name),n=await M({declaration:e,yamlValue:l[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(l[e.name]=n)}I(o.document,["agents","env",i.skill.name],l),P(o);for(const[e,r]of Object.entries(l))w(e)&&T(t,e,r);Y(t,i),t.outro(`已配置 Agent 环境变量: ${i.skill.name}`)}async function S(e,t,r){if(void 0!==t){const r=e.find(e=>e.skill.name===t);if(!r)throw new Error(`Agent "${t}" 未找到`);if(!E(r))throw new Error(`Agent "${t}" 未声明环境变量需求。`);return r}const n=e.filter(E);if(0===n.length)throw new Error("没有已注册且声明环境变量需求的 Agent。");const i=await r.select({message:"选择要配置的 Agent",options:n.map(e=>({value:e.skill.name,label:e.skill.name,hint:e.skill.description}))}),a=n.find(e=>e.skill.name===i);if(!a)throw new Error(`Agent "${i}" 未找到`);return a}function E(e){return v(e.skill.env).length>0}async function M(e,t){const{declaration:r,processEnvValue:n,source:i,yamlValue:a}=e,o="agents.env"===i&&O(a),l=[r.required?"必填":"可选",o?"回车保留当前值":void 0].filter(e=>void 0!==e).join(","),s=`${r.name}(${l})`,c=[r.purpose?`用途: ${r.purpose}`:void 0,r.example?`示例: ${r.example}`:void 0,r.default?`默认: ${r.default}`:void 0,o?"当前 YAML 已配置,回车保留当前值。":void 0,void 0===a||o?void 0:"当前 YAML 值为空或占位符未解析,会被视为缺失。","process.env"===i?"当前来源: 当前 shell 临时环境变量,尚未持久写入 YAML。":void 0].filter(e=>void 0!==e).join("\n");if(c.length>0&&t.info(c),"process.env"===i&&"string"==typeof n&&n.length>0){if(await t.confirm({message:`是否将 ${r.name} 的当前 shell 值写入 roll.config.yaml?`,initialValue:!0}))return n}const u=r.required&&!o&&void 0===r.default&&"process.env"!==i,m={message:s,...o&&void 0!==a?{defaultValue:a}:{},...void 0!==r.example?{placeholder:r.example}:{},required:u},f=w(r.name)?await t.password({message:s,required:u}):await t.text(m);return 0===f.trim().length?o?a:void 0:f}function j(e){I(e,["install","fetchRetries"],a.install.fetchRetries),I(e,["install","preferOffline"],a.install.preferOffline),I(e,["install","networkTimeoutMs"],a.install.networkTimeoutMs)}function q(){const r=f(),i=r??n(process.cwd(),"roll.config.yaml");if(void 0===r||!e(i))return{configPath:i,existed:!1,document:L()};const a=t(i,"utf-8");return{configPath:i,existed:!0,raw:a,document:m(a,i)}}function L(){return{llm:{"default-provider":a.llm.defaultProvider,"default-model":a.llm.defaultModel,providers:{}},ask:{},agents:{"data-dir":a.agents.dataDir}}}function P(e){const t=i(e.document,{lineWidth:0});d(t,e.configPath),e.existed&&void 0!==e.raw&&r(R(e.configPath),e.raw,"utf-8"),r(e.configPath,t,"utf-8")}function R(e){const t=new Date;return`${e}.bak.${[t.getFullYear().toString().padStart(4,"0"),(t.getMonth()+1).toString().padStart(2,"0"),t.getDate().toString().padStart(2,"0"),"-",t.getHours().toString().padStart(2,"0"),t.getMinutes().toString().padStart(2,"0"),t.getSeconds().toString().padStart(2,"0")].join("")}`}function I(e,t,r){const n=g(t),i=n[n.length-1];if(void 0===i)throw new Error("配置路径不能为空");let a=e;for(const e of n.slice(0,-1)){const t=a[e];("object"!=typeof t||null===t||Array.isArray(t))&&(a[e]={}),a=a[e]}a[i]=r}function N(e,t){const r=g(t),n=r[r.length-1];if(void 0===n)return;let i=e;for(const e of r.slice(0,-1)){if("object"!=typeof i||null===i||Array.isArray(i))return;i=i[e]}"object"!=typeof i||null===i||Array.isArray(i)||delete i[n]}function D(e,t){return r=>{if(!/^\d+$/u.test(r))return"请输入整数";const n=Number.parseInt(r,10);return n<e||n>t?`请输入 ${String(e)} 到 ${String(t)} 之间的整数`:void 0}}function O(e){return"string"==typeof e&&e.length>0&&!k.test(e)}function T(e,t,r){0===r.trim().length||k.test(r)||e.warn(`${t} 将以明文写入 roll.config.yaml,建议改用 \${ENV_VAR} 引用并避免把配置文件提交到版本库。`)}function Y(e,t){const r=t.skill.name;switch(t.runtime.ownership){case"core-managed":e.info(`需重启后生效:roll agent stop ${r} && roll agent start ${r}`);break;case"external-managed":e.info(`请重启你自行管理的 "${r}" 进程以加载新环境变量。`);break;case"on-demand":e.info(`下次 roll run / roll ask 调用 "${r}" 时自动生效。`)}}
@@ -1 +1 @@
1
- import{defineCommand as o}from"citty";import{readFileSync as t,writeFileSync as r,existsSync as e}from"node:fs";import{resolve as n}from"node:path";import{createInterface as i}from"node:readline/promises";import{stringify as s}from"yaml";import{inspectConfigFile as c,loadConfig as a,parseConfigDocument as l,validateConfigText as d}from"../../config/loader.js";import{encodePathToYaml as f,normalizeUserPath as u}from"../../config/key-codec.js";import{applyKnownConfigMigrations as g}from"../../config/migration.js";export default o({meta:{description:"管理全局配置"},args:{action:{type:"positional",description:"操作(init/get/set/migrate)",required:!0},key:{type:"positional",description:"配置键(get/set 时使用,用英文句点 `.` 分隔,如 ask.confirm-threshold)",required:!1},value:{type:"positional",description:"配置值(set 时使用)",required:!1}},async run({args:o}){try{if("init"===o.action)return void await v();if("get"===o.action)return void w(o.key);if("set"===o.action)return void S(o.key,o.value);if("migrate"===o.action)return void $();console.error(`✗ 未知操作: ${o.action}。可用: init, get, set, migrate`),process.exitCode=1}catch(o){const t=o instanceof Error?o.message:String(o);console.error(`✗ ${t}`),process.exitCode=1}}});function p(o,t){const r=o?.trim();return r||t}function m({provider:o,model:t,apiKeyEnv:r}){return`llm:\n default-provider: ${o}\n default-model: ${t}\n providers:\n ${o}:\n api-key: \${${r}}\n\nask:\n confirm-threshold: 0.5\n\nagents:\n data-dir: ~/.roll-agent/agents\n`}async function h(){if(!process.stdin.isTTY){const[o,r,e]=t(0,"utf-8").split(/\r?\n/u);return{provider:p(o,"anthropic"),model:p(r,"claude-sonnet-4-20250514"),apiKeyEnv:p(e,"ANTHROPIC_API_KEY")}}const o=i({input:process.stdin,output:process.stderr}),r=p(await o.question("默认 LLM provider (anthropic/openai/qwen) [anthropic]: "),"anthropic"),e=p(await o.question("默认 model [claude-sonnet-4-20250514]: "),"claude-sonnet-4-20250514"),n=p(await o.question("API Key 环境变量名 [ANTHROPIC_API_KEY]: "),"ANTHROPIC_API_KEY");return o.close(),{provider:r,model:e,apiKeyEnv:n}}async function v(){const o=n(process.cwd(),"roll.config.yaml");if(e(o)){const t=c({configPath:o});switch(t.status){case"needs-migration":console.error(`⚠ 现有配置文件需要迁移: ${o}`),console.error(" 建议先运行 `roll config migrate`,再决定是否重新初始化。");break;case"invalid":console.error(`⚠ 现有配置文件存在问题:\n${t.error.message}`)}if(console.error(`⚠ 配置文件已存在: ${o}`),!process.stdin.isTTY)throw new Error("非交互模式下不会覆盖现有配置文件,请手动删除后重试。");const r=i({input:process.stdin,output:process.stderr}),e=await r.question("是否覆盖?(y/N) ");if(r.close(),"y"!==e.toLowerCase())return void console.error("已取消。")}const t=m(await h());d(t,o),r(o,t,"utf-8"),console.log(`✓ 配置文件已创建: ${o}`)}function y(o){const t=new Date;return`${o}.bak.${[t.getFullYear().toString().padStart(4,"0"),(t.getMonth()+1).toString().padStart(2,"0"),t.getDate().toString().padStart(2,"0"),"-",t.getHours().toString().padStart(2,"0"),t.getMinutes().toString().padStart(2,"0"),t.getSeconds().toString().padStart(2,"0")].join("")}`}function $(){const o=c();if("not-found"===o.status)throw new Error("未找到配置文件。请先运行 roll config init");if("valid"===o.status)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);if("invalid"===o.status)throw o.error;const t=l(o.raw,o.configPath),e=g(t);if(!e.ok){const o=e.issues.map(o=>` - ${o.message}`).join("\n");throw new Error(`配置无法自动迁移:\n${o}`)}if(!e.changed)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);const n=y(o.configPath);r(n,o.raw,"utf-8");const i=s(e.document,{lineWidth:0});d(i,o.configPath),r(o.configPath,i,"utf-8"),console.log(`✓ 配置文件已迁移: ${o.configPath}`),console.log(`✓ 已备份原文件: ${n}`);for(const o of e.summary)console.log(` - ${o}`)}function w(o){const{config:t,configPath:r}=a();if(!o)return console.log(JSON.stringify(t,null,2)),void(r&&console.error(`(来源: ${r})`));const e=u(o.split("."));let n=t;for(const t of e){if("object"!=typeof n||null===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);n=n[t]}if(void 0===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);console.log("object"==typeof n?JSON.stringify(n,null,2):String(n))}function S(o,e){if(!o||void 0===e)return console.error("✗ 用法: roll config set <key> <value>"),console.error(" 示例: roll config set ask.confirm-threshold 0.5"),void(process.exitCode=1);const{configPath:n}=a();if(!n)return console.error("✗ 未找到配置文件。请先运行 roll config init"),void(process.exitCode=1);const i=t(n,"utf-8"),c=l(i,n),u=f(o.split(".")),g=u[u.length-1];if(void 0===g)return console.error("✗ 配置键不能为空"),void(process.exitCode=1);let p=c;for(let o=0;o<u.length-1;o++){const t=u[o],r=p[t];("object"!=typeof r||null===r||Array.isArray(r))&&(p[t]={}),p=p[t]}let m=e;"true"===e?m=!0:"false"===e?m=!1:/^\d+(\.\d+)?$/.test(e)&&(m=Number(e)),p[g]=m;const h=s(c,{lineWidth:0});d(h,n),r(n,h,"utf-8"),console.log(`✓ ${o} = ${String(m)}`),console.error(` (已写入: ${n})`)}
1
+ import{defineCommand as o}from"citty";import{readFileSync as e,writeFileSync as t,existsSync as r}from"node:fs";import{resolve as n}from"node:path";import{createInterface as i}from"node:readline/promises";import{stringify as s}from"yaml";import{DEFAULT_CONFIG as c,DEFAULT_LLM_MODELS as l,LLM_PROVIDER_OPTIONS as a}from"../../config/defaults.js";import{inspectConfigFile as f,loadConfig as d,parseConfigDocument as u,validateConfigText as p}from"../../config/loader.js";import{encodePathToYaml as g,normalizeUserPath as m}from"../../config/key-codec.js";import{applyKnownConfigMigrations as v}from"../../config/migration.js";import{explainConfig as y}from"./config-explain.js";import{runConfigSetup as h}from"./config-setup.js";export default o({meta:{description:"管理全局配置"},args:{action:{type:"positional",description:"操作(init/get/set/migrate/setup/explain)",required:!0},key:{type:"positional",description:"配置键(get/set 时使用,用英文句点 `.` 分隔,如 ask.confirm-threshold)",required:!1},value:{type:"positional",description:"配置值(set 时使用)",required:!1}},async run({args:o}){try{if("init"===o.action)return void await j();if("get"===o.action)return void K(o.key);if("set"===o.action)return void q(o.key,o.value);if("migrate"===o.action)return void A();if("setup"===o.action)return process.stdin.isTTY?void await h(o.key,o.value):(console.error("✗ `roll config setup` 需要交互式终端。"),console.error(" 非交互环境请改用 `roll config set <key> <value>` 或直接编辑 roll.config.yaml。"),console.error(" 查看配置说明:`roll config explain <key>`。"),void(process.exitCode=1));if("explain"===o.action)return void y(o.key);console.error(`✗ 未知操作: ${o.action}。可用: init, get, set, migrate, setup, explain`),process.exitCode=1}catch(o){const e=o instanceof Error?o.message:String(o);console.error(`✗ ${e}`),process.exitCode=1}}});const $={anthropic:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY",qwen:"DASHSCOPE_API_KEY",deepseek:"DEEPSEEK_API_KEY"};function P(o,e){const t=o?.trim();return t||e}function w(o){return a.some(e=>e===o)}function S(o){return w(o)?l[o]:c.llm.defaultModel}function k(o){return w(o)?$[o]:$.anthropic}function E({provider:o,model:e,apiKeyEnv:t}){return`llm:\n default-provider: ${o}\n default-model: ${e}\n providers:\n ${o}:\n api-key: \${${t}}\n\nask:\n confirm-threshold: 0.5\n\nagents:\n data-dir: ~/.roll-agent/agents\n`}async function x(){if(!process.stdin.isTTY){const[o,t,r]=e(0,"utf-8").split(/\r?\n/u),n=P(o,c.llm.defaultProvider);return{provider:n,model:P(t,S(n)),apiKeyEnv:P(r,k(n))}}const o=i({input:process.stdin,output:process.stderr}),t=P(await o.question(`默认 LLM provider (${a.join("/")}) [${c.llm.defaultProvider}]: `),c.llm.defaultProvider),r=S(t),n=P(await o.question(`默认 model [${r}]: `),r),s=k(t),l=P(await o.question(`API Key 环境变量名 [${s}]: `),s);return o.close(),{provider:t,model:n,apiKeyEnv:l}}async function j(){const o=n(process.cwd(),"roll.config.yaml");if(r(o)){const e=f({configPath:o});switch(e.status){case"needs-migration":console.error(`⚠ 现有配置文件需要迁移: ${o}`),console.error(" 建议先运行 `roll config migrate`,再决定是否重新初始化。");break;case"invalid":console.error(`⚠ 现有配置文件存在问题:\n${e.error.message}`)}if(console.error(`⚠ 配置文件已存在: ${o}`),!process.stdin.isTTY)throw new Error("非交互模式下不会覆盖现有配置文件,请手动删除后重试。");const t=i({input:process.stdin,output:process.stderr}),r=await t.question("是否覆盖?(y/N) ");if(t.close(),"y"!==r.toLowerCase())return void console.error("已取消。")}const e=E(await x());p(e,o),t(o,e,"utf-8"),console.log(`✓ 配置文件已创建: ${o}`)}function C(o){const e=new Date;return`${o}.bak.${[e.getFullYear().toString().padStart(4,"0"),(e.getMonth()+1).toString().padStart(2,"0"),e.getDate().toString().padStart(2,"0"),"-",e.getHours().toString().padStart(2,"0"),e.getMinutes().toString().padStart(2,"0"),e.getSeconds().toString().padStart(2,"0")].join("")}`}function A(){const o=f();if("not-found"===o.status)throw new Error("未找到配置文件。请先运行 roll config init");if("valid"===o.status)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);if("invalid"===o.status)throw o.error;const e=u(o.raw,o.configPath),r=v(e);if(!r.ok){const o=r.issues.map(o=>` - ${o.message}`).join("\n");throw new Error(`配置无法自动迁移:\n${o}`)}if(!r.changed)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);const n=C(o.configPath);t(n,o.raw,"utf-8");const i=s(r.document,{lineWidth:0});p(i,o.configPath),t(o.configPath,i,"utf-8"),console.log(`✓ 配置文件已迁移: ${o.configPath}`),console.log(`✓ 已备份原文件: ${n}`);for(const o of r.summary)console.log(` - ${o}`)}function K(o){const{config:e,configPath:t}=d();if(!o)return console.log(JSON.stringify(e,null,2)),void(t&&console.error(`(来源: ${t})`));const r=m(o.split("."));let n=e;for(const e of r){if("object"!=typeof n||null===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);n=n[e]}if(void 0===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);console.log("object"==typeof n?JSON.stringify(n,null,2):String(n))}function q(o,r){if(!o||void 0===r)return console.error("✗ 用法: roll config set <key> <value>"),console.error(" 示例: roll config set ask.confirm-threshold 0.5"),void(process.exitCode=1);const{configPath:n}=d();if(!n)return console.error("✗ 未找到配置文件。请先运行 roll config init"),void(process.exitCode=1);const i=e(n,"utf-8"),c=u(i,n),l=g(o.split(".")),a=l[l.length-1];if(void 0===a)return console.error("✗ 配置键不能为空"),void(process.exitCode=1);let f=c;for(let o=0;o<l.length-1;o++){const e=l[o],t=f[e];("object"!=typeof t||null===t||Array.isArray(t))&&(f[e]={}),f=f[e]}let m=r;"true"===r?m=!0:"false"===r?m=!1:/^\d+(\.\d+)?$/.test(r)&&(m=Number(r)),f[a]=m;const v=s(c,{lineWidth:0});p(v,n),t(n,v,"utf-8"),console.log(`✓ ${o} = ${String(m)}`),console.error(` (已写入: ${n})`)}
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{resolve as a}from"node:path";import{existsSync as t}from"node:fs";import{execFile as r}from"node:child_process";import{promisify as n}from"node:util";import{inspectConfigFile as s,loadAgentsConfig as i,loadConfig as o}from"../../config/loader.js";import{getAgentEnv as l}from"../../config/helpers.js";import{getAgentPid as c,startAgent as u,stopAgentGracefully as m,waitForAgentReady as p}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as k}from"../../registry/dev-spawn.js";import{runAgentSetup as d}from"../../registry/runtime-setup.js";import{AgentStore as g}from"../../registry/store.js";import{discoverAgent as f}from"../../registry/discovery.js";import{inferAgentSourceType as $,readInstalledPackageManifest as h,resolveInstalledPackageRoot as w}from"../../registry/source.js";import{McpClientManager as y}from"../../mcp/client-manager.js";import{log as v,createSpinner as S}from"../utils/output.js";import{checkForUpdate as V,checkPublishedPackageUpdate as b,getCurrentVersion as j}from"../utils/update-checker.js";import{detectInstallCommand as P,formatPackageManagerError as B,runPackageManager as A}from"../utils/package-manager.js";const D=n(r);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function N(e,a){switch(e.status){case"needs-migration":{const t="post-update"===a?"升级后需要迁移本地配置":"检测到本地配置需要迁移";v.warn(`${t}: ${e.configPath}`);for(const a of e.report.issues)v.warn(` - ${a.message}`);e.report.canAutoMigrate&&v.info("建议命令: roll config migrate");break}case"invalid":v.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),v.warn(` - ${e.error.message}`),"post-update"===a&&v.info("请修复配置文件后再继续使用相关命令。")}}function T(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function E(e,a){switch(e.status){case"up-to-date":return e.currentVersion?`已是最新版本 (v${e.currentVersion})`:"已是最新版本";case"update-available":return e.currentVersion&&e.latestVersion?`可更新 v${e.currentVersion} → v${e.latestVersion}`:"检测到可用更新";case"pinned-behind":return e.currentVersion&&e.latestVersion?`固定版本 v${e.currentVersion};latest=v${e.latestVersion}`:"固定版本,需手动调整 package spec";case"unsupported-spec":return`不支持检查此 package spec: ${a}`;case"unknown":return e.currentVersion?`无法检查最新版本 (current=v${e.currentVersion})`:"无法检查最新版本"}}function C(e,a){if("installed-package"!==e.source?.type)return e;const t=w(e.source.installDir,e.source.packageName),r=h(t);if(!r)return e;const n={...e.source,...r.name?{packageName:r.name}:{},...r.version?{installedVersion:r.version}:{}},s=n.packageName!==e.source.packageName||n.installedVersion!==e.source.installedVersion,i=t!==e.installPath;if(!s&&!i)return e;const o={...e,installPath:t,source:n};return a&&a.replace(e.skill.name,o),o}async function M(e,a){const t=j();if(t===e)return v.info(`roll 已是最新版本 (v${t})`),!1;if(v.info(`roll v${t} → v${e}`),a)return v.info("[dry-run] 跳过实际更新"),!0;const r=S("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`]};try{return await A(n,{timeout:6e4}),r.succeed(`roll 已更新到 v${e}`),!0}catch(e){return r.fail("更新失败"),v.error(B(n,e)),!1}}async function x(e){const r=S(`更新 ${e.skill.name} (git pull)...`).start();try{await D("git",["pull"],{cwd:e.installPath,timeout:3e4}),r.succeed(`${e.skill.name} 代码已更新`);const n=a(e.installPath,"package.json");if(t(n)){const a=P(e.installPath);if(a){const t=S(`安装 ${e.skill.name} 依赖...`).start();try{await A(a,{cwd:e.installPath,timeout:6e4}),t.succeed(`${e.skill.name} 依赖已更新 (${a.command} ${a.args.join(" ")})`)}catch(r){return t.fail(`${e.skill.name} 依赖安装失败`),v.error(B(a,r)),!1}}else v.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(a){return r.fail(`${e.skill.name} 更新失败`),v.error(a instanceof Error?a.message:String(a)),!1}}async function R(e){if("installed-package"!==e.source?.type)return;const a=S(`更新 ${e.skill.name} (npm install)...`).start(),r={command:"npm",args:["install","--prefix",e.source.installDir,e.source.packageSpec]};try{await A(r,{timeout:12e4});const n=w(e.source.installDir,e.source.packageName);if(!t(n))throw new Error(`Installed package root not found: ${n}`);const s=h(n);return a.succeed(`${e.skill.name} 已重新安装`),{packageRoot:n,packageName:s?.name??e.source.packageName,...s?.version?{installedVersion:s.version}:{}}}catch(t){return a.fail(`${e.skill.name} 更新失败`),void v.error(B(r,t))}}async function I(e){const a=S(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),t=new y;try{const r=k(e),n=await t.connect(e.skill.name,r,e.installPath),{tools:s}=await n.listTools();return a.succeed(`${e.skill.name} 元数据已刷新(${s.length} 个 tool)`),!0}catch(t){return a.fail(`${e.skill.name} 刷新失败`),v.error(t instanceof Error?t.message:String(t)),!1}finally{await t.disconnectAll()}}export default e({meta:{description:"检查并更新 roll 及已注册 Agent"},args:{check:{type:"boolean",description:"仅检查 roll/Agent 可用更新,不执行安装或刷新",default:!1},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1}},async run({args:e}){const a=e.check,t=s();v.info("检查 roll 更新...");const r=await V({forceRefresh:!0});let n,o;r.hasUpdate?v.success(`roll 有新版本: v${r.current} → v${r.latest}`):v.info(`roll 已是最新版本 (v${r.current})`);let l=[];try{n=i().agentsConfig,o=new g(n.dataDir),l=o.list()}catch(e){v.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const u=[];for(const e of l){let a=e;"installed-package"===$(a)&&(a=C(a,o));const t=$(a);switch(t){case"git":u.push({name:a.skill.name,sourceType:t,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==a.source?.type){u.push({name:a.skill.name,sourceType:t,icon:"?",action:"无法检查已安装包版本"});break}const e=await b({packageName:a.source.packageName,packageSpec:a.source.packageSpec,...a.source.installedVersion?{currentVersion:a.source.installedVersion}:{}},{forceRefresh:!0});u.push({name:a.skill.name,sourceType:t,icon:T(e.status),action:E(e,a.source.packageSpec)});break}case"remote-manifest":u.push({name:a.skill.name,sourceType:t,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":u.push({name:a.skill.name,sourceType:t,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(l.length>0){v.info(`\n已注册 Agent (${l.length}):`);for(const e of u)v.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else n&&v.info("无已注册 Agent");if("needs-migration"!==t.status&&"invalid"!==t.status||(v.info(""),N(t,a?"check":"pre-update")),a)return;v.info("");let p=!1,k=!1;r.hasUpdate&&(p=await M(r.latest,!1),p||(k=!0)),!p||"needs-migration"!==t.status&&"invalid"!==t.status||(v.info(""),N(t,"post-update"));let h=0,w=0;for(const a of l){if(!o||!n)break;switch($(a)){case"git":{const e="core-managed"===a.runtime.ownership&&void 0!==c(n.dataDir,a.skill.name);if(await x(a))try{const t=f(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};o.replace(a.skill.name,r)?(await L(r,e,n.dataDir),h++):(v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),w++}else w++;break}case"installed-package":{const t="core-managed"===a.runtime.ownership&&void 0!==c(n.dataDir,a.skill.name);if(t)try{await m(n.dataDir,a.skill.name)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),w++;break}const r=await R(a);if(r)try{const s=f(r.packageRoot),i="installed-package"===a.source?.type?{...a.source,packageName:r.packageName,...r.installedVersion?{installedVersion:r.installedVersion}:{}}:void 0,l={...a,skill:s.skill,transport:s.transport,runtime:s.runtime,installPath:r.packageRoot,...i?{source:i}:{},...s.skillBody.length>0?{skillBody:s.skillBody}:{}},c=await d(l,{skipBrowserSetup:e["skip-browser-setup"]});c.ok||(v.warn(`${l.skill.name} setup 失败:${c.message}`),c.retryCommand&&v.info(`重试命令: ${c.retryCommand}`));o.replace(a.skill.name,l)?c.ok?(t&&await U(l,n.dataDir),h++):(o.updateStatus(l.skill.name,"error"),w++):(v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),w++}else{if(t)try{await U(a,n.dataDir)}catch{o.updateStatus(a.skill.name,"error")}w++}break}case"remote-manifest":try{const e=f(a.installPath),t={...a,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!o.replace(a.skill.name,t)){v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++;break}await I(t)?h++:w++}catch(e){v.warn(`${a.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),w++}break;case"local-path":{const e="core-managed"===a.runtime.ownership&&void 0!==c(n.dataDir,a.skill.name);try{const t=f(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};o.replace(a.skill.name,r)?(await L(r,e,n.dataDir),h++):(v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),w++}break}}}v.info("");if(w+(k?1:0)>0){process.exitCode=1;const e=k?"roll 更新失败":p?"roll ✓":"roll 无更新";return void v.warn(`更新完成但有失败:${e}${w>0?`,${w} 个 Agent 更新失败`:""}${h>0?`,${h} 个 Agent 已更新`:""}`)}p||h>0?v.success(`更新完成:${p?"roll ✓":"roll 无更新"}${h>0?`,${h} 个 Agent 已更新`:""}`):v.success("一切都已是最新版本")}});async function L(e,a,t){a&&"core-managed"===e.runtime.ownership&&(await m(t,e.skill.name),await U(e,t))}async function U(e,a){let t=!1;try{u(e,a,l(o().config,e.skill.name)),t=!0,await p(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(r){throw t&&await m(a,e.skill.name).catch(()=>{}),r}}
1
+ import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r}from"node:fs";import{execFile as a}from"node:child_process";import{promisify as s}from"node:util";import{inspectConfigFile as n,loadAgentsConfig as i,loadConfig as o,loadInstallConfig as l}from"../../config/loader.js";import{DEFAULT_CONFIG as c}from"../../config/defaults.js";import{getAgentEnv as u}from"../../config/helpers.js";import{getAgentPid as m,startAgent as p,stopAgentGracefully as f,waitForAgentReady as g}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as k}from"../../registry/dev-spawn.js";import{runAgentSetup as d}from"../../registry/runtime-setup.js";import{AgentStore as y}from"../../registry/store.js";import{discoverAgent as h}from"../../registry/discovery.js";import{inferAgentSourceType as $,readInstalledPackageManifest as w,resolveInstalledPackageRoot as v}from"../../registry/source.js";import{McpClientManager as S}from"../../mcp/client-manager.js";import{log as b,createSpinner as V}from"../utils/output.js";import{checkForUpdate as j,checkPublishedPackageUpdate as P,getCurrentVersion as A}from"../utils/update-checker.js";import{buildNpmRetryPolicy as M,detectInstallCommand as B,formatPackageManagerError as T,npmInstallNetworkArgs as D,runPackageManager as R,runPackageManagerWithRetry as E}from"../utils/package-manager.js";function N(e){return D({...e.registry?{registry:e.registry}:{},fetchRetries:e.fetchRetries,preferOffline:e.preferOffline})}function x(e){return{forceRefresh:!0,fetchRetries:e.fetchRetries,...e.registry?{registry:e.registry}:{}}}function C(e){return"invalid"===e.status&&(e.error.message.startsWith("Invalid YAML syntax")||e.error.message.includes("(expected YAML object)"))}const I=s(a);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function L(e,t){switch(e.status){case"needs-migration":{const r="post-update"===t?"升级后需要迁移本地配置":"检测到本地配置需要迁移";b.warn(`${r}: ${e.configPath}`);for(const t of e.report.issues)b.warn(` - ${t.message}`);e.report.canAutoMigrate&&b.info("建议命令: roll config migrate");break}case"invalid":b.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),b.warn(` - ${e.error.message}`),"post-update"===t&&b.info("请修复配置文件后再继续使用相关命令。")}}function O(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function U(e,t){switch(e.status){case"up-to-date":return e.currentVersion?`已是最新版本 (v${e.currentVersion})`:"已是最新版本";case"update-available":return e.currentVersion&&e.latestVersion?`可更新 v${e.currentVersion} → v${e.latestVersion}`:"检测到可用更新";case"pinned-behind":return e.currentVersion&&e.latestVersion?`固定版本 v${e.currentVersion};latest=v${e.latestVersion}`:"固定版本,需手动调整 package spec";case"unsupported-spec":return`不支持检查此 package spec: ${t}`;case"unknown":return e.currentVersion?`无法检查最新版本 (current=v${e.currentVersion})`:"无法检查最新版本"}}function Y(e,t){if("installed-package"!==e.source?.type)return e;const r=v(e.source.installDir,e.source.packageName),a=w(r);if(!a)return e;const s={...e.source,...a.name?{packageName:a.name}:{},...a.version?{installedVersion:a.version}:{}},n=s.packageName!==e.source.packageName||s.installedVersion!==e.source.installedVersion,i=r!==e.installPath;if(!n&&!i)return e;const o={...e,installPath:r,source:s};return t&&t.replace(e.skill.name,o),o}async function K(e,t,r){const a=A();if(a===e)return b.info(`roll 已是最新版本 (v${a})`),!1;if(b.info(`roll v${a} → v${e}`),t)return b.info("[dry-run] 跳过实际更新"),!0;const s=V("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`,...N(r)]};try{return await E(n,{timeout:r.networkTimeoutMs},{...M(r.fetchRetries),onRetry:({attempt:e,delayMs:t})=>{s.text=`roll 更新遇到网络问题,${Math.round(t/1e3)}s 后重试(第 ${e+1} 次)...`}}),s.succeed(`roll 已更新到 v${e}`),!0}catch(e){return s.fail("更新失败"),b.error(T(n,e)),!1}}async function W(e){const a=V(`更新 ${e.skill.name} (git pull)...`).start();try{await I("git",["pull"],{cwd:e.installPath,timeout:3e4}),a.succeed(`${e.skill.name} 代码已更新`);const s=t(e.installPath,"package.json");if(r(s)){const t=B(e.installPath);if(t){const r=V(`安装 ${e.skill.name} 依赖...`).start();try{await R(t,{cwd:e.installPath,timeout:6e4}),r.succeed(`${e.skill.name} 依赖已更新 (${t.command} ${t.args.join(" ")})`)}catch(a){return r.fail(`${e.skill.name} 依赖安装失败`),b.error(T(t,a)),!1}}else b.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(t){return a.fail(`${e.skill.name} 更新失败`),b.error(t instanceof Error?t.message:String(t)),!1}}async function _(e,t){if("installed-package"!==e.source?.type)return;const a=V(`更新 ${e.skill.name} (npm install)...`).start(),s={command:"npm",args:["install","--prefix",e.source.installDir,e.source.packageSpec,...N(t)]};try{await E(s,{timeout:t.networkTimeoutMs},{...M(t.fetchRetries),onRetry:({attempt:t,delayMs:r})=>{a.text=`更新 ${e.skill.name} 遇到网络问题,${Math.round(r/1e3)}s 后重试(第 ${t+1} 次)...`}});const n=v(e.source.installDir,e.source.packageName);if(!r(n))throw new Error(`Installed package root not found: ${n}`);const i=w(n);return a.succeed(`${e.skill.name} 已重新安装`),{packageRoot:n,packageName:i?.name??e.source.packageName,...i?.version?{installedVersion:i.version}:{}}}catch(t){return a.fail(`${e.skill.name} 更新失败`),void b.error(T(s,t))}}async function q(e){const t=V(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),r=new S;try{const a=k(e),s=await r.connect(e.skill.name,a,e.installPath),{tools:n}=await s.listTools();return t.succeed(`${e.skill.name} 元数据已刷新(${n.length} 个 tool)`),!0}catch(r){return t.fail(`${e.skill.name} 刷新失败`),b.error(r instanceof Error?r.message:String(r)),!1}finally{await r.disconnectAll()}}export default e({meta:{description:"检查并更新 roll 及已注册 Agent"},args:{check:{type:"boolean",description:"仅检查 roll/Agent 可用更新,不执行安装或刷新",default:!1},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1}},async run({args:e}){const t=e.check,r=n();let a;try{a=l().installConfig}catch(e){if(!C(r))return b.error(`install 配置无效,已停止更新:${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1);a=c.install,b.warn(`无法读取 install 配置,使用默认值:${e instanceof Error?e.message:String(e)}`)}a.registry&&b.info(`使用 npm registry: ${a.registry}(roll.config.yaml install.registry)`);const s=x(a);b.info("检查 roll 更新...");const o=await j(s);let u,p;o.hasUpdate?b.success(`roll 有新版本: v${o.current} → v${o.latest}`):b.info(`roll 已是最新版本 (v${o.current})`);let g=[];try{u=i().agentsConfig,p=new y(u.dataDir),g=p.list()}catch(e){b.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const k=[];for(const e of g){let t=e;"installed-package"===$(t)&&(t=Y(t,p));const r=$(t);switch(r){case"git":k.push({name:t.skill.name,sourceType:r,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==t.source?.type){k.push({name:t.skill.name,sourceType:r,icon:"?",action:"无法检查已安装包版本"});break}const e=await P({packageName:t.source.packageName,packageSpec:t.source.packageSpec,...t.source.installedVersion?{currentVersion:t.source.installedVersion}:{}},s);k.push({name:t.skill.name,sourceType:r,icon:O(e.status),action:U(e,t.source.packageSpec)});break}case"remote-manifest":k.push({name:t.skill.name,sourceType:r,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":k.push({name:t.skill.name,sourceType:r,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(g.length>0){b.info(`\n已注册 Agent (${g.length}):`);for(const e of k)b.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else u&&b.info("无已注册 Agent");if("needs-migration"!==r.status&&"invalid"!==r.status||(b.info(""),L(r,t?"check":"pre-update")),t)return;b.info("");let w=!1,v=!1;o.hasUpdate&&(w=await K(o.latest,!1,a),w||(v=!0)),!w||"needs-migration"!==r.status&&"invalid"!==r.status||(b.info(""),L(r,"post-update"));let S=0,V=0;for(const t of g){if(!p||!u)break;switch($(t)){case"git":{const e="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);if(await W(t))try{const r=h(t.installPath),a={...t,skill:r.skill,transport:r.transport,runtime:r.runtime,...r.skillBody.length>0?{skillBody:r.skillBody}:{}};p.replace(t.skill.name,a)?(await z(a,e,u.dataDir),S++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}else V++;break}case"installed-package":{const r="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);if(r)try{await f(u.dataDir,t.skill.name)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),V++;break}const s=await _(t,a);if(s)try{const a=h(s.packageRoot),n="installed-package"===t.source?.type?{...t.source,packageName:s.packageName,...s.installedVersion?{installedVersion:s.installedVersion}:{}}:void 0,i={...t,skill:a.skill,transport:a.transport,runtime:a.runtime,installPath:s.packageRoot,...n?{source:n}:{},...a.skillBody.length>0?{skillBody:a.skillBody}:{}},o=await d(i,{skipBrowserSetup:e["skip-browser-setup"]});o.ok||(b.warn(`${i.skill.name} setup 失败:${o.message}`),o.retryCommand&&b.info(`重试命令: ${o.retryCommand}`));p.replace(t.skill.name,i)?o.ok?(r&&await F(i,u.dataDir),S++):(p.updateStatus(i.skill.name,"error"),V++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}else{if(r)try{await F(t,u.dataDir)}catch{p.updateStatus(t.skill.name,"error")}V++}break}case"remote-manifest":try{const e=h(t.installPath),r={...t,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!p.replace(t.skill.name,r)){b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++;break}await q(r)?S++:V++}catch(e){b.warn(`${t.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),V++}break;case"local-path":{const e="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);try{const r=h(t.installPath),a={...t,skill:r.skill,transport:r.transport,runtime:r.runtime,...r.skillBody.length>0?{skillBody:r.skillBody}:{}};p.replace(t.skill.name,a)?(await z(a,e,u.dataDir),S++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}break}}}b.info("");if(V+(v?1:0)>0){process.exitCode=1;const e=v?"roll 更新失败":w?"roll ✓":"roll 无更新";return void b.warn(`更新完成但有失败:${e}${V>0?`,${V} 个 Agent 更新失败`:""}${S>0?`,${S} 个 Agent 已更新`:""}`)}w||S>0?b.success(`更新完成:${w?"roll ✓":"roll 无更新"}${S>0?`,${S} 个 Agent 已更新`:""}`):b.success("一切都已是最新版本")}});async function z(e,t,r){t&&"core-managed"===e.runtime.ownership&&(await f(r,e.skill.name),await F(e,r))}async function F(e,t){let r=!1;try{p(e,t,u(o().config,e.skill.name)),r=!0,await g(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(a){throw r&&await f(t,e.skill.name).catch(()=>{}),a}}
@@ -24,7 +24,56 @@ export interface PackageManagerExecInvocation {
24
24
  export declare function detectInstallCommand(projectDir: string): InstallCommandSpec | undefined;
25
25
  export declare function createInstallCommand(command?: PackageManagerCommandName): InstallCommandSpec;
26
26
  export declare function runPackageManager(spec: PackageManagerRunSpec, options?: PackageManagerRunOptions): Promise<PackageManagerRunResult>;
27
+ export interface PackageManagerRetryPolicy {
28
+ /** 整体命令的最大尝试次数(>=1)。 */
29
+ readonly attempts: number;
30
+ /** 每次重试前的等待时长(毫秒),长度不足时复用最后一项。 */
31
+ readonly backoffMs?: readonly number[];
32
+ /** 判定某次失败是否值得重试,默认仅对网络/超时类错误重试。 */
33
+ readonly isRetryable?: (error: unknown) => boolean;
34
+ /** 每次重试前回调(用于日志)。 */
35
+ readonly onRetry?: (info: {
36
+ attempt: number;
37
+ error: unknown;
38
+ delayMs: number;
39
+ }) => void;
40
+ }
41
+ /**
42
+ * 在 {@link runPackageManager} 之上叠加“仅对网络/超时类错误重试”的能力。
43
+ *
44
+ * 与 npm 自身的 `--fetch-retries`(针对单个 tarball 抓取)互补:
45
+ * 这里重试的是整条命令,覆盖 registry 完全不可达、超时被 kill 等硬失败场景。
46
+ */
47
+ export declare function runPackageManagerWithRetry(spec: PackageManagerRunSpec, options: PackageManagerRunOptions | undefined, policy: PackageManagerRetryPolicy): Promise<PackageManagerRunResult>;
48
+ /**
49
+ * 构造一个 npm 安装重试策略。
50
+ *
51
+ * `fetchRetries` 同时用于 npm 的 `--fetch-retries` 与 roll 层整体重试:
52
+ * 整体尝试次数 = `min(fetchRetries + 1, 3)`,避免弱网下重试时间无界增长。
53
+ */
54
+ export declare function buildNpmRetryPolicy(fetchRetries: number): PackageManagerRetryPolicy;
27
55
  export declare function createPackageManagerExecInvocation(spec: PackageManagerRunSpec, platform?: NodeJS.Platform): PackageManagerExecInvocation;
28
56
  export declare function shouldRunPackageManagerViaShell(platform: NodeJS.Platform): boolean;
57
+ export interface NpmNetworkOptions {
58
+ /** 显式 registry(镜像源)。未提供时沿用 npm 默认源。 */
59
+ readonly registry?: string;
60
+ /** 透传给 npm 的 `--fetch-retries`。 */
61
+ readonly fetchRetries?: number;
62
+ /** 是否附加 `--prefer-offline`。 */
63
+ readonly preferOffline?: boolean;
64
+ }
65
+ /**
66
+ * 构造 `npm install` 的网络韧性参数。
67
+ *
68
+ * 始终附加 `--no-audit --no-fund`(减少额外网络请求、提速),并按需附加
69
+ * `--registry` / `--fetch-retries` / `--prefer-offline`。
70
+ */
71
+ export declare function npmInstallNetworkArgs(options?: NpmNetworkOptions): string[];
72
+ /** 构造 `npm view` 的网络参数(只需 registry + fetch-retries)。 */
73
+ export declare function npmViewNetworkArgs(options?: NpmNetworkOptions): string[];
29
74
  export declare function formatPackageManagerCommand(spec: PackageManagerRunSpec): string;
75
+ /** 判断错误是否疑似网络/镜像源问题。 */
76
+ export declare function isLikelyNetworkError(error: unknown): boolean;
77
+ /** 网络/超时类错误才值得对整条命令重试。 */
78
+ export declare function isRetryablePackageManagerError(error: unknown): boolean;
30
79
  export declare function formatPackageManagerError(spec: PackageManagerRunSpec, error: unknown, platform?: NodeJS.Platform): string;
@@ -1 +1 @@
1
- import{execFile as n}from"node:child_process";import{existsSync as r,readFileSync as e}from"node:fs";import{resolve as o}from"node:path";import{promisify as t}from"node:util";const a=t(n);export const PACKAGE_MANAGER_COMMANDS=["bun","npm","pnpm","yarn"];export function detectInstallCommand(n){const e=o(n,"package.json");if(r(e)){const n=c(e);if(n)return{command:n,args:["install"]}}const t=[["pnpm-lock.yaml","pnpm"],["package-lock.json","npm"],["npm-shrinkwrap.json","npm"],["yarn.lock","yarn"],["bun.lock","bun"],["bun.lockb","bun"]];for(const[e,a]of t)if(r(o(n,e)))return{command:a,args:["install"]}}export function createInstallCommand(n="pnpm"){return{command:n,args:["install"]}}export async function runPackageManager(n,r={}){const{platform:e,...o}=r,t=createPackageManagerExecInvocation(n,e??process.platform),c=await a(t.file,[...t.args],{...o,encoding:"utf-8",...t.shell?{shell:t.shell}:{}});return{stdout:c.stdout,stderr:c.stderr}}export function createPackageManagerExecInvocation(n,r=process.platform){return shouldRunPackageManagerViaShell(r)?{file:"cmd.exe",args:["/d","/s","/c",u(n)],shell:!1}:{file:n.command,args:n.args,shell:!1}}export function shouldRunPackageManagerViaShell(n){return"win32"===n}export function formatPackageManagerCommand(n){return[n.command,...n.args].map(i).join(" ")}export function formatPackageManagerError(n,r,e=process.platform){const o=formatPackageManagerCommand(n);return p(n.command,r,e)?[`未找到 ${n.command},无法执行 \`${o}\`。`,"win32"===e?`请确认 ${n.command}.cmd 位于 PATH 中;npm 全局目录通常是 C:\\Users\\<you>\\AppData\\Roaming\\npm。`:`请确认已安装 ${n.command},并且当前 shell 的 PATH 可以访问它。`].join(""):r instanceof Error?r.message:String(r)}function c(n){try{const r=JSON.parse(e(n,"utf-8"));if("string"!=typeof r.packageManager||0===r.packageManager.length)return;const[o]=r.packageManager.split("@",1);if(!o)return;return s(o)?o:void 0}catch{return}}function s(n){return PACKAGE_MANAGER_COMMANDS.some(r=>r===n)}function i(n){return/\s/.test(n)?JSON.stringify(n):n}function u(n){return[n.command,...n.args].map(m).join(" ")}function m(n){if(0===n.length)return'""';if(/["\r\n]/.test(n))throw new Error(`Unsupported Windows shell argument: ${n}`);return/[\s"&|<>^()%!]/.test(n)?`"${l(n)}"`:n}function l(n){return n.replace(/[\^&|<>%!]/g,n=>`^${n}`)}function p(n,r,e){if("ENOENT"===f(r))return!0;if("win32"!==e)return!1;const o=g(r).toLowerCase();return o.includes(`'${n}' is not recognized`)||o.includes(`"${n}" is not recognized`)||o.includes(`${n} is not recognized`)||o.includes(`'${n}' 不是内部或外部命令`)||o.includes(`"${n}" 不是内部或外部命令`)||o.includes(`${n} 不是内部或外部命令`)}function f(n){if("object"==typeof n&&null!==n&&"code"in n)return"string"==typeof n.code?n.code:void 0}function g(n){return n instanceof Error?n.message:String(n)}
1
+ import{execFile as e}from"node:child_process";import{existsSync as r,readFileSync as n}from"node:fs";import{resolve as t}from"node:path";import{promisify as o}from"node:util";const a=o(e),i=67108864;export const PACKAGE_MANAGER_COMMANDS=["bun","npm","pnpm","yarn"];export function detectInstallCommand(e){const n=t(e,"package.json");if(r(n)){const e=g(n);if(e)return{command:e,args:["install"]}}const o=[["pnpm-lock.yaml","pnpm"],["package-lock.json","npm"],["npm-shrinkwrap.json","npm"],["yarn.lock","yarn"],["bun.lock","bun"],["bun.lockb","bun"]];for(const[n,a]of o)if(r(t(e,n)))return{command:a,args:["install"]}}export function createInstallCommand(e="pnpm"){return{command:e,args:["install"]}}export async function runPackageManager(e,r={}){const{platform:n,...t}=r,o=createPackageManagerExecInvocation(e,n??process.platform),s=await a(o.file,[...o.args],{maxBuffer:i,...t,encoding:"utf-8",...o.shell?{shell:o.shell}:{}});return{stdout:s.stdout,stderr:s.stderr}}function s(e){return new Promise(r=>setTimeout(r,e))}export async function runPackageManagerWithRetry(e,r={},n){const t=Math.max(1,n.attempts),o=n.isRetryable??isRetryablePackageManagerError;let a;for(let i=1;i<=t;i+=1)try{return await runPackageManager(e,r)}catch(e){a=e;if(!(i<t)||!o(e))throw e;const r=n.backoffMs??[],c=r[i-1]??r[r.length-1]??2e3;n.onRetry?.({attempt:i,error:e,delayMs:c}),await s(c)}throw a}export function buildNpmRetryPolicy(e){const r=Math.min(Math.max(e,0)+1,3);return{attempts:r,backoffMs:[2e3,5e3].slice(0,Math.max(r-1,0)),isRetryable:isRetryablePackageManagerError}}export function createPackageManagerExecInvocation(e,r=process.platform){return shouldRunPackageManagerViaShell(r)?{file:c(),args:["/d","/s","/c",E(e)],shell:!1}:{file:e.command,args:e.args,shell:!1}}export function shouldRunPackageManagerViaShell(e){return"win32"===e}function c(){const e=process.env.ComSpec??process.env.COMSPEC;return e&&e.trim().length>0?e:"cmd.exe"}export function npmInstallNetworkArgs(e={}){const r=["--no-audit","--no-fund"];return e.registry&&r.push(`--registry=${e.registry}`),void 0!==e.fetchRetries&&r.push(`--fetch-retries=${String(e.fetchRetries)}`),e.preferOffline&&r.push("--prefer-offline"),r}export function npmViewNetworkArgs(e={}){const r=[];return e.registry&&r.push(`--registry=${e.registry}`),void 0!==e.fetchRetries&&r.push(`--fetch-retries=${String(e.fetchRetries)}`),r}export function formatPackageManagerCommand(e){return[e.command,...e.args].map(d).join(" ")}const u="疑似网络或 npm 源不稳定。可在 roll.config.yaml 配置 `install.registry` 指向可用镜像(如 https://registry.npmmirror.com),或检查 VPN / 代理后重试。",l=new Set(["ETIMEDOUT","ECONNRESET","ECONNREFUSED","ENOTFOUND","EAI_AGAIN","ENETUNREACH","EHOSTUNREACH","EPIPE","ERR_SOCKET_TIMEOUT","UND_ERR_CONNECT_TIMEOUT","UND_ERR_HEADERS_TIMEOUT"]),f=[/\bnetwork\b/i,/ETIMEDOUT/i,/ECONNRESET/i,/ECONNREFUSED/i,/ENOTFOUND/i,/EAI_AGAIN/i,/socket hang up/i,/fetch failed/i,/getaddrinfo/i,/tunneling socket could not be established/i,/request to .+ failed/i,/npm\s+(?:error|ERR!)\s+code\s+E429/i,/\bregistry\b.*\b(timeout|timed out|unreachable)\b/i,/\bproxy\b/i];function m(e){if("object"!=typeof e||null===e)return!1;const r="killed"in e&&!0===e.killed,n="signal"in e&&"SIGTERM"===e.signal,t=!("code"in e)||null===e.code||void 0===e.code;return r&&n&&t}export function isLikelyNetworkError(e){const r=M(e);if(r&&l.has(r))return!0;const n=N(e);return f.some(e=>e.test(n))}export function isRetryablePackageManagerError(e){return m(e)||isLikelyNetworkError(e)}export function formatPackageManagerError(e,r,n=process.platform){const t=formatPackageManagerCommand(e);if(y(e.command,r,n))return[`未找到 ${e.command},无法执行 \`${t}\`。`,"win32"===n?`请确认 ${e.command}.cmd 位于 PATH 中;npm 全局目录通常是 C:\\Users\\<you>\\AppData\\Roaming\\npm。`:`请确认已安装 ${e.command},并且当前 shell 的 PATH 可以访问它。`].join("");if(m(r))return`\`${t}\` 执行超时被终止。${u}`;if(isLikelyNetworkError(r)){return`\`${t}\` 执行失败:${r instanceof Error?r.message:String(r)}\n${u}`}return r instanceof Error?r.message:String(r)}function g(e){try{const r=JSON.parse(n(e,"utf-8"));if("string"!=typeof r.packageManager||0===r.packageManager.length)return;const[t]=r.packageManager.split("@",1);if(!t)return;return p(t)?t:void 0}catch{return}}function p(e){return PACKAGE_MANAGER_COMMANDS.some(r=>r===e)}function d(e){return/\s/.test(e)?JSON.stringify(e):e}function E(e){return[e.command,...e.args].map(h).join(" ")}function h(e){if(0===e.length)return'""';if(/["\r\n]/.test(e))throw new Error(`Unsupported Windows shell argument: ${e}`);return/[\s"&|<>^()%!]/.test(e)?`"${k(e)}"`:e}function k(e){return e.replace(/[\^&|<>%!]/g,e=>`^${e}`)}function y(e,r,n){if("ENOENT"===M(r))return!0;if("win32"!==n)return!1;const t=N(r).toLowerCase();return t.includes(`'${e}' is not recognized`)||t.includes(`"${e}" is not recognized`)||t.includes(`${e} is not recognized`)||t.includes(`'${e}' 不是内部或外部命令`)||t.includes(`"${e}" 不是内部或外部命令`)||t.includes(`${e} 不是内部或外部命令`)}function M(e){if("object"==typeof e&&null!==e&&"code"in e)return"string"==typeof e.code?e.code:void 0}function N(e){return e instanceof Error?e.message:String(e)}
@@ -3,6 +3,12 @@ export type PublishedPackageUpdateStatus = (typeof PUBLISHED_PACKAGE_UPDATE_STAT
3
3
  interface PackageVersionQueryOptions {
4
4
  readonly forceRefresh?: boolean;
5
5
  readonly allowNetwork?: boolean;
6
+ /** 显式 registry(镜像源),透传给 `npm view`。 */
7
+ readonly registry?: string;
8
+ /** 透传给 npm 的 `--fetch-retries`。 */
9
+ readonly fetchRetries?: number;
10
+ /** `npm view` 单次超时(毫秒),默认 10s。慢网下仍有缓存兜底。 */
11
+ readonly timeoutMs?: number;
6
12
  }
7
13
  export interface PublishedPackageUpdateInfo {
8
14
  readonly packageName: string;
@@ -1 +1 @@
1
- import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as r}from"node:fs";import{resolve as s,dirname as a}from"node:path";import{homedir as o}from"node:os";import{runPackageManager as c}from"./package-manager.js";const i=864e5,u="@roll-agent/core";export const PUBLISHED_PACKAGE_UPDATE_STATUSES=["up-to-date","update-available","pinned-behind","unsupported-spec","unknown"];function p(){return s(o(),".roll-agent","update-check.json")}function f(t){return"object"==typeof t&&null!==t&&("latestVersion"in t&&"checkedAt"in t&&("string"==typeof t.latestVersion&&t.latestVersion.length>0&&"number"==typeof t.checkedAt))}function l(t){return"object"==typeof t&&null!==t&&"packages"in t&&("object"==typeof t.packages&&null!==t.packages&&Object.values(t.packages).every(t=>f(t)))}function h(t){return f(t)}function d(){const e=p();if(r(e))try{const n=t(e,"utf-8"),r=JSON.parse(n);if(l(r))return r;if(h(r))return{packages:{[u]:{latestVersion:r.latestVersion,checkedAt:r.checkedAt}}}}catch{}}function g(t){try{const s=p(),o=a(s);r(o)||n(o,{recursive:!0}),e(s,JSON.stringify(t),"utf-8")}catch{}}function k(t,e){const n=d();g({packages:{...n?.packages??{},[t]:{latestVersion:e,checkedAt:Date.now()}}})}function m(t){return d()?.packages[t]}async function V(t){try{const{stdout:e}=await c({command:"npm",args:["view",t,"version","--json"]},{timeout:5e3}),n=e.trim();if(0===n.length)return;const r=JSON.parse(n);return"string"==typeof r&&r.length>0?r:void 0}catch{return}}export async function fetchLatestPublishedVersion(t,e={}){const n=e.forceRefresh??!1,r=e.allowNetwork??!0,s=m(t);if(!n&&s&&Date.now()-s.checkedAt<i)return s.latestVersion;if(!r)return s?.latestVersion;const a=await V(t);return a?(k(t,a),a):s?.latestVersion}export function getCurrentVersion(){try{const e=s(import.meta.dirname,"../../../package.json"),n=t(e,"utf-8"),r=JSON.parse(n);if("object"==typeof r&&null!==r&&"version"in r){const t=r.version;if("string"==typeof t)return t}}catch{}return"0.0.0"}export function isNewerVersion(t,e){const n=t=>t.replace(/^v/,"").split(".").map(Number),r=n(t),s=n(e);for(let t=0;t<3;t+=1){const e=r[t]??0,n=s[t]??0;if(e>n)return!0;if(e<n)return!1}return!1}function y(t,e){return!["file:","git+","http://","https://","link:","workspace:","npm:"].some(t=>e.startsWith(t))&&(!(e.startsWith(".")||e.startsWith("/")||e.startsWith("~")||e.endsWith(".tgz")||e.endsWith(".tar.gz"))&&(e===t||e.startsWith(`${t}@`)))}function w(t,e){if(e.startsWith(`${t}@`))return e.slice(t.length+1)}function N(t,e){const n=w(t,e);if(!n)return!1;const r=n.replace(/^v/,"");return/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r)}export async function checkPublishedPackageUpdate(t,e={}){const{packageName:n,packageSpec:r,currentVersion:s}=t;if(!y(n,r))return{packageName:n,...s?{currentVersion:s}:{},status:"unsupported-spec"};if(!s)return{packageName:n,status:"unknown"};const a=await fetchLatestPublishedVersion(n,e);return a?isNewerVersion(a,s)?N(n,r)?{packageName:n,currentVersion:s,latestVersion:a,status:"pinned-behind"}:{packageName:n,currentVersion:s,latestVersion:a,status:"update-available"}:{packageName:n,currentVersion:s,latestVersion:a,status:"up-to-date"}:{packageName:n,currentVersion:s,status:"unknown"}}export async function checkForUpdate(t={}){const e=getCurrentVersion(),n=await fetchLatestPublishedVersion(u,t)??e;return{current:e,latest:n,hasUpdate:isNewerVersion(n,e)}}
1
+ import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as r}from"node:fs";import{resolve as s,dirname as a}from"node:path";import{homedir as o}from"node:os";import{npmViewNetworkArgs as i,runPackageManager as c}from"./package-manager.js";const u=864e5,p="@roll-agent/core",f=1e4;export const PUBLISHED_PACKAGE_UPDATE_STATUSES=["up-to-date","update-available","pinned-behind","unsupported-spec","unknown"];function l(){return s(o(),".roll-agent","update-check.json")}function h(t){return"object"==typeof t&&null!==t&&("latestVersion"in t&&"checkedAt"in t&&("string"==typeof t.latestVersion&&t.latestVersion.length>0&&"number"==typeof t.checkedAt))}function g(t){return"object"==typeof t&&null!==t&&"packages"in t&&("object"==typeof t.packages&&null!==t.packages&&Object.values(t.packages).every(t=>h(t)))}function d(t){return h(t)}function k(){const e=l();if(r(e))try{const n=t(e,"utf-8"),r=JSON.parse(n);if(g(r))return r;if(d(r))return{packages:{[p]:{latestVersion:r.latestVersion,checkedAt:r.checkedAt}}}}catch{}}function m(t){try{const s=l(),o=a(s);r(o)||n(o,{recursive:!0}),e(s,JSON.stringify(t),"utf-8")}catch{}}function V(t,e){const n=k();m({packages:{...n?.packages??{},[t]:{latestVersion:e,checkedAt:Date.now()}}})}function y(t){return k()?.packages[t]}async function w(t,e={}){try{const n=i({...e.registry?{registry:e.registry}:{},...void 0!==e.fetchRetries?{fetchRetries:e.fetchRetries}:{}}),{stdout:r}=await c({command:"npm",args:["view",t,"version","--json",...n]},{timeout:e.timeoutMs??f}),s=r.trim();if(0===s.length)return;const a=JSON.parse(s);return"string"==typeof a&&a.length>0?a:void 0}catch{return}}export async function fetchLatestPublishedVersion(t,e={}){const n=e.forceRefresh??!1,r=e.allowNetwork??!0,s=y(t);if(!n&&s&&Date.now()-s.checkedAt<u)return s.latestVersion;if(!r)return s?.latestVersion;const a=await w(t,e);return a?(V(t,a),a):s?.latestVersion}export function getCurrentVersion(){try{const e=s(import.meta.dirname,"../../../package.json"),n=t(e,"utf-8"),r=JSON.parse(n);if("object"==typeof r&&null!==r&&"version"in r){const t=r.version;if("string"==typeof t)return t}}catch{}return"0.0.0"}export function isNewerVersion(t,e){const n=t=>t.replace(/^v/,"").split(".").map(Number),r=n(t),s=n(e);for(let t=0;t<3;t+=1){const e=r[t]??0,n=s[t]??0;if(e>n)return!0;if(e<n)return!1}return!1}function N(t,e){return!["file:","git+","http://","https://","link:","workspace:","npm:"].some(t=>e.startsWith(t))&&(!(e.startsWith(".")||e.startsWith("/")||e.startsWith("~")||e.endsWith(".tgz")||e.endsWith(".tar.gz"))&&(e===t||e.startsWith(`${t}@`)))}function b(t,e){if(e.startsWith(`${t}@`))return e.slice(t.length+1)}function v(t,e){const n=b(t,e);if(!n)return!1;const r=n.replace(/^v/,"");return/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r)}export async function checkPublishedPackageUpdate(t,e={}){const{packageName:n,packageSpec:r,currentVersion:s}=t;if(!N(n,r))return{packageName:n,...s?{currentVersion:s}:{},status:"unsupported-spec"};if(!s)return{packageName:n,status:"unknown"};const a=await fetchLatestPublishedVersion(n,e);return a?isNewerVersion(a,s)?v(n,r)?{packageName:n,currentVersion:s,latestVersion:a,status:"pinned-behind"}:{packageName:n,currentVersion:s,latestVersion:a,status:"update-available"}:{packageName:n,currentVersion:s,latestVersion:a,status:"up-to-date"}:{packageName:n,currentVersion:s,status:"unknown"}}export async function checkForUpdate(t={}){const e=getCurrentVersion(),n=await fetchLatestPublishedVersion(p,t)??e;return{current:e,latest:n,hasUpdate:isNewerVersion(n,e)}}
@@ -1,4 +1,4 @@
1
1
  import type { BrowserConfig } from "./schema.ts";
2
- export declare const LEGACY_BROWSER_IDENTITY_ENV_KEYS: readonly ["BROWSER_CDP_PORT", "BROWSER_USER_DATA_DIR", "BROWSER_CDP_URL", "BROWSER_SESSIONS_DIR"];
2
+ export declare const LEGACY_BROWSER_IDENTITY_ENV_KEYS: readonly ["BROWSER_CDP_PORT", "BROWSER_USER_DATA_DIR", "BROWSER_CDP_URL", "BROWSER_SESSIONS_DIR", "BROWSER_PROFILE_COLOR"];
3
3
  export type LegacyBrowserIdentityEnvKey = (typeof LEGACY_BROWSER_IDENTITY_ENV_KEYS)[number];
4
4
  export declare function collectBrowserConfigWarnings(browserConfig: BrowserConfig, agentEnv: Readonly<Record<string, string>> | undefined, inheritedEnv?: NodeJS.ProcessEnv): readonly string[];
@@ -1 +1 @@
1
- export const LEGACY_BROWSER_IDENTITY_ENV_KEYS=["BROWSER_CDP_PORT","BROWSER_USER_DATA_DIR","BROWSER_CDP_URL","BROWSER_SESSIONS_DIR"];export function collectBrowserConfigWarnings(n,e,t=process.env){const s=Object.keys(n.instances);if(0===s.length)return[];const r=[];s.length>1&&void 0===n.defaultInstance&&r.push(`browser.instances 声明了 ${String(s.length)} 个实例,但未配置 browser.default-instance;未显式传 browserInstance 的 tool 调用会返回 needs_input`);const o=LEGACY_BROWSER_IDENTITY_ENV_KEYS.filter(n=>{const s=e?.[n],r=t[n];return void 0!==s&&s.length>0||void 0!==r&&r.length>0});return o.length>0&&r.push(`browser.instances 已配置,agents.env 或 shell 中的 ${o.join(", ")} 会被忽略;实例身份以 browser.instances 为准`),r}
1
+ export const LEGACY_BROWSER_IDENTITY_ENV_KEYS=["BROWSER_CDP_PORT","BROWSER_USER_DATA_DIR","BROWSER_CDP_URL","BROWSER_SESSIONS_DIR","BROWSER_PROFILE_COLOR"];export function collectBrowserConfigWarnings(n,e,t=process.env){const s=Object.keys(n.instances);if(0===s.length)return[];const r=[];s.length>1&&void 0===n.defaultInstance&&r.push(`browser.instances 声明了 ${String(s.length)} 个实例,但未配置 browser.default-instance;未显式传 browserInstance 的 tool 调用会返回 needs_input`);const o=LEGACY_BROWSER_IDENTITY_ENV_KEYS.filter(n=>{const s=e?.[n],r=t[n];return void 0!==s&&s.length>0||void 0!==r&&r.length>0});return o.length>0&&r.push(`browser.instances 已配置,agents.env 或 shell 中的 ${o.join(", ")} 会被忽略;实例身份以 browser.instances 为准`),r}
@@ -1,4 +1,13 @@
1
1
  import type { RollConfig } from "./schema.ts";
2
+ export declare const LLM_PROVIDER_OPTIONS: readonly ["anthropic", "openai", "qwen", "deepseek"];
3
+ export type LlmProviderOption = (typeof LLM_PROVIDER_OPTIONS)[number];
4
+ export declare const DEFAULT_LLM_PROVIDER: LlmProviderOption;
5
+ export declare const DEFAULT_LLM_MODELS: {
6
+ readonly anthropic: "claude-sonnet-4-6";
7
+ readonly openai: "gpt-5.5";
8
+ readonly qwen: "qwen3.6-plus";
9
+ readonly deepseek: "deepseek-v4-flash";
10
+ };
2
11
  /** 默认配置值 */
3
12
  export declare const DEFAULT_CONFIG: RollConfig;
4
13
  /** 配置文件查找顺序 */
@@ -1 +1 @@
1
- export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:"claude-sonnet-4-20250514",providers:{}},ask:{},agents:{dataDir:"~/.roll-agent/agents"},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
1
+ export const LLM_PROVIDER_OPTIONS=["anthropic","openai","qwen","deepseek"];export const DEFAULT_LLM_PROVIDER="anthropic";export const DEFAULT_LLM_MODELS={anthropic:"claude-sonnet-4-6",openai:"gpt-5.5",qwen:"qwen3.6-plus",deepseek:"deepseek-v4-flash"};export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:DEFAULT_LLM_MODELS.anthropic,providers:{}},ask:{},agents:{dataDir:"~/.roll-agent/agents"},install:{fetchRetries:3,preferOffline:!1,networkTimeoutMs:12e4},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
@@ -1 +1 @@
1
- const e={kind:"leaf"},r={kind:"object",fields:{apiKey:e,baseUrl:e}},o={kind:"object",fields:{platform:e,mode:e,headless:e,cdpUrl:e,cdpHost:e,cdpPort:e,channel:e,executablePath:e,userDataDir:e,sessionsDir:e,args:e,profileName:e,windowBounds:{kind:"object",fields:{x:e,y:e,width:e,height:e}},trackingAgentId:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:r}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}},browser:{kind:"object",fields:{defaultInstance:e,instances:{kind:"record",value:o}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,r)=>r.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function t(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(r,o=CONFIG_KEY_CODEC){if(Array.isArray(r))return r.map(e=>decodeFromYaml(e,o));if(!t(r))return r;if("object"===o.kind){const t={};for(const[n,d]of Object.entries(r)){const r=kebabToCamel(n),a=o.fields[r]??e;t[r]=decodeFromYaml(d,a)}return t}if("record"===o.kind){const e={};for(const[t,n]of Object.entries(r))e[t]=decodeFromYaml(n,o.value);return e}return r}function n(r,o){const t=[];let n=CONFIG_KEY_CODEC;for(const d of r)if("object"===n.kind){const r=kebabToCamel(d),a=n.fields[r];a?(t.push("camel"===o?r:camelToKebab(r)),n=a):(t.push(d),n=e)}else"record"===n.kind?(t.push(d),n=n.value):t.push(d);return t}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
1
+ const e={kind:"leaf"},r={kind:"object",fields:{apiKey:e,baseUrl:e}},o={kind:"object",fields:{platform:e,mode:e,headless:e,cdpUrl:e,cdpHost:e,cdpPort:e,channel:e,executablePath:e,userDataDir:e,sessionsDir:e,args:e,profileName:e,profileColor:e,windowBounds:{kind:"object",fields:{x:e,y:e,width:e,height:e}},trackingAgentId:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:r}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}},install:{kind:"object",fields:{registry:e,fetchRetries:e,preferOffline:e,networkTimeoutMs:e}},browser:{kind:"object",fields:{defaultInstance:e,instances:{kind:"record",value:o}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,r)=>r.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function t(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(r,o=CONFIG_KEY_CODEC){if(Array.isArray(r))return r.map(e=>decodeFromYaml(e,o));if(!t(r))return r;if("object"===o.kind){const t={};for(const[n,i]of Object.entries(r)){const r=kebabToCamel(n),d=o.fields[r]??e;t[r]=decodeFromYaml(i,d)}return t}if("record"===o.kind){const e={};for(const[t,n]of Object.entries(r))e[t]=decodeFromYaml(n,o.value);return e}return r}function n(r,o){const t=[];let n=CONFIG_KEY_CODEC;for(const i of r)if("object"===n.kind){const r=kebabToCamel(i),d=n.fields[r];d?(t.push("camel"===o?r:camelToKebab(r)),n=d):(t.push(i),n=e)}else"record"===n.kind?(t.push(i),n=n.value):t.push(i);return t}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
@@ -16,6 +16,11 @@ export interface LoadAgentsConfigResult {
16
16
  /** 实际加载的配置文件路径,undefined 表示使用默认配置 */
17
17
  readonly configPath: string | undefined;
18
18
  }
19
+ export interface LoadInstallConfigResult {
20
+ readonly installConfig: RollConfig["install"];
21
+ /** 实际加载的配置文件路径,undefined 表示使用默认配置 */
22
+ readonly configPath: string | undefined;
23
+ }
19
24
  declare const CONFIG_INSPECTION_STATUSES: {
20
25
  readonly notFound: "not-found";
21
26
  readonly valid: "valid";
@@ -50,6 +55,13 @@ export interface ConfigInspectionInvalid extends ConfigInspectionBase {
50
55
  export type ConfigInspectionResult = ConfigInspectionNotFound | ConfigInspectionValid | ConfigInspectionNeedsMigration | ConfigInspectionInvalid;
51
56
  export declare function parseConfigDocument(raw: string, configPath: string): Record<string, unknown>;
52
57
  export declare function validateConfigText(raw: string, configPath: string): RollConfig;
58
+ /**
59
+ * 加载 `install` 段配置(npm 安装/更新行为)。
60
+ *
61
+ * 解析失败(缺失文件、YAML 语法错误、install 段非法)时抛错,
62
+ * 避免用户显式配置 registry 时因局部错误静默回退到 npm 默认源。
63
+ */
64
+ export declare function loadInstallConfig(options?: LoadConfigOptions): LoadInstallConfigResult;
53
65
  /**
54
66
  * 加载并校验 Roll 配置。
55
67
  *
@@ -1 +1 @@
1
- import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,rollConfigSchema as i}from"./schema.js";import{DEFAULT_CONFIG as s,CONFIG_FILE_NAMES as a}from"./defaults.js";import{decodeFromYaml as f}from"./key-codec.js";import{detectKnownConfigMigrations as c,formatConfigMigrationError as u}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(l(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function l(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function d(n){return l(n)&&"number"==typeof n.line&&"number"==typeof n.col}function p(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(d)}function h(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(p(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function m(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of a){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function w(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:v(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:v(r.sessionsDir)}:{}}]))}}}const $={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(h(r,n),{cause:n instanceof Error?n:void 0})}if(!l(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function y(n,r,t={}){const o=parseConfigDocument(n,r),e=c(o,t);if(e.needsMigration)throw new Error(u(r,e));return o}export function validateConfigText(n,r){const t=y(n,r),o=g(f(t));if(!l(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=E(s,o),a=i.safeParse(e);if(!a.success){const n=a.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return w(a.data)}function P(n,r){const t=y(n,r,{scope:"agents"}),o=g(f(t));if(!l(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=l(o.agents)?o.agents:{},a=E(s.agents,i),c=e.safeParse(a);if(!c.success){const n=c.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...c.data,dataDir:v(c.data.dataDir)}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:w(s),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...s.agents,dataDir:v(s.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:P(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??m(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:$.notFound,configPath:void 0};if(!r(o))return{status:$.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:$.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=c(i);if(s.needsMigration)return{status:$.needsMigration,configPath:o,raw:e,report:s};try{return{status:$.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:$.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function E(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=E(r,e)}return t}
1
+ import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,installConfigSchema as i,rollConfigSchema as s}from"./schema.js";import{DEFAULT_CONFIG as a,CONFIG_FILE_NAMES as f}from"./defaults.js";import{decodeFromYaml as c}from"./key-codec.js";import{detectKnownConfigMigrations as u,formatConfigMigrationError as l}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(d(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function d(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function p(n){return d(n)&&"number"==typeof n.line&&"number"==typeof n.col}function h(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(p)}function m(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(h(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function w(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of f){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function $(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:v(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:v(r.sessionsDir)}:{}}]))}}}const C={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(m(r,n),{cause:n instanceof Error?n:void 0})}if(!d(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function P(n,r,t={}){const o=parseConfigDocument(n,r),e=u(o,t);if(e.needsMigration)throw new Error(l(r,e));return o}export function validateConfigText(n,r){const t=P(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=j(a,o),i=s.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return $(i.data)}function E(n,r){const t=P(n,r,{scope:"agents"}),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=d(o.agents)?o.agents:{},s=j(a.agents,i),f=e.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...f.data,dataDir:v(f.data.dataDir)}}function y(n,r){const t=parseConfigDocument(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=d(o.install)?o.install:{},s=j(a.install,e),f=i.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return f.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:a.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:y(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:$(a),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...a.agents,dataDir:v(a.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:E(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??w(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:C.notFound,configPath:void 0};if(!r(o))return{status:C.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=u(i);if(s.needsMigration)return{status:C.needsMigration,configPath:o,raw:e,report:s};try{return{status:C.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function j(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=j(r,e)}return t}
@@ -1 +1 @@
1
- import{createHash as e}from"node:crypto";import{z as n}from"zod";export const DIAGNOSTIC_TOOL_CANDIDATES=["diagnostic_status","browser_status"];const t=new Set(DIAGNOSTIC_TOOL_CANDIDATES),i=/^[0-9a-f]{8}$/;export const EffectiveEnvSourceSchema=n.object({present:n.boolean(),fingerprint:n.string().regex(i).optional()});export const BrowserSecurityDiagnosticSchema=n.object({domainAllowlist:n.array(n.string()),maxPageContentBytes:n.number(),maxSnapshotNodes:n.number(),actionPolicy:n.enum(["log","deny","confirm"]),foregroundPolicy:n.enum(["when-minimized","always","never"]).default("when-minimized")});export const BrowserUseToolPolicyDiagnosticSchema=n.object({approvalTtlMs:n.number(),tools:n.record(n.object({policy:n.enum(["log","deny","confirm"])}))});export const BrowserUsePolicyWarningDiagnosticSchema=n.object({code:n.string(),message:n.string()});export const BrowserInstanceStatusDiagnosticSchema=n.object({id:n.string(),platform:n.enum(["zhipin","yupao"]).optional(),mode:n.enum(["managed-cdp","remote-cdp","existing-session"]),cdp:n.object({endpoint:n.string(),port:n.number().int().min(1).max(65535).optional(),versionReachable:n.boolean(),listReachable:n.boolean()}),profile:n.object({userDataDir:n.string().optional(),exists:n.boolean(),writable:n.boolean()}),tracking:n.object({source:n.enum(["instance","default-env","missing"]),agentIdFingerprint:n.string().regex(i).optional()})});export const AgentRuntimeEnvDiagnosticPayloadSchema=n.object({effectiveEnvSources:n.record(EffectiveEnvSourceSchema),security:BrowserSecurityDiagnosticSchema.optional(),toolPolicy:BrowserUseToolPolicyDiagnosticSchema.optional(),policyWarnings:n.array(BrowserUsePolicyWarningDiagnosticSchema).optional(),defaultInstanceId:n.string().optional(),primaryInstanceId:n.string().optional(),instances:n.array(BrowserInstanceStatusDiagnosticSchema).optional()});export function shouldSkipRuntimeReadinessForTool(e){return t.has(e)}export function inspectAgentRuntimeEnvRequirements(e,n,t){const i=e.items.map(e=>{const i=n?.[e.name],r=void 0!==i;if("unverified"===t.status)return{...e,configuredInAgentsEnv:r,runtime:{verified:!1,reason:t.reason,message:t.message}};const o=t.payload.effectiveEnvSources[e.name];return o?.present?{...e,configuredInAgentsEnv:r,runtime:{verified:!0,present:!0,...void 0!==o.fingerprint?{fingerprint:o.fingerprint}:{},matchesAgentsEnv:void 0!==i&&void 0!==o.fingerprint&&s(i)===o.fingerprint}}:{...e,configuredInAgentsEnv:r,runtime:{verified:!0,present:!1}}});return{items:i,inspection:t,missingRequired:i.filter(e=>e.required&&r(e)),ephemeralItems:i.filter(o)}}export function summarizeAgentRuntimeEnvReport(e){if(e.missingRequired.length>0){const n=c(e.missingRequired);return"verified"===e.inspection.status?{status:"fail",message:`运行态缺失: ${n}`}:{status:"fail",message:`${a(e)};${e.inspection.message}`}}if(e.ephemeralItems.length>0)return{status:"warn",message:`运行态漂移: ${c(e.ephemeralItems)}`};if("verified"===e.inspection.status)return{status:"ok",message:`声明的必填项已在运行态生效(${e.inspection.toolName})`};const n=e.items.filter(e=>e.required&&"process.env"===e.source);return n.length>0?{status:"warn",message:`依赖当前 shell 环境: ${c(n)};${e.inspection.message}`}:"diagnostic-tool-unavailable"===e.inspection.reason?{status:"ok",message:`声明的必填项已满足(${e.inspection.message})`}:{status:"warn",message:`声明的必填项已满足;${e.inspection.message}`}}export function formatAgentEnvDeclarationSource(e){switch(e.source){case"agents.env":return"已配置于 agents.env";case"process.env":return"仅当前 shell 环境";case"default":return`默认值 (${e.default})`;case"missing":return"缺失"}}export function formatAgentRuntimeVerification(e){return"verified"===e.inspection.status?`已验证(${e.inspection.toolName})`:`未校验(${e.inspection.message})`}export function formatAgentEnvRuntimeStatus(e){if(e.runtime.verified)return e.runtime.present?e.runtime.matchesAgentsEnv?"✓ from yaml (stable)":e.configuredInAgentsEnv?"⚠ differs from yaml (ephemeral)":"⚠ from shell (ephemeral)":"default"===e.source?"未设置(使用默认值)":"process.env"===e.source?"✗ 当前运行 agent 未看到该变量":"✗ missing"}function s(n){return e("sha256").update(n).digest("hex").slice(0,8)}function r(e){return e.runtime.verified?!e.runtime.present&&"default"!==e.source:"missing"===e.source}function o(e){return e.runtime.verified&&e.runtime.present&&!e.runtime.matchesAgentsEnv}function a(e){return`缺少必填项: ${c(e.missingRequired)}`}function c(e){return e.map(e=>e.name).join(", ")}
1
+ import{createHash as e}from"node:crypto";import{z as n}from"zod";export const DIAGNOSTIC_TOOL_CANDIDATES=["diagnostic_status","browser_status"];const t=[...DIAGNOSTIC_TOOL_CANDIDATES,"browser_stop"],i=new Set(t),s=/^[0-9a-f]{8}$/;export const EffectiveEnvSourceSchema=n.object({present:n.boolean(),fingerprint:n.string().regex(s).optional()});export const BrowserSecurityDiagnosticSchema=n.object({domainAllowlist:n.array(n.string()),maxPageContentBytes:n.number(),maxSnapshotNodes:n.number(),actionPolicy:n.enum(["log","deny","confirm"]),foregroundPolicy:n.enum(["when-minimized","always","never"]).default("when-minimized")});export const BrowserUseToolPolicyDiagnosticSchema=n.object({approvalTtlMs:n.number(),tools:n.record(n.object({policy:n.enum(["log","deny","confirm"])}))});export const BrowserUsePolicyWarningDiagnosticSchema=n.object({code:n.string(),message:n.string()});export const BrowserInstanceStatusDiagnosticSchema=n.object({id:n.string(),platform:n.enum(["zhipin","yupao"]).optional(),mode:n.enum(["managed-cdp","remote-cdp","existing-session"]),cdp:n.object({endpoint:n.string(),port:n.number().int().min(1).max(65535).optional(),versionReachable:n.boolean(),listReachable:n.boolean()}),profile:n.object({userDataDir:n.string().optional(),exists:n.boolean(),writable:n.boolean()}),tracking:n.object({source:n.enum(["instance","default-env","missing"]),agentIdFingerprint:n.string().regex(s).optional()})});export const AgentRuntimeEnvDiagnosticPayloadSchema=n.object({effectiveEnvSources:n.record(EffectiveEnvSourceSchema),security:BrowserSecurityDiagnosticSchema.optional(),toolPolicy:BrowserUseToolPolicyDiagnosticSchema.optional(),policyWarnings:n.array(BrowserUsePolicyWarningDiagnosticSchema).optional(),defaultInstanceId:n.string().optional(),primaryInstanceId:n.string().optional(),instances:n.array(BrowserInstanceStatusDiagnosticSchema).optional()});export function shouldSkipRuntimeReadinessForTool(e){return i.has(e)}export function inspectAgentRuntimeEnvRequirements(e,n,t){const i=e.items.map(e=>{const i=n?.[e.name],s=void 0!==i;if("unverified"===t.status)return{...e,configuredInAgentsEnv:s,runtime:{verified:!1,reason:t.reason,message:t.message}};const o=t.payload.effectiveEnvSources[e.name];return o?.present?{...e,configuredInAgentsEnv:s,runtime:{verified:!0,present:!0,...void 0!==o.fingerprint?{fingerprint:o.fingerprint}:{},matchesAgentsEnv:void 0!==i&&void 0!==o.fingerprint&&r(i)===o.fingerprint}}:{...e,configuredInAgentsEnv:s,runtime:{verified:!0,present:!1}}});return{items:i,inspection:t,missingRequired:i.filter(e=>e.required&&o(e)),ephemeralItems:i.filter(a)}}export function summarizeAgentRuntimeEnvReport(e){if(e.missingRequired.length>0){const n=u(e.missingRequired);return"verified"===e.inspection.status?{status:"fail",message:`运行态缺失: ${n}`}:{status:"fail",message:`${c(e)};${e.inspection.message}`}}if(e.ephemeralItems.length>0)return{status:"warn",message:`运行态漂移: ${u(e.ephemeralItems)}`};if("verified"===e.inspection.status)return{status:"ok",message:`声明的必填项已在运行态生效(${e.inspection.toolName})`};const n=e.items.filter(e=>e.required&&"process.env"===e.source);return n.length>0?{status:"warn",message:`依赖当前 shell 环境: ${u(n)};${e.inspection.message}`}:"diagnostic-tool-unavailable"===e.inspection.reason?{status:"ok",message:`声明的必填项已满足(${e.inspection.message})`}:{status:"warn",message:`声明的必填项已满足;${e.inspection.message}`}}export function formatAgentEnvDeclarationSource(e){switch(e.source){case"agents.env":return"已配置于 agents.env";case"process.env":return"仅当前 shell 环境";case"default":return`默认值 (${e.default})`;case"missing":return"缺失"}}export function formatAgentRuntimeVerification(e){return"verified"===e.inspection.status?`已验证(${e.inspection.toolName})`:`未校验(${e.inspection.message})`}export function formatAgentEnvRuntimeStatus(e){if(e.runtime.verified)return e.runtime.present?e.runtime.matchesAgentsEnv?"✓ from yaml (stable)":e.configuredInAgentsEnv?"⚠ differs from yaml (ephemeral)":"⚠ from shell (ephemeral)":"default"===e.source?"未设置(使用默认值)":"process.env"===e.source?"✗ 当前运行 agent 未看到该变量":"✗ missing"}function r(n){return e("sha256").update(n).digest("hex").slice(0,8)}function o(e){return e.runtime.verified?!e.runtime.present&&"default"!==e.source:"missing"===e.source}function a(e){return e.runtime.verified&&e.runtime.present&&!e.runtime.matchesAgentsEnv}function c(e){return`缺少必填项: ${u(e.missingRequired)}`}function u(e){return e.map(e=>e.name).join(", ")}
@@ -74,6 +74,32 @@ export declare const agentsConfigSchema: z.ZodObject<{
74
74
  dataDir: string;
75
75
  env?: Record<string, Record<string, string>> | undefined;
76
76
  }>;
77
+ /**
78
+ * npm 安装/更新行为配置(影响 `roll agent install` / `roll update`)。
79
+ *
80
+ * 安全口径:默认走 npm 自身的源(通常官方源),只有用户显式配置 `registry`
81
+ * 才切换镜像源;roll 不做隐式自动 fallback。
82
+ */
83
+ export declare const installConfigSchema: z.ZodObject<{
84
+ /** 显式 opt-in 的 npm registry(如国内镜像)。未配置时使用 npm 默认源。 */
85
+ registry: z.ZodOptional<z.ZodString>;
86
+ /** 透传给 npm 的 `--fetch-retries`,并用于 roll 层整体重试次数。 */
87
+ fetchRetries: z.ZodDefault<z.ZodNumber>;
88
+ /** 安装时附加 `--prefer-offline`,默认关闭以避免更新时复用过期 npm 元数据。 */
89
+ preferOffline: z.ZodDefault<z.ZodBoolean>;
90
+ /** 单次 npm 安装命令的超时(毫秒)。 */
91
+ networkTimeoutMs: z.ZodDefault<z.ZodNumber>;
92
+ }, "strip", z.ZodTypeAny, {
93
+ fetchRetries: number;
94
+ preferOffline: boolean;
95
+ networkTimeoutMs: number;
96
+ registry?: string | undefined;
97
+ }, {
98
+ registry?: string | undefined;
99
+ fetchRetries?: number | undefined;
100
+ preferOffline?: boolean | undefined;
101
+ networkTimeoutMs?: number | undefined;
102
+ }>;
77
103
  export declare const browserInstanceConfigSchema: z.ZodEffects<z.ZodObject<{
78
104
  platform: z.ZodOptional<z.ZodEnum<["zhipin", "yupao"]>>;
79
105
  mode: z.ZodDefault<z.ZodEnum<["managed-cdp", "remote-cdp", "existing-session"]>>;
@@ -87,6 +113,7 @@ export declare const browserInstanceConfigSchema: z.ZodEffects<z.ZodObject<{
87
113
  sessionsDir: z.ZodOptional<z.ZodString>;
88
114
  args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
89
115
  profileName: z.ZodOptional<z.ZodString>;
116
+ profileColor: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
90
117
  windowBounds: z.ZodOptional<z.ZodObject<{
91
118
  x: z.ZodOptional<z.ZodNumber>;
92
119
  y: z.ZodOptional<z.ZodNumber>;
@@ -117,6 +144,7 @@ export declare const browserInstanceConfigSchema: z.ZodEffects<z.ZodObject<{
117
144
  sessionsDir?: string | undefined;
118
145
  args?: string[] | undefined;
119
146
  profileName?: string | undefined;
147
+ profileColor?: string | undefined;
120
148
  windowBounds?: {
121
149
  x?: number | undefined;
122
150
  y?: number | undefined;
@@ -137,6 +165,7 @@ export declare const browserInstanceConfigSchema: z.ZodEffects<z.ZodObject<{
137
165
  sessionsDir?: string | undefined;
138
166
  args?: string[] | undefined;
139
167
  profileName?: string | undefined;
168
+ profileColor?: string | undefined;
140
169
  windowBounds?: {
141
170
  x?: number | undefined;
142
171
  y?: number | undefined;
@@ -157,6 +186,7 @@ export declare const browserInstanceConfigSchema: z.ZodEffects<z.ZodObject<{
157
186
  sessionsDir?: string | undefined;
158
187
  args?: string[] | undefined;
159
188
  profileName?: string | undefined;
189
+ profileColor?: string | undefined;
160
190
  windowBounds?: {
161
191
  x?: number | undefined;
162
192
  y?: number | undefined;
@@ -177,6 +207,7 @@ export declare const browserInstanceConfigSchema: z.ZodEffects<z.ZodObject<{
177
207
  sessionsDir?: string | undefined;
178
208
  args?: string[] | undefined;
179
209
  profileName?: string | undefined;
210
+ profileColor?: string | undefined;
180
211
  windowBounds?: {
181
212
  x?: number | undefined;
182
213
  y?: number | undefined;
@@ -200,6 +231,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
200
231
  sessionsDir: z.ZodOptional<z.ZodString>;
201
232
  args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
202
233
  profileName: z.ZodOptional<z.ZodString>;
234
+ profileColor: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
203
235
  windowBounds: z.ZodOptional<z.ZodObject<{
204
236
  x: z.ZodOptional<z.ZodNumber>;
205
237
  y: z.ZodOptional<z.ZodNumber>;
@@ -230,6 +262,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
230
262
  sessionsDir?: string | undefined;
231
263
  args?: string[] | undefined;
232
264
  profileName?: string | undefined;
265
+ profileColor?: string | undefined;
233
266
  windowBounds?: {
234
267
  x?: number | undefined;
235
268
  y?: number | undefined;
@@ -250,6 +283,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
250
283
  sessionsDir?: string | undefined;
251
284
  args?: string[] | undefined;
252
285
  profileName?: string | undefined;
286
+ profileColor?: string | undefined;
253
287
  windowBounds?: {
254
288
  x?: number | undefined;
255
289
  y?: number | undefined;
@@ -270,6 +304,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
270
304
  sessionsDir?: string | undefined;
271
305
  args?: string[] | undefined;
272
306
  profileName?: string | undefined;
307
+ profileColor?: string | undefined;
273
308
  windowBounds?: {
274
309
  x?: number | undefined;
275
310
  y?: number | undefined;
@@ -290,6 +325,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
290
325
  sessionsDir?: string | undefined;
291
326
  args?: string[] | undefined;
292
327
  profileName?: string | undefined;
328
+ profileColor?: string | undefined;
293
329
  windowBounds?: {
294
330
  x?: number | undefined;
295
331
  y?: number | undefined;
@@ -312,6 +348,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
312
348
  sessionsDir?: string | undefined;
313
349
  args?: string[] | undefined;
314
350
  profileName?: string | undefined;
351
+ profileColor?: string | undefined;
315
352
  windowBounds?: {
316
353
  x?: number | undefined;
317
354
  y?: number | undefined;
@@ -336,6 +373,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
336
373
  sessionsDir?: string | undefined;
337
374
  args?: string[] | undefined;
338
375
  profileName?: string | undefined;
376
+ profileColor?: string | undefined;
339
377
  windowBounds?: {
340
378
  x?: number | undefined;
341
379
  y?: number | undefined;
@@ -358,6 +396,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
358
396
  sessionsDir?: string | undefined;
359
397
  args?: string[] | undefined;
360
398
  profileName?: string | undefined;
399
+ profileColor?: string | undefined;
361
400
  windowBounds?: {
362
401
  x?: number | undefined;
363
402
  y?: number | undefined;
@@ -382,6 +421,7 @@ export declare const browserConfigSchema: z.ZodEffects<z.ZodObject<{
382
421
  sessionsDir?: string | undefined;
383
422
  args?: string[] | undefined;
384
423
  profileName?: string | undefined;
424
+ profileColor?: string | undefined;
385
425
  windowBounds?: {
386
426
  x?: number | undefined;
387
427
  y?: number | undefined;
@@ -441,6 +481,26 @@ export declare const rollConfigSchema: z.ZodObject<{
441
481
  dataDir: string;
442
482
  env?: Record<string, Record<string, string>> | undefined;
443
483
  }>;
484
+ install: z.ZodDefault<z.ZodObject<{
485
+ /** 显式 opt-in 的 npm registry(如国内镜像)。未配置时使用 npm 默认源。 */
486
+ registry: z.ZodOptional<z.ZodString>;
487
+ /** 透传给 npm 的 `--fetch-retries`,并用于 roll 层整体重试次数。 */
488
+ fetchRetries: z.ZodDefault<z.ZodNumber>;
489
+ /** 安装时附加 `--prefer-offline`,默认关闭以避免更新时复用过期 npm 元数据。 */
490
+ preferOffline: z.ZodDefault<z.ZodBoolean>;
491
+ /** 单次 npm 安装命令的超时(毫秒)。 */
492
+ networkTimeoutMs: z.ZodDefault<z.ZodNumber>;
493
+ }, "strip", z.ZodTypeAny, {
494
+ fetchRetries: number;
495
+ preferOffline: boolean;
496
+ networkTimeoutMs: number;
497
+ registry?: string | undefined;
498
+ }, {
499
+ registry?: string | undefined;
500
+ fetchRetries?: number | undefined;
501
+ preferOffline?: boolean | undefined;
502
+ networkTimeoutMs?: number | undefined;
503
+ }>>;
444
504
  browser: z.ZodDefault<z.ZodEffects<z.ZodObject<{
445
505
  defaultInstance: z.ZodOptional<z.ZodString>;
446
506
  instances: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodObject<{
@@ -456,6 +516,7 @@ export declare const rollConfigSchema: z.ZodObject<{
456
516
  sessionsDir: z.ZodOptional<z.ZodString>;
457
517
  args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
458
518
  profileName: z.ZodOptional<z.ZodString>;
519
+ profileColor: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
459
520
  windowBounds: z.ZodOptional<z.ZodObject<{
460
521
  x: z.ZodOptional<z.ZodNumber>;
461
522
  y: z.ZodOptional<z.ZodNumber>;
@@ -486,6 +547,7 @@ export declare const rollConfigSchema: z.ZodObject<{
486
547
  sessionsDir?: string | undefined;
487
548
  args?: string[] | undefined;
488
549
  profileName?: string | undefined;
550
+ profileColor?: string | undefined;
489
551
  windowBounds?: {
490
552
  x?: number | undefined;
491
553
  y?: number | undefined;
@@ -506,6 +568,7 @@ export declare const rollConfigSchema: z.ZodObject<{
506
568
  sessionsDir?: string | undefined;
507
569
  args?: string[] | undefined;
508
570
  profileName?: string | undefined;
571
+ profileColor?: string | undefined;
509
572
  windowBounds?: {
510
573
  x?: number | undefined;
511
574
  y?: number | undefined;
@@ -526,6 +589,7 @@ export declare const rollConfigSchema: z.ZodObject<{
526
589
  sessionsDir?: string | undefined;
527
590
  args?: string[] | undefined;
528
591
  profileName?: string | undefined;
592
+ profileColor?: string | undefined;
529
593
  windowBounds?: {
530
594
  x?: number | undefined;
531
595
  y?: number | undefined;
@@ -546,6 +610,7 @@ export declare const rollConfigSchema: z.ZodObject<{
546
610
  sessionsDir?: string | undefined;
547
611
  args?: string[] | undefined;
548
612
  profileName?: string | undefined;
613
+ profileColor?: string | undefined;
549
614
  windowBounds?: {
550
615
  x?: number | undefined;
551
616
  y?: number | undefined;
@@ -568,6 +633,7 @@ export declare const rollConfigSchema: z.ZodObject<{
568
633
  sessionsDir?: string | undefined;
569
634
  args?: string[] | undefined;
570
635
  profileName?: string | undefined;
636
+ profileColor?: string | undefined;
571
637
  windowBounds?: {
572
638
  x?: number | undefined;
573
639
  y?: number | undefined;
@@ -592,6 +658,7 @@ export declare const rollConfigSchema: z.ZodObject<{
592
658
  sessionsDir?: string | undefined;
593
659
  args?: string[] | undefined;
594
660
  profileName?: string | undefined;
661
+ profileColor?: string | undefined;
595
662
  windowBounds?: {
596
663
  x?: number | undefined;
597
664
  y?: number | undefined;
@@ -614,6 +681,7 @@ export declare const rollConfigSchema: z.ZodObject<{
614
681
  sessionsDir?: string | undefined;
615
682
  args?: string[] | undefined;
616
683
  profileName?: string | undefined;
684
+ profileColor?: string | undefined;
617
685
  windowBounds?: {
618
686
  x?: number | undefined;
619
687
  y?: number | undefined;
@@ -638,6 +706,7 @@ export declare const rollConfigSchema: z.ZodObject<{
638
706
  sessionsDir?: string | undefined;
639
707
  args?: string[] | undefined;
640
708
  profileName?: string | undefined;
709
+ profileColor?: string | undefined;
641
710
  windowBounds?: {
642
711
  x?: number | undefined;
643
712
  y?: number | undefined;
@@ -648,6 +717,12 @@ export declare const rollConfigSchema: z.ZodObject<{
648
717
  }> | undefined;
649
718
  }>>;
650
719
  }, "strip", z.ZodTypeAny, {
720
+ install: {
721
+ fetchRetries: number;
722
+ preferOffline: boolean;
723
+ networkTimeoutMs: number;
724
+ registry?: string | undefined;
725
+ };
651
726
  ask: {
652
727
  llmModel?: string | undefined;
653
728
  confirmThreshold?: number | undefined;
@@ -666,6 +741,7 @@ export declare const rollConfigSchema: z.ZodObject<{
666
741
  sessionsDir?: string | undefined;
667
742
  args?: string[] | undefined;
668
743
  profileName?: string | undefined;
744
+ profileColor?: string | undefined;
669
745
  windowBounds?: {
670
746
  x?: number | undefined;
671
747
  y?: number | undefined;
@@ -705,6 +781,12 @@ export declare const rollConfigSchema: z.ZodObject<{
705
781
  dataDir: string;
706
782
  env?: Record<string, Record<string, string>> | undefined;
707
783
  };
784
+ install?: {
785
+ registry?: string | undefined;
786
+ fetchRetries?: number | undefined;
787
+ preferOffline?: boolean | undefined;
788
+ networkTimeoutMs?: number | undefined;
789
+ } | undefined;
708
790
  browser?: {
709
791
  defaultInstance?: string | undefined;
710
792
  instances?: Record<string, {
@@ -720,6 +802,7 @@ export declare const rollConfigSchema: z.ZodObject<{
720
802
  sessionsDir?: string | undefined;
721
803
  args?: string[] | undefined;
722
804
  profileName?: string | undefined;
805
+ profileColor?: string | undefined;
723
806
  windowBounds?: {
724
807
  x?: number | undefined;
725
808
  y?: number | undefined;
@@ -732,3 +815,4 @@ export declare const rollConfigSchema: z.ZodObject<{
732
815
  }>;
733
816
  export type RollConfig = z.infer<typeof rollConfigSchema>;
734
817
  export type BrowserConfig = z.infer<typeof browserConfigSchema>;
818
+ export type InstallConfig = z.infer<typeof installConfigSchema>;
@@ -1 +1 @@
1
- import{z as e}from"zod";const o=["zhipin","yupao"],t=["managed-cdp","remote-cdp","existing-session"],n=["chrome","chromium","msedge"];export const browserWindowBoundsSchema=e.object({x:e.number().int().optional(),y:e.number().int().optional(),width:e.number().int().positive().optional(),height:e.number().int().positive().optional()});export const providerConfigSchema=e.object({apiKey:e.string(),baseUrl:e.string().optional()});export const llmConfigSchema=e.object({defaultProvider:e.string(),defaultModel:e.string(),providers:e.record(e.string(),providerConfigSchema)});export const askConfigSchema=e.object({llmModel:e.string().optional(),confirmThreshold:e.number().optional()});export const agentsConfigSchema=e.object({dataDir:e.string(),env:e.record(e.string(),e.record(e.string(),e.string())).optional()});export const browserInstanceConfigSchema=e.object({platform:e.enum(o).optional(),mode:e.enum(t).default("managed-cdp"),headless:e.boolean().optional(),cdpUrl:e.string().optional(),cdpHost:e.string().default("127.0.0.1"),cdpPort:e.number().int().min(1).max(65535).optional(),channel:e.enum(n).default("chrome"),executablePath:e.string().optional(),userDataDir:e.string().trim().min(1),sessionsDir:e.string().trim().min(1).optional(),args:e.array(e.string()).optional(),profileName:e.string().trim().min(1).optional(),windowBounds:browserWindowBoundsSchema.optional(),trackingAgentId:e.string().trim().min(1).optional()}).superRefine((o,t)=>{"managed-cdp"===o.mode&&void 0===o.cdpPort&&t.addIssue({code:e.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==o.mode&&"existing-session"!==o.mode||void 0!==o.cdpUrl||t.addIssue({code:e.ZodIssueCode.custom,path:["cdpUrl"],message:`${o.mode} browser instance requires cdpUrl`})});export const browserConfigSchema=e.object({defaultInstance:e.string().trim().min(1).optional(),instances:e.record(e.string(),browserInstanceConfigSchema).default({})}).superRefine((o,t)=>{const n=Object.entries(o.instances);void 0!==o.defaultInstance&&void 0===o.instances[o.defaultInstance]&&t.addIssue({code:e.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${o.defaultInstance}" is not declared in browser.instances`});const s=new Map,r=new Map;for(const[o,i]of n){if(void 0!==i.cdpPort){const n=s.get(i.cdpPort);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"cdpPort"],message:`cdpPort ${String(i.cdpPort)} is already used by browser instance "${n}"`}):s.set(i.cdpPort,o)}const n=r.get(i.userDataDir);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"userDataDir"],message:`userDataDir is already used by browser instance "${n}"`}):r.set(i.userDataDir,o)}});export const rollConfigSchema=e.object({llm:llmConfigSchema,ask:askConfigSchema,agents:agentsConfigSchema,browser:browserConfigSchema.default({})});
1
+ import{z as e}from"zod";const o=["zhipin","yupao"],t=["managed-cdp","remote-cdp","existing-session"],n=["chrome","chromium","msedge"],s=e.string().trim().regex(/^#[\da-fA-F]{6}$/,"profileColor must be a hex RGB color such as #2563EB").transform(e=>e.toUpperCase());export const browserWindowBoundsSchema=e.object({x:e.number().int().optional(),y:e.number().int().optional(),width:e.number().int().positive().optional(),height:e.number().int().positive().optional()});export const providerConfigSchema=e.object({apiKey:e.string(),baseUrl:e.string().optional()});export const llmConfigSchema=e.object({defaultProvider:e.string(),defaultModel:e.string(),providers:e.record(e.string(),providerConfigSchema)});export const askConfigSchema=e.object({llmModel:e.string().optional(),confirmThreshold:e.number().optional()});export const agentsConfigSchema=e.object({dataDir:e.string(),env:e.record(e.string(),e.record(e.string(),e.string())).optional()});export const installConfigSchema=e.object({registry:e.string().trim().url().optional(),fetchRetries:e.number().int().min(0).max(10).default(3),preferOffline:e.boolean().default(!1),networkTimeoutMs:e.number().int().min(1e4).default(12e4)});export const browserInstanceConfigSchema=e.object({platform:e.enum(o).optional(),mode:e.enum(t).default("managed-cdp"),headless:e.boolean().optional(),cdpUrl:e.string().optional(),cdpHost:e.string().default("127.0.0.1"),cdpPort:e.number().int().min(1).max(65535).optional(),channel:e.enum(n).default("chrome"),executablePath:e.string().optional(),userDataDir:e.string().trim().min(1),sessionsDir:e.string().trim().min(1).optional(),args:e.array(e.string()).optional(),profileName:e.string().trim().min(1).optional(),profileColor:s.optional(),windowBounds:browserWindowBoundsSchema.optional(),trackingAgentId:e.string().trim().min(1).optional()}).superRefine((o,t)=>{"managed-cdp"===o.mode&&void 0===o.cdpPort&&t.addIssue({code:e.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==o.mode&&"existing-session"!==o.mode||void 0!==o.cdpUrl||t.addIssue({code:e.ZodIssueCode.custom,path:["cdpUrl"],message:`${o.mode} browser instance requires cdpUrl`})});export const browserConfigSchema=e.object({defaultInstance:e.string().trim().min(1).optional(),instances:e.record(e.string(),browserInstanceConfigSchema).default({})}).superRefine((o,t)=>{const n=Object.entries(o.instances);void 0!==o.defaultInstance&&void 0===o.instances[o.defaultInstance]&&t.addIssue({code:e.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${o.defaultInstance}" is not declared in browser.instances`});const s=new Map,r=new Map;for(const[o,i]of n){if(void 0!==i.cdpPort){const n=s.get(i.cdpPort);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"cdpPort"],message:`cdpPort ${String(i.cdpPort)} is already used by browser instance "${n}"`}):s.set(i.cdpPort,o)}const n=r.get(i.userDataDir);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"userDataDir"],message:`userDataDir is already used by browser instance "${n}"`}):r.set(i.userDataDir,o)}});export const rollConfigSchema=e.object({llm:llmConfigSchema,ask:askConfigSchema,agents:agentsConfigSchema,install:installConfigSchema.default({}),browser:browserConfigSchema.default({})});
@@ -1 +1 @@
1
- import{spawn as t}from"node:child_process";import{closeSync as e,existsSync as n,mkdirSync as r,openSync as i,readFileSync as o,unlinkSync as s,writeFileSync as a}from"node:fs";import{resolve as c,dirname as l}from"node:path";import{McpClientManager as p}from"../mcp/client-manager.js";import{resolveDevSpawnSpec as d}from"./dev-spawn.js";import{inferAgentSourceType as m}from"./source.js";const u=1,g="unknown";function f(t,e){return c(t,"pids",`${e}.pid`)}function h(t,e){return c(t,"pids",`${e}.runtime.json`)}function w(t,e){for(const r of[f(t,e),h(t,e)])n(r)&&s(r)}export function cleanupOrphanAgentRuntimeMetadata(t,e){return void 0===getAgentPid(t,e)&&(w(t,e),!0)}export function getAgentLogPath(t,e){return c(t,"logs",`${e}.log`)}function $(t){try{return process.kill(t,0),!0}catch{return!1}}export function getAgentPid(t,e){const r=f(t,e);if(!n(r))return;const i=Number(o(r,"utf-8").trim());if(!Number.isNaN(i)&&$(i))return i;w(t,e)}export function getRollCoreVersion(){try{const t=c(import.meta.dirname,"../../package.json"),e=JSON.parse(o(t,"utf-8"));return A(e)&&"string"==typeof e.version?e.version:g}catch{return g}}export function writeAgentRuntimeSidecar(t,e,i){const o=h(e,t.skill.name),s=l(o);n(s)||r(s,{recursive:!0});const c={schemaVersion:1,agentName:t.skill.name,pid:i,coreVersion:getRollCoreVersion(),startedAt:(new Date).toISOString(),..."streamable-http"===t.transport.type?{endpoint:t.transport.endpoint}:{}};a(o,`${JSON.stringify(c,null,2)}\n`,"utf-8")}export function inspectManagedAgentRuntime(t,e){const n=getAgentPid(e,t.skill.name),r=getRollCoreVersion(),i="streamable-http"===t.transport.type?t.transport.endpoint:void 0;if(void 0===n){const n=v(e,t.skill.name);return void 0!==n?{..."invalid"!==n?{sidecar:n}:{},expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:[{code:"orphan-sidecar",message:"invalid"===n?"runtime sidecar 存在但没有活动 PID,且无法解析":`runtime sidecar 记录 PID ${String(n.pid)},但没有活动 PID`,fix:`运行 \`roll doctor --fix\` 清理 ${t.skill.name} 的过期 runtime 元数据`}]}:{expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:[]}}const o=[],s=v(e,t.skill.name);return"invalid"===s?(o.push({code:"invalid-sidecar",message:"runtime sidecar 无法解析",fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o}):s?(s.pid!==n&&o.push({code:"pid-mismatch",message:`runtime sidecar PID ${String(s.pid)} 与活动 PID ${String(n)} 不一致`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),s.coreVersion!==r&&o.push({code:"version-mismatch",message:`runtime sidecar 来自 core ${s.coreVersion},当前 core 是 ${r}`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),i&&s.endpoint!==i&&o.push({code:"endpoint-mismatch",message:`runtime sidecar endpoint 是 ${s.endpoint??"n/a"},当前配置是 ${i}`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,sidecar:s,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o}):(o.push({code:"missing-sidecar",message:"进程存在但缺少 runtime sidecar",fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o})}export async function probeAgentEndpoint(t,e={}){const n=new p;try{const r=await n.connect(t.skill.name,t.transport,t.installPath,void 0!==e.timeoutMs?{timeoutMs:e.timeoutMs}:{});await r.listTools()}finally{await n.disconnectAll()}}export async function waitForAgentReady(t,e={}){const n=e.startupTimeoutMs??15e3,r=e.probeTimeoutMs??2e3,i=e.intervalMs??500,o=Date.now()+n;let s;for(;Date.now()<o;)try{return void await probeAgentEndpoint(t,{timeoutMs:r})}catch(t){s=t,await y(i)}throw new Error(`Agent "${t.skill.name}" did not become ready within ${n}ms${s?`: ${s instanceof Error?s.message:String(s)}`:""}`)}export function startAgent(o,s,c){if("streamable-http"===o.transport.type&&"core-managed"!==o.runtime.ownership)throw new Error(`Agent "${o.skill.name}" 使用 streamable-http 传输且非 core-managed,请手动启动服务。\n 端点: ${o.transport.endpoint}`);const p=getAgentPid(s,o.skill.name);if(void 0!==p)throw new Error(`Agent "${o.skill.name}" 已在运行 (PID: ${String(p)})`);const d=k(o),m=getAgentLogPath(s,o.skill.name),u=l(m);n(u)||r(u,{recursive:!0});const g=i(m,"a"),h=t(d.command,[...d.args??[]],{cwd:o.installPath,detached:!0,stdio:["ignore",g,g],...c?{env:{...process.env,...c}}:{}});if(e(g),!h.pid)throw new Error(`Failed to start agent "${o.skill.name}"`);const $=f(s,o.skill.name),y=l($);n(y)||r(y,{recursive:!0});try{a($,String(h.pid),"utf-8"),writeAgentRuntimeSidecar(o,s,h.pid)}catch(t){try{process.kill(h.pid,"SIGTERM")}catch{}throw w(s,o.skill.name),new Error(`Failed to persist runtime metadata for agent "${o.skill.name}"`,{cause:t})}return h.unref(),h.pid}export function stopAgent(t,e){const n=getAgentPid(t,e);if(void 0===n)return!1;try{process.kill(n,"SIGTERM")}catch{}return w(t,e),!0}export async function stopAgentGracefully(t,e,n={}){const r=getAgentPid(t,e);if(void 0===r)return!1;try{process.kill(r,"SIGTERM")}catch{return w(t,e),!0}const i=n.timeoutMs??15e3,o=n.intervalMs??200,s=Date.now()+i;for(;Date.now()<s;){if(!$(r))return w(t,e),!0;await y(o)}throw new Error(`Agent "${e}" did not stop within ${i}ms`)}function k(t){if("stdio"===t.transport.type){const e=d(t.transport.command,t.transport.args,t.installPath,m(t));return e||{command:t.transport.command,...t.transport.args?{args:t.transport.args}:{}}}if("core-managed"===t.runtime.ownership){const e=d(t.runtime.start.command,t.runtime.start.args,t.installPath,m(t));return e||{command:t.runtime.start.command,...t.runtime.start.args?{args:t.runtime.start.args}:{}}}throw new Error(`Agent "${t.skill.name}" does not have a managed runtime start command`)}function y(t){return new Promise(e=>{setTimeout(e,t)})}function v(t,e){const r=h(t,e);if(!n(r))return;let i;try{i=JSON.parse(o(r,"utf-8"))}catch{return"invalid"}return x(i)?i:"invalid"}function x(t){return!!A(t)&&(1===t.schemaVersion&&(!("string"!=typeof t.agentName||"number"!=typeof t.pid||!Number.isInteger(t.pid)||"string"!=typeof t.coreVersion||"string"!=typeof t.startedAt)&&(void 0===t.endpoint||"string"==typeof t.endpoint)))}function A(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
1
+ import{spawn as t}from"node:child_process";import{closeSync as e,existsSync as n,mkdirSync as r,openSync as i,readFileSync as o,unlinkSync as s,writeFileSync as a}from"node:fs";import{resolve as c,dirname as l}from"node:path";import{McpClientManager as m}from"../mcp/client-manager.js";import{resolveDevSpawnSpec as p}from"./dev-spawn.js";import{inferAgentSourceType as d}from"./source.js";const u=1,g="unknown",f=15e3,h=2e3,w=500;function A(t,e){return c(t,"pids",`${e}.pid`)}function $(t,e){return c(t,"pids",`${e}.runtime.json`)}function k(t,e){for(const r of[A(t,e),$(t,e)])n(r)&&s(r)}export function cleanupOrphanAgentRuntimeMetadata(t,e){return void 0===getAgentPid(t,e)&&(k(t,e),!0)}export function getAgentLogPath(t,e){return c(t,"logs",`${e}.log`)}function v(t){try{return process.kill(t,0),!0}catch{return!1}}export function getAgentPid(t,e){const r=A(t,e);if(!n(r))return;const i=Number(o(r,"utf-8").trim());if(!Number.isNaN(i)&&v(i))return i;k(t,e)}export function getRollCoreVersion(){try{const t=c(import.meta.dirname,"../../package.json"),e=JSON.parse(o(t,"utf-8"));return R(e)&&"string"==typeof e.version?e.version:g}catch{return g}}export function writeAgentRuntimeSidecar(t,e,i){const o=$(e,t.skill.name),s=l(o);n(s)||r(s,{recursive:!0});const c={schemaVersion:1,agentName:t.skill.name,pid:i,coreVersion:getRollCoreVersion(),startedAt:(new Date).toISOString(),..."streamable-http"===t.transport.type?{endpoint:t.transport.endpoint}:{}};a(o,`${JSON.stringify(c,null,2)}\n`,"utf-8")}export function inspectManagedAgentRuntime(t,e){const n=getAgentPid(e,t.skill.name),r=getRollCoreVersion(),i="streamable-http"===t.transport.type?t.transport.endpoint:void 0;if(void 0===n){const n=P(e,t.skill.name);return void 0!==n?{..."invalid"!==n?{sidecar:n}:{},expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:[{code:"orphan-sidecar",message:"invalid"===n?"runtime sidecar 存在但没有活动 PID,且无法解析":`runtime sidecar 记录 PID ${String(n.pid)},但没有活动 PID`,fix:`运行 \`roll doctor --fix\` 清理 ${t.skill.name} 的过期 runtime 元数据`}]}:{expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:[]}}const o=[],s=P(e,t.skill.name);return"invalid"===s?(o.push({code:"invalid-sidecar",message:"runtime sidecar 无法解析",fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o}):s?(s.pid!==n&&o.push({code:"pid-mismatch",message:`runtime sidecar PID ${String(s.pid)} 与活动 PID ${String(n)} 不一致`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),s.coreVersion!==r&&o.push({code:"version-mismatch",message:`runtime sidecar 来自 core ${s.coreVersion},当前 core 是 ${r}`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),i&&s.endpoint!==i&&o.push({code:"endpoint-mismatch",message:`runtime sidecar endpoint 是 ${s.endpoint??"n/a"},当前配置是 ${i}`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,sidecar:s,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o}):(o.push({code:"missing-sidecar",message:"进程存在但缺少 runtime sidecar",fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o})}export async function probeAgentEndpoint(t,e={}){const n=new m;try{const r=await n.connect(t.skill.name,t.transport,t.installPath,void 0!==e.timeoutMs?{timeoutMs:e.timeoutMs}:{});await r.listTools()}finally{await n.disconnectAll()}}function y(t){const e=process.env[t];if(void 0===e||0===e.trim().length)return;const n=Number.parseInt(e,10);return!Number.isInteger(n)||n<=0||String(n)!==e.trim()?void 0:n}export async function waitForAgentReady(t,e={}){const n=y("ROLL_AGENT_READY_STARTUP_TIMEOUT_MS")??e.startupTimeoutMs??f,r=y("ROLL_AGENT_READY_PROBE_TIMEOUT_MS")??e.probeTimeoutMs??h,i=y("ROLL_AGENT_READY_INTERVAL_MS")??e.intervalMs??w,o=Date.now()+n;let s;for(;Date.now()<o;)try{return void await probeAgentEndpoint(t,{timeoutMs:r})}catch(t){s=t,await E(i)}throw new Error(`Agent "${t.skill.name}" did not become ready within ${n}ms${s?`: ${s instanceof Error?s.message:String(s)}`:""}`)}export function startAgent(o,s,c){if("streamable-http"===o.transport.type&&"core-managed"!==o.runtime.ownership)throw new Error(`Agent "${o.skill.name}" 使用 streamable-http 传输且非 core-managed,请手动启动服务。\n 端点: ${o.transport.endpoint}`);const m=getAgentPid(s,o.skill.name);if(void 0!==m)throw new Error(`Agent "${o.skill.name}" 已在运行 (PID: ${String(m)})`);const p=x(o),d=getAgentLogPath(s,o.skill.name),u=l(d);n(u)||r(u,{recursive:!0});const g=i(d,"a"),f=t(p.command,[...p.args??[]],{cwd:o.installPath,detached:!0,stdio:["ignore",g,g],...c?{env:{...process.env,...c}}:{}});if(e(g),!f.pid)throw new Error(`Failed to start agent "${o.skill.name}"`);const h=A(s,o.skill.name),w=l(h);n(w)||r(w,{recursive:!0});try{a(h,String(f.pid),"utf-8"),writeAgentRuntimeSidecar(o,s,f.pid)}catch(t){try{process.kill(f.pid,"SIGTERM")}catch{}throw k(s,o.skill.name),new Error(`Failed to persist runtime metadata for agent "${o.skill.name}"`,{cause:t})}return f.unref(),f.pid}export function stopAgent(t,e){const n=getAgentPid(t,e);if(void 0===n)return!1;try{process.kill(n,"SIGTERM")}catch{}return k(t,e),!0}export async function stopAgentGracefully(t,e,n={}){const r=getAgentPid(t,e);if(void 0===r)return!1;try{process.kill(r,"SIGTERM")}catch{return k(t,e),!0}const i=n.timeoutMs??15e3,o=n.intervalMs??200,s=Date.now()+i;for(;Date.now()<s;){if(!v(r))return k(t,e),!0;await E(o)}throw new Error(`Agent "${e}" did not stop within ${i}ms`)}function x(t){if("stdio"===t.transport.type){const e=p(t.transport.command,t.transport.args,t.installPath,d(t));return e||{command:t.transport.command,...t.transport.args?{args:t.transport.args}:{}}}if("core-managed"===t.runtime.ownership){const e=p(t.runtime.start.command,t.runtime.start.args,t.installPath,d(t));return e||{command:t.runtime.start.command,...t.runtime.start.args?{args:t.runtime.start.args}:{}}}throw new Error(`Agent "${t.skill.name}" does not have a managed runtime start command`)}function E(t){return new Promise(e=>{setTimeout(e,t)})}function P(t,e){const r=$(t,e);if(!n(r))return;let i;try{i=JSON.parse(o(r,"utf-8"))}catch{return"invalid"}return S(i)?i:"invalid"}function S(t){return!!R(t)&&(1===t.schemaVersion&&(!("string"!=typeof t.agentName||"number"!=typeof t.pid||!Number.isInteger(t.pid)||"string"!=typeof t.coreVersion||"string"!=typeof t.startedAt)&&(void 0===t.endpoint||"string"==typeof t.endpoint)))}function R(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
@@ -1 +1 @@
1
- import{execFile as e}from"node:child_process";import{existsSync as r}from"node:fs";import{createRequire as t}from"node:module";import{resolve as o}from"node:path";import{promisify as s}from"node:util";const n=s(e);export async function runAgentSetup(e,r={}){const t="core-managed"===e.runtime.ownership?e.runtime.setup?.playwright?.browsers:void 0;if(!t||0===t.length)return{ok:!0,skipped:!0,message:"该 Agent 无额外 setup 步骤"};if(r.skipBrowserSetup)return{ok:!0,skipped:!0,message:`已跳过浏览器运行时安装 (${t.join(", ")})`};const o=i(e.installPath);if(!o)return{ok:!1,skipped:!1,message:"未找到 playwright-core CLI,无法安装浏览器运行时"};try{return await n(process.execPath,[o,"install",...t],{cwd:e.installPath,timeout:3e5}),{ok:!0,skipped:!1,message:`浏览器运行时安装完成 (${t.join(", ")})`,retryCommand:`${process.execPath} ${o} install ${t.join(" ")}`}}catch(e){return{ok:!1,skipped:!1,message:e instanceof Error?e.message:String(e),retryCommand:`${process.execPath} ${o} install ${t.join(" ")}`}}}function i(e){const s=o(e,"package.json");if(r(s))try{return t(s).resolve("playwright-core/cli.js")}catch{return}}
1
+ import{execFile as e}from"node:child_process";import{existsSync as r}from"node:fs";import{createRequire as t}from"node:module";import{resolve as o}from"node:path";import{promisify as s}from"node:util";const n=s(e);export async function runAgentSetup(e,r={}){const t="core-managed"===e.runtime.ownership?e.runtime.setup?.playwright?.browsers:void 0;if(!t||0===t.length)return{ok:!0,skipped:!0,message:"该 Agent 无额外 setup 步骤"};if(r.skipBrowserSetup)return{ok:!0,skipped:!0,message:`已跳过浏览器运行时安装 (${t.join(", ")})`};const o=i(e.installPath);if(!o)return{ok:!1,skipped:!1,message:"未找到 playwright-core CLI,无法安装浏览器运行时"};try{return await n(process.execPath,[o,"install",...t],{cwd:e.installPath,timeout:3e5,maxBuffer:67108864}),{ok:!0,skipped:!1,message:`浏览器运行时安装完成 (${t.join(", ")})`,retryCommand:`${process.execPath} ${o} install ${t.join(" ")}`}}catch(e){return{ok:!1,skipped:!1,message:e instanceof Error?e.message:String(e),retryCommand:`${process.execPath} ${o} install ${t.join(" ")}`}}}function i(e){const s=o(e,"package.json");if(r(s))try{return t(s).resolve("playwright-core/cli.js")}catch{return}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/core",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,6 +35,7 @@
35
35
  "@ai-sdk/deepseek": "^2.0.29",
36
36
  "@ai-sdk/openai": "^3.0.52",
37
37
  "@ai-sdk/provider": "^3.0.8",
38
+ "@clack/prompts": "1.4.0",
38
39
  "@modelcontextprotocol/sdk": "^1.12.0",
39
40
  "ai": "^6.0.154",
40
41
  "chalk": "^5.4.0",