@roll-agent/core 0.6.5 → 0.6.7

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.
@@ -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 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:"注册一个 Agent(本地路径、Git URL 或远程 endpoint"},args:{path:{type:"positional",description:"Agent 本地路径或 Git URL",required:!1},remote:{type:"string",description:"远程 MCP endpoint(需配合 --name/--description"},name:{type:"string",description:"远程 Agent 名称"},description:{type:"string",description:"远程 Agent 描述"}},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 k=t(n,"package.json"),A="1"===process.env.ROLL_SKIP_INSTALL;if(e.remote)l.info("远程 Agent 使用本地 manifest,无需安装依赖。");else if(r(k)&&!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(k)&&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),S={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(S),l.success(`Agent "${s.skill.name}" 注册成功`),L(s.skill.name,s.skill.env,o.env)}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function L(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(", ")}`),void l.info(`请在 roll.config.yaml 的 agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(l.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),l.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 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 +1 @@
1
- import{defineCommand as t}from"citty";import{loadAgentsConfig as e}from"../../config/loader.js";import{getAgentLogPath as r,getAgentPid as a,probeAgentEndpoint as n}from"../../registry/process-manager.js";import{AgentStore as s}from"../../registry/store.js";import{log as o}from"../utils/output.js";export default t({meta:{description:"检查 Agent 健康状态(兼容 on-demand / core-managed / external-managed)"},args:{restart:{type:"boolean",description:"兼容旧参数,stdio 按需模式下不生效",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:t}){const{agentsConfig:r}=e(),a=new s(r.dataDir),n=a.list();if(t.restart&&o.warn("`--restart` 仅为兼容保留参数;v1 不执行自动重启逻辑。"),0===n.length)return t.json?void console.log("[]"):void o.info("暂无已注册 Agent。");const l=[];for(const t of n)l.push(await i(t,a,r.dataDir));const p=l.filter(t=>!t.healthy);if(t.json)return console.log(JSON.stringify(l,null,2)),void(p.length>0&&(process.exitCode=1));for(const t of l)t.healthy?o.success(`${t.agentName} [${t.transport}]: ${t.message}`):o.error(`${t.agentName} [${t.transport}]: ${t.message}`);p.length>0&&(process.exitCode=1)}});async function i(t,e,r){switch(t.runtime.ownership){case"on-demand":return{agentName:t.skill.name,transport:t.transport.type,healthy:!0,message:"按需模式:无需常驻进程,由 run/ask 在调用时启动"};case"external-managed":return l(t,e);case"core-managed":return p(t,e,r)}}async function l(t,e){try{return await n(t,{timeoutMs:5e3}),e.updateStatus(t.skill.name,"online"),{agentName:t.skill.name,transport:t.transport.type,healthy:!0,message:"streamable-http"===t.transport.type?`外部服务可连接 (${t.transport.endpoint})`:"外部服务可连接"}}catch(r){return e.updateStatus(t.skill.name,"error"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:"streamable-http"===t.transport.type?`外部服务不可连接 (${t.transport.endpoint}): ${r instanceof Error?r.message:String(r)}`:`外部服务不可连接: ${r instanceof Error?r.message:String(r)}`}}}async function p(t,e,s){const o=a(s,t.skill.name);if(void 0===o)return e.updateStatus(t.skill.name,"stopped"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:`未运行(缺少活动 PID)。日志: ${r(s,t.skill.name)}`};try{return await n(t,{timeoutMs:5e3}),e.updateStatus(t.skill.name,"online"),{agentName:t.skill.name,transport:t.transport.type,healthy:!0,message:"streamable-http"===t.transport.type?`运行中 (PID: ${String(o)}),可连接 (${t.transport.endpoint})`:`运行中 (PID: ${String(o)})`}}catch(a){return e.updateStatus(t.skill.name,"error"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:"streamable-http"===t.transport.type?`进程存在但不可连接 (${t.transport.endpoint}): ${a instanceof Error?a.message:String(a)}。日志: ${r(s,t.skill.name)}`:`进程存在但不可连接: ${a instanceof Error?a.message:String(a)}。日志: ${r(s,t.skill.name)}`}}}
1
+ import{defineCommand as t}from"citty";import{loadAgentsConfig as e}from"../../config/loader.js";import{getAgentLogPath as a,getAgentPid as r,inspectManagedAgentRuntime as n,probeAgentEndpoint as s}from"../../registry/process-manager.js";import{AgentStore as o}from"../../registry/store.js";import{log as i}from"../utils/output.js";export default t({meta:{description:"检查 Agent 健康状态(兼容 on-demand / core-managed / external-managed)"},args:{restart:{type:"boolean",description:"兼容旧参数;当前不会执行自动重启",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:t}){const{agentsConfig:a}=e(),r=new o(a.dataDir),n=r.list();if(t.restart&&i.warn("`--restart` 仅为兼容保留参数;v1 不执行自动重启逻辑。"),0===n.length)return t.json?void console.log("[]"):void i.info("暂无已注册 Agent。");const s=[];for(const t of n)s.push(await l(t,r,a.dataDir));const p=s.filter(t=>!t.healthy);if(t.json)return console.log(JSON.stringify(s,null,2)),void(p.length>0&&(process.exitCode=1));for(const t of s)t.healthy?i.success(`${t.agentName} [${t.transport}]: ${t.message}`):i.error(`${t.agentName} [${t.transport}]: ${t.message}`);p.length>0&&(process.exitCode=1)}});async function l(t,e,a){switch(t.runtime.ownership){case"on-demand":return{agentName:t.skill.name,transport:t.transport.type,healthy:!0,message:"按需模式:无需常驻进程,由 run/ask 在调用时启动"};case"external-managed":return p(t,e);case"core-managed":return m(t,e,a)}}async function p(t,e){try{return await s(t,{timeoutMs:5e3}),e.updateStatus(t.skill.name,"online"),{agentName:t.skill.name,transport:t.transport.type,healthy:!0,message:"streamable-http"===t.transport.type?`外部服务可连接 (${t.transport.endpoint})`:"外部服务可连接"}}catch(a){return e.updateStatus(t.skill.name,"error"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:"streamable-http"===t.transport.type?`外部服务不可连接 (${t.transport.endpoint}): ${a instanceof Error?a.message:String(a)}`:`外部服务不可连接: ${a instanceof Error?a.message:String(a)}`}}}async function m(t,e,o){const i=r(o,t.skill.name);if(void 0===i)return e.updateStatus(t.skill.name,"stopped"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:`未运行(缺少活动 PID)。日志: ${a(o,t.skill.name)}`};const l=n(t,o);if(l.issues.length>0)return e.updateStatus(t.skill.name,"error"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:`${l.issues.map(t=>t.message).join(";")}。日志: ${a(o,t.skill.name)}`};try{return await s(t,{timeoutMs:5e3}),e.updateStatus(t.skill.name,"online"),{agentName:t.skill.name,transport:t.transport.type,healthy:!0,message:"streamable-http"===t.transport.type?`运行中 (PID: ${String(i)}),可连接 (${t.transport.endpoint})`:`运行中 (PID: ${String(i)})`}}catch(r){return e.updateStatus(t.skill.name,"error"),{agentName:t.skill.name,transport:t.transport.type,healthy:!1,message:"streamable-http"===t.transport.type?`进程存在但不可连接 (${t.transport.endpoint}): ${r instanceof Error?r.message:String(r)}。日志: ${a(o,t.skill.name)}`:`进程存在但不可连接: ${r instanceof Error?r.message:String(r)}。日志: ${a(o,t.skill.name)}`}}}
@@ -4,15 +4,15 @@ declare const _default: import("citty").CommandDef<{
4
4
  description: string;
5
5
  required: true;
6
6
  };
7
- skipBrowserSetup: {
7
+ "skip-browser-setup": {
8
8
  type: "boolean";
9
9
  description: string;
10
10
  default: false;
11
11
  };
12
- noStart: {
12
+ start: {
13
13
  type: "boolean";
14
14
  description: string;
15
- default: false;
15
+ default: true;
16
16
  };
17
17
  }>;
18
18
  export default _default;
@@ -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{inspectAgentEnvRequirements as o}from"../../config/helpers.js";import{loadAgentsConfig as n}from"../../config/loader.js";import{discoverAgent as a}from"../../registry/discovery.js";import{startAgent as l,stopAgentGracefully as p,waitForAgentReady as m}from"../../registry/process-manager.js";import{runAgentSetup as c}from"../../registry/runtime-setup.js";import{AgentStore as d}from"../../registry/store.js";import{parsePackageName as g,readInstalledPackageManifest as u,resolveInstalledPackageRoot as f,sanitizeInstallId as k}from"../../registry/source.js";import{log as y}from"../utils/output.js";import{formatPackageManagerError as h,runPackageManager as w}from"../utils/package-manager.js";function v(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 包并注册到本地"},args:{package:{type:"positional",description:"npm package spec",required:!0},skipBrowserSetup:{type:"boolean",description:"跳过浏览器运行时安装",default:!1},noStart:{type:"boolean",description:"安装后不自动启动 core-managed Agent",default:!1}},async run({args:e}){const{agentsConfig:o}=n(),S=e.package;if(v(S))return y.error(`Git URL 请使用 \`roll agent add ${S}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const j=s(S);if(t(j)&&i(j).isDirectory())return y.error(`本地源码目录请使用 \`roll agent add ${S}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const C=g(S),x=s(o.dataDir,"installed",k(C));t(x)||r(x,{recursive:!0}),y.info(`安装 ${S}...`);const A={command:"npm",args:["install","--prefix",x,S]};try{await w(A,{timeout:12e4})}catch(e){return y.error(`安装失败: ${h(A,e)}`),void(process.exitCode=1)}const b=f(x,C);if(!t(b))return y.error(`安装完成但未找到包目录: ${b}`),void(process.exitCode=1);const B=u(b);y.info("解析已安装 Agent 的 SKILL.md...");const D=a(b),W=new d(o.dataDir),q={skill:D.skill,transport:D.transport,runtime:D.runtime,installPath:b,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:B?.name??C,packageSpec:S,installDir:x,...B?.version?{installedVersion:B.version}:{}},...D.skillBody.length>0?{skillBody:D.skillBody}:{}};"core-managed"===q.runtime.ownership&&q.runtime.setup?.playwright&&!e.skipBrowserSetup&&y.info(`即将安装浏览器运行时 (${q.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const R=await c(q,{skipBrowserSetup:e.skipBrowserSetup});R.ok?R.skipped?y.info(R.message):y.success(R.message):(y.warn(`Agent setup 失败:${R.message}`),R.retryCommand&&y.info(`重试命令: ${R.retryCommand}`));const E=W.findByName(D.skill.name);try{const t="core-managed"===E?.runtime.ownership&&"online"===E.status;if("installed-package"===E?.source?.type?W.replace(E.skill.name,q):W.add(q),!R.ok)return W.updateStatus(D.skill.name,"error"),void(process.exitCode=1);if("core-managed"===q.runtime.ownership&&!e.noStart){t&&await p(o.dataDir,q.skill.name),W.updateStatus(q.skill.name,"starting");let e=!1;try{l(q,o.dataDir),e=!0,await m(q,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),W.updateStatus(q.skill.name,"online")}catch(t){return e&&await p(o.dataDir,q.skill.name).catch(()=>{}),W.updateStatus(q.skill.name,"error"),y.error(`Agent "${D.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}y.success(`Agent "${D.skill.name}" 安装并注册成功`),$(D.skill.name,D.skill.env,o.env)}catch(e){y.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function $(e,t,r){const i=o(e,t,r);if(i)return i.missingRequired.length>0?(y.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),void y.info(`请在 roll.config.yaml 的 agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(y.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),y.info(`建议将这些项写入 roll.config.yaml 的 agents.env.${e}。`)))}
1
+ import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as r,statSync as s}from"node:fs";import{resolve as i}from"node:path";import{inspectAgentEnvRequirements as o}from"../../config/helpers.js";import{loadAgentsConfig as n}from"../../config/loader.js";import{discoverAgent as a}from"../../registry/discovery.js";import{startAgent as l,stopAgentGracefully as m,waitForAgentReady as p}from"../../registry/process-manager.js";import{runAgentSetup as c}from"../../registry/runtime-setup.js";import{AgentStore as d}from"../../registry/store.js";import{parsePackageName as g,readInstalledPackageManifest as u,resolveInstalledPackageRoot as f,sanitizeInstallId as k}from"../../registry/source.js";import{log as y}from"../utils/output.js";import{formatPackageManagerError as h,runPackageManager as w}from"../utils/package-manager.js";function v(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}=n(),j=e.package;if(v(j))return y.error(`Git URL 请使用 \`roll agent add ${j}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const b=i(j);if(t(b)&&s(b).isDirectory())return y.error(`本地源码目录请使用 \`roll agent add ${j}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const S=g(j),C=i(o.dataDir,"installed",k(S));t(C)||r(C,{recursive:!0}),y.info(`安装 ${j}...`);const x={command:"npm",args:["install","--prefix",C,j]};try{await w(x,{timeout:12e4})}catch(e){return y.error(`安装失败: ${h(x,e)}`),void(process.exitCode=1)}const A=f(C,S);if(!t(A))return y.error(`安装完成但未找到包目录: ${A}`),void(process.exitCode=1);const D=u(A);y.info("解析已安装 Agent 的 SKILL.md...");const W=a(A),q=new d(o.dataDir),B={skill:W.skill,transport:W.transport,runtime:W.runtime,installPath:A,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:D?.name??S,packageSpec:j,installDir:C,...D?.version?{installedVersion:D.version}:{}},...W.skillBody.length>0?{skillBody:W.skillBody}:{}};"core-managed"===B.runtime.ownership&&B.runtime.setup?.playwright&&!e["skip-browser-setup"]&&y.info(`即将安装浏览器运行时 (${B.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const R=await c(B,{skipBrowserSetup:e["skip-browser-setup"]});R.ok?R.skipped?y.info(R.message):y.success(R.message):(y.warn(`Agent setup 失败:${R.message}`),R.retryCommand&&y.info(`重试命令: ${R.retryCommand}`));const E=q.findByName(W.skill.name);try{const t="core-managed"===E?.runtime.ownership&&"online"===E.status;if("installed-package"===E?.source?.type?q.replace(E.skill.name,B):q.add(B),!R.ok)return q.updateStatus(W.skill.name,"error"),void(process.exitCode=1);if("core-managed"===B.runtime.ownership&&e.start){t&&await m(o.dataDir,B.skill.name),q.updateStatus(B.skill.name,"starting");let e=!1;try{l(B,o.dataDir),e=!0,await p(B,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),q.updateStatus(B.skill.name,"online")}catch(t){return e&&await m(o.dataDir,B.skill.name).catch(()=>{}),q.updateStatus(B.skill.name,"error"),y.error(`Agent "${W.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}y.success(`Agent "${W.skill.name}" 安装并注册成功`),$(W.skill.name,W.skill.env,o.env)}catch(e){y.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function $(e,t,r){const s=o(e,t,r);if(s)return s.missingRequired.length>0?(y.warn(`Agent "${e}" 仍缺少必填环境变量: ${s.missingRequired.map(e=>e.name).join(", ")}`),void y.info(`请在 roll.config.yaml 的 agents.env.${e} 中显式配置这些项。`)):void(s.processEnvOnlyRequired.length>0&&(y.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${s.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),y.info(`建议将这些项写入 roll.config.yaml 的 agents.env.${e}。`)))}
@@ -1 +1 @@
1
- import{defineCommand as t}from"citty";import{getAgentEnv as e}from"../../config/helpers.js";import{loadAgentsConfig as r,loadConfig as a}from"../../config/loader.js";import{getAgentLogPath as n,getAgentPid as i,probeAgentEndpoint as s,startAgent as o,stopAgentGracefully as m,waitForAgentReady as p}from"../../registry/process-manager.js";import{AgentStore as l}from"../../registry/store.js";import{log as d}from"../utils/output.js";export default t({meta:{description:"启动 Agent(core-managed HTTP 可由 Roll 托管)"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:t}){const{agentsConfig:c}=r(),u=new l(c.dataDir),g=u.findByName(t.name);if(!g)return d.error(`Agent "${t.name}" 未找到`),void(process.exitCode=1);switch(g.runtime.ownership){case"on-demand":return void d.success(`Agent "${t.name}" 为按需模式,无需手动启动。`);case"external-managed":return d.info(`Agent "${t.name}" 由外部服务管理,Roll 不负责启动。`),void("streamable-http"===g.transport.type&&d.info(`端点: ${g.transport.endpoint}`))}const f=i(c.dataDir,g.skill.name);if(void 0!==f)try{return await s(g,{timeoutMs:3e3}),u.updateStatus(g.skill.name,"online"),void d.success(`Agent "${t.name}" 已在运行 (PID: ${String(f)})\n 端点: ${"streamable-http"===g.transport.type?g.transport.endpoint:"n/a"}`)}catch(e){return u.updateStatus(g.skill.name,"error"),d.error(`Agent "${t.name}" 进程存在但不可连接:${e instanceof Error?e.message:String(e)}`),d.info(`日志: ${n(c.dataDir,g.skill.name)}`),void(process.exitCode=1)}let $;u.updateStatus(g.skill.name,"starting");try{const r=e(a().config,g.skill.name);$=o(g,c.dataDir,r),await p(g,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),u.updateStatus(g.skill.name,"online"),d.success(`Agent "${t.name}" 已启动 (PID: ${String($)})\n 端点: ${"streamable-http"===g.transport.type?g.transport.endpoint:"n/a"}\n 日志: ${n(c.dataDir,g.skill.name)}`)}catch(e){void 0!==$&&await m(c.dataDir,g.skill.name).catch(()=>{}),u.updateStatus(g.skill.name,"error"),d.error(`Agent "${t.name}" 启动失败:${e instanceof Error?e.message:String(e)}`),d.info(`日志: ${n(c.dataDir,g.skill.name)}`),process.exitCode=1}}});
1
+ import{defineCommand as t}from"citty";import{getAgentEnv as e}from"../../config/helpers.js";import{loadAgentsConfig as r,loadConfig as a}from"../../config/loader.js";import{getAgentLogPath as n,getAgentPid as i,probeAgentEndpoint as s,startAgent as o,stopAgentGracefully as m,waitForAgentReady as p}from"../../registry/process-manager.js";import{AgentStore as l}from"../../registry/store.js";import{log as d}from"../utils/output.js";export default t({meta:{description:"启动由 Roll 托管的 core-managed Agent"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:t}){const{agentsConfig:c}=r(),u=new l(c.dataDir),g=u.findByName(t.name);if(!g)return d.error(`Agent "${t.name}" 未找到`),void(process.exitCode=1);switch(g.runtime.ownership){case"on-demand":return void d.success(`Agent "${t.name}" 为按需模式,无需手动启动。`);case"external-managed":return d.info(`Agent "${t.name}" 由外部服务管理,Roll 不负责启动。`),void("streamable-http"===g.transport.type&&d.info(`端点: ${g.transport.endpoint}`))}const f=i(c.dataDir,g.skill.name);if(void 0!==f)try{return await s(g,{timeoutMs:3e3}),u.updateStatus(g.skill.name,"online"),void d.success(`Agent "${t.name}" 已在运行 (PID: ${String(f)})\n 端点: ${"streamable-http"===g.transport.type?g.transport.endpoint:"n/a"}`)}catch(e){return u.updateStatus(g.skill.name,"error"),d.error(`Agent "${t.name}" 进程存在但不可连接:${e instanceof Error?e.message:String(e)}`),d.info(`日志: ${n(c.dataDir,g.skill.name)}`),void(process.exitCode=1)}let $;u.updateStatus(g.skill.name,"starting");try{const r=e(a().config,g.skill.name);$=o(g,c.dataDir,r),await p(g,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),u.updateStatus(g.skill.name,"online"),d.success(`Agent "${t.name}" 已启动 (PID: ${String($)})\n 端点: ${"streamable-http"===g.transport.type?g.transport.endpoint:"n/a"}\n 日志: ${n(c.dataDir,g.skill.name)}`)}catch(e){void 0!==$&&await m(c.dataDir,g.skill.name).catch(()=>{}),u.updateStatus(g.skill.name,"error"),d.error(`Agent "${t.name}" 启动失败:${e instanceof Error?e.message:String(e)}`),d.info(`日志: ${n(c.dataDir,g.skill.name)}`),process.exitCode=1}}});
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{loadAgentsConfig as t}from"../../config/loader.js";import{stopAgentGracefully as r}from"../../registry/process-manager.js";import{AgentStore as n}from"../../registry/store.js";import{log as o}from"../utils/output.js";export default e({meta:{description:"停止 Agent(core-managed HTTP 可由 Roll 托管)"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:e}){const{agentsConfig:a}=t(),s=new n(a.dataDir),i=s.findByName(e.name);if(!i)return o.error(`Agent "${e.name}" 未找到`),void(process.exitCode=1);switch(i.runtime.ownership){case"on-demand":return void o.success(`Agent "${e.name}" 为按需模式,无需手动停止。`);case"external-managed":return o.info(`Agent "${e.name}" 由外部服务管理,请在外部停止。`),void("streamable-http"===i.transport.type&&o.info(`端点: ${i.transport.endpoint}`))}let m=!1;try{m=await r(a.dataDir,i.skill.name)}catch(t){return o.error(`停止 Agent "${e.name}" 失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}s.updateStatus(i.skill.name,"stopped"),m?o.success(`Agent "${e.name}" 已停止`):o.info(`Agent "${e.name}" 当前未运行`)}});
1
+ import{defineCommand as e}from"citty";import{loadAgentsConfig as t}from"../../config/loader.js";import{stopAgentGracefully as r}from"../../registry/process-manager.js";import{AgentStore as n}from"../../registry/store.js";import{log as o}from"../utils/output.js";export default e({meta:{description:"停止由 Roll 托管的 core-managed Agent"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:e}){const{agentsConfig:a}=t(),s=new n(a.dataDir),i=s.findByName(e.name);if(!i)return o.error(`Agent "${e.name}" 未找到`),void(process.exitCode=1);switch(i.runtime.ownership){case"on-demand":return void o.success(`Agent "${e.name}" 为按需模式,无需手动停止。`);case"external-managed":return o.info(`Agent "${e.name}" 由外部服务管理,请在外部停止。`),void("streamable-http"===i.transport.type&&o.info(`端点: ${i.transport.endpoint}`))}let m=!1;try{m=await r(a.dataDir,i.skill.name)}catch(t){return o.error(`停止 Agent "${e.name}" 失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}s.updateStatus(i.skill.name,"stopped"),m?o.success(`Agent "${e.name}" 已停止`):o.info(`Agent "${e.name}" 当前未运行`)}});
@@ -1 +1 @@
1
- import{defineCommand as o}from"citty";import{getAgentEnvFromAgentsConfig as t}from"../../config/helpers.js";import{loadAgentsConfig as e}from"../../config/loader.js";import{McpClientManager as n}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as i}from"../../registry/dev-spawn.js";import{AgentStore as r}from"../../registry/store.js";import{normalizeListedTools as s}from"../utils/agent-tools.js";import{formatAgentToolsTextOutput as l}from"../utils/agent-tools-output.js";import{log as a}from"../utils/output.js";export default o({meta:{description:"查看 Agent 暴露的 MCP tools inputSchema"},args:{name:{type:"positional",description:"Agent 名称",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:o}){const{agentsConfig:m}=e(),c=new r(m.dataDir).findByName(o.name);if(!c)return a.error(`Agent "${o.name}" 未找到。使用 \`roll agent list\` 查看已注册 Agent。`),void(process.exitCode=1);const g=new n;try{a.info(`连接 Agent "${c.skill.name}" 并获取 MCP tools/list...`);const e=i(c),n=t(m,c.skill.name),r=await g.connect(c.skill.name,e,c.installPath,{...n?{env:n}:{}}),{tools:p}=await r.listTools(),f=s(p);if(o.json)return void console.log(JSON.stringify(f,null,2));if(0===f.length)return void console.log(`Agent "${c.skill.name}" 暂未暴露任何 tool。`);console.log(l(c.skill.name,f))}catch(o){a.error(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{await g.disconnectAll()}}});
1
+ import{defineCommand as o}from"citty";import{getAgentEnvFromAgentsConfig as t}from"../../config/helpers.js";import{loadAgentsConfig as e}from"../../config/loader.js";import{McpClientManager as n}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as i}from"../../registry/dev-spawn.js";import{AgentStore as r}from"../../registry/store.js";import{normalizeListedTools as s}from"../utils/agent-tools.js";import{formatAgentToolsTextOutput as l}from"../utils/agent-tools-output.js";import{log as a}from"../utils/output.js";export default o({meta:{description:"查看 Agent 暴露的 MCP tools 及输入 schema"},args:{name:{type:"positional",description:"Agent 名称",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:o}){const{agentsConfig:m}=e(),c=new r(m.dataDir).findByName(o.name);if(!c)return a.error(`Agent "${o.name}" 未找到。使用 \`roll agent list\` 查看已注册 Agent。`),void(process.exitCode=1);const g=new n;try{a.info(`连接 Agent "${c.skill.name}" 并获取 MCP tools/list...`);const e=i(c),n=t(m,c.skill.name),r=await g.connect(c.skill.name,e,c.installPath,{...n?{env:n}:{}}),{tools:p}=await r.listTools(),f=s(p);if(o.json)return void console.log(JSON.stringify(f,null,2));if(0===f.length)return void console.log(`Agent "${c.skill.name}" 暂未暴露任何 tool。`);console.log(l(c.skill.name,f))}catch(o){a.error(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{await g.disconnectAll()}}});
@@ -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,n,s,o,a){return n?e?".jsx":".js":!s||o&&a?s+o+"."+a.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";const n=import.meta.url.endsWith(".ts")?"ts":"js";function s(e){const s=new URL(`./${e}.${n}`,import.meta.url).href;return import(t(s)).then(t=>t.default)}export default e({meta:{description:"管理 Agent(支持本地目录、已安装产物、远程服务)"},subCommands:{add:()=>s("agent-add"),install:()=>s("agent-install"),remove:()=>s("agent-remove"),list:()=>s("agent-list"),tools:()=>s("agent-tools"),start:()=>s("agent-start"),stop:()=>s("agent-stop"),info:()=>s("agent-info"),health:()=>s("agent-health")}});
1
+ var t=this&&this.__rewriteRelativeImportExtension||function(t,e){return"string"==typeof t&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(t,n,s,o,a){return n?e?".jsx":".js":!s||o&&a?s+o+"."+a.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";const n=import.meta.url.endsWith(".ts")?"ts":"js";function s(e){const s=new URL(`./${e}.${n}`,import.meta.url).href;return import(t(s)).then(t=>t.default)}export default e({meta:{description:"管理 Agent(本地目录、Git 仓库、已安装 npm 包、远程服务)"},subCommands:{add:()=>s("agent-add"),install:()=>s("agent-install"),remove:()=>s("agent-remove"),list:()=>s("agent-list"),tools:()=>s("agent-tools"),start:()=>s("agent-start"),stop:()=>s("agent-stop"),info:()=>s("agent-info"),health:()=>s("agent-health")}});
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{getAgentEnv as t,getMissingAgentEnvRuntimeIssues as o,inspectAgentEnvRequirements as r}from"../../config/helpers.js";import{shouldSkipRuntimeReadinessForTool as n}from"../../config/runtime-env.js";import{createProviderModel as i,resolveLLMCall as a}from"../../llm/providers.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{AgentStore as m}from"../../registry/store.js";import{resolveTransportWithDevSpawnSpec as c}from"../../registry/dev-spawn.js";import{routeWithLLM as u}from"../../router/llm-router.js";import{extractToolInput as d}from"../../tool-runtime/argument-extractor.js";import{formatValidationIssuesMessage as g}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as v}from"../utils/agent-tools.js";import{log as j,redactToolArgsForLog as y}from"../utils/output.js";import{extractTextContent as $,isToolErrorResult as N}from"../utils/tool-results.js";const k=.5;function x(e){console.log(JSON.stringify(e,null,2))}function w(e){const s=$("object"==typeof e.result&&null!==e.result&&"content"in e.result?e.result.content:void 0);for(const e of s)console.log(e)}export default e({meta:{description:"LLM 智能路由,自动选择 Agent 和 tool"},args:{message:{type:"positional",description:"自然语言消息",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},async run({args:e}){const{config:h}=s(),C=new m(h.agents.dataDir).list();if(0===C.length)return j.error("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。"),void(process.exitCode=1);const L=s=>{if(e.json)x(s);else if("success"===s.status)w(s)},M=h.llm.defaultProvider,S=h.ask.llmModel??h.llm.defaultModel,b=h.llm.providers[M];if(!b)return j.error(`LLM provider "${M}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const{model:A,providerOptions:I}=a(M,S,b.apiKey,"structured-output",b.baseUrl);let O;j.info(`分析意图: "${e.message}"`);try{O=await u(e.message,C,A,I)}catch(e){const s={status:"failed",stage:"route",message:`LLM 路由失败: ${e instanceof Error?e.message:String(e)}`};return j.error(s.message),L(s),void(process.exitCode=1)}j.info(`路由决策: ${O.agentName}.${O.toolName} (置信度: ${String(O.confidence)})`);const T=h.ask.confirmThreshold??k;if(O.confidence<T){const e={status:"needs_confirmation",decision:O,message:`置信度 ${String(O.confidence)} 低于阈值 ${String(T)},跳过执行。 可使用 \`roll run ${O.agentName} ${O.toolName}\` 手动调用。`};return j.warn(e.message),L(e),void(process.exitCode=1)}const J=C.find(e=>e.skill.name===O.agentName);if(!J){const e={status:"failed",stage:"route",decision:O,message:`Agent "${O.agentName}" 未找到(LLM 返回了无效的 Agent 名称)`};return j.error(e.message),L(e),void(process.exitCode=1)}const E=i(M,h.llm.defaultModel,b.apiKey,b.baseUrl),K=new l;let P="connect";try{j.info(`连接 Agent "${J.skill.name}"...`);const s=t(h,J.skill.name),i=c(J),a=await K.connect(J.skill.name,i,J.installPath,{samplingModel:E,...s?{env:s}:{}}),l=v((await a.listTools()).tools),m=l.find(e=>e.name===O.toolName);if(!m){const e={status:"failed",stage:"route",decision:O,message:p(J.skill.name,O.toolName,l)};return j.error(e.message),L(e),void(process.exitCode=1)}P="execute";const u=await d(e.message,m,A,I),k={...O,input:u},x=r(J.skill.name,J.skill.env,h.agents.env),w=n(m.name)?[]:o(x),C=f(m,k.input,{runtimeIssues:w});if(!C.ok){const e={status:"needs_input",decision:k,validationIssues:C.issues,runtimeIssues:C.runtimeIssues,message:g(J.skill.name,O.toolName,C.issues,C.runtimeIssues)};return j.warn(e.message),L(e),void(process.exitCode=1)}j.info(`调用 ${J.skill.name}.${O.toolName}`),j.debug(`调用参数: ${JSON.stringify(y(k.input))}`);const M=await a.callTool({name:O.toolName,arguments:k.input});if(N(M)){const e={status:"failed",stage:"execute",decision:k,message:$(M.content).join("\n")||"Tool 调用失败"};return j.error(e.message),L(e),void(process.exitCode=1)}L({status:"success",decision:k,result:M}),j.success("调用完成")}catch(e){const s={status:"failed",stage:P,decision:O,message:e instanceof Error?e.message:String(e)};j.error(s.message),L(s),process.exitCode=1}finally{await K.disconnectAll()}}});
1
+ import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{getAgentEnv as t,getMissingAgentEnvRuntimeIssues as o,inspectAgentEnvRequirements as r}from"../../config/helpers.js";import{shouldSkipRuntimeReadinessForTool as n}from"../../config/runtime-env.js";import{createProviderModel as i,resolveLLMCall as a}from"../../llm/providers.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{AgentStore as m}from"../../registry/store.js";import{resolveTransportWithDevSpawnSpec as c}from"../../registry/dev-spawn.js";import{routeWithLLM as u}from"../../router/llm-router.js";import{extractToolInput as d}from"../../tool-runtime/argument-extractor.js";import{formatValidationIssuesMessage as g}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as v}from"../utils/agent-tools.js";import{log as j,redactToolArgsForLog as y}from"../utils/output.js";import{extractTextContent as $,isToolErrorResult as N}from"../utils/tool-results.js";const k=.5;function x(e){console.log(JSON.stringify(e,null,2))}function w(e){const s=$("object"==typeof e.result&&null!==e.result&&"content"in e.result?e.result.content:void 0);for(const e of s)console.log(e)}export default e({meta:{description:"LLM 从自然语言中选择 Agent 和 MCP tool"},args:{message:{type:"positional",description:"自然语言消息",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},async run({args:e}){const{config:C}=s(),h=new m(C.agents.dataDir).list();if(0===h.length)return j.error("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。"),void(process.exitCode=1);const M=s=>{if(e.json)x(s);else if("success"===s.status)w(s)},L=C.llm.defaultProvider,S=C.ask.llmModel??C.llm.defaultModel,b=C.llm.providers[L];if(!b)return j.error(`LLM provider "${L}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const{model:A,providerOptions:I}=a(L,S,b.apiKey,"structured-output",b.baseUrl);let O;j.info(`分析意图: "${e.message}"`);try{O=await u(e.message,h,A,I)}catch(e){const s={status:"failed",stage:"route",message:`LLM 路由失败: ${e instanceof Error?e.message:String(e)}`};return j.error(s.message),M(s),void(process.exitCode=1)}j.info(`路由决策: ${O.agentName}.${O.toolName} (置信度: ${String(O.confidence)})`);const T=C.ask.confirmThreshold??k;if(O.confidence<T){const e={status:"needs_confirmation",decision:O,message:`置信度 ${String(O.confidence)} 低于阈值 ${String(T)},跳过执行。 可使用 \`roll run ${O.agentName} ${O.toolName}\` 手动调用。`};return j.warn(e.message),M(e),void(process.exitCode=1)}const J=h.find(e=>e.skill.name===O.agentName);if(!J){const e={status:"failed",stage:"route",decision:O,message:`Agent "${O.agentName}" 未找到(LLM 返回了无效的 Agent 名称)`};return j.error(e.message),M(e),void(process.exitCode=1)}const P=i(L,C.llm.defaultModel,b.apiKey,b.baseUrl),E=new l;let K="connect";try{j.info(`连接 Agent "${J.skill.name}"...`);const s=t(C,J.skill.name),i=c(J),a=await E.connect(J.skill.name,i,J.installPath,{samplingModel:P,...s?{env:s}:{}}),l=v((await a.listTools()).tools),m=l.find(e=>e.name===O.toolName);if(!m){const e={status:"failed",stage:"route",decision:O,message:p(J.skill.name,O.toolName,l)};return j.error(e.message),M(e),void(process.exitCode=1)}K="execute";const u=await d(e.message,m,A,I),k={...O,input:u},x=r(J.skill.name,J.skill.env,C.agents.env),w=n(m.name)?[]:o(x),h=f(m,k.input,{runtimeIssues:w});if(!h.ok){const e={status:"needs_input",decision:k,validationIssues:h.issues,runtimeIssues:h.runtimeIssues,message:g(J.skill.name,O.toolName,h.issues,h.runtimeIssues)};return j.warn(e.message),M(e),void(process.exitCode=1)}j.info(`调用 ${J.skill.name}.${O.toolName}`),j.debug(`调用参数: ${JSON.stringify(y(k.input))}`);const L=await a.callTool({name:O.toolName,arguments:k.input});if(N(L)){const e={status:"failed",stage:"execute",decision:k,message:$(L.content).join("\n")||"Tool 调用失败"};return j.error(e.message),M(e),void(process.exitCode=1)}M({status:"success",decision:k,result:L}),j.success("调用完成")}catch(e){const s={status:"failed",stage:K,decision:O,message:e instanceof Error?e.message:String(e)};j.error(s.message),M(s),process.exitCode=1}finally{await E.disconnectAll()}}});
@@ -1 +1 @@
1
- import{defineCommand as s}from"citty";import{log as e}from"../utils/output.js";const t="roll chat 仍处于 experimental 阶段,当前只提供命令骨架,不会执行会话编排、不会恢复 session,也不会隐式降级到 roll ask。";function o(){return{status:"unavailable",message:t}}function n(s){console.log(JSON.stringify(s,null,2))}export default s({meta:{description:"Experimental: 未来会话式统一入口(当前仅提供命令骨架)"},args:{message:{type:"positional",description:"起始消息",required:!1},session:{type:"string",description:"继续指定会话 ID"},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){const t=o();s.json?n(t):e.warn(t.message),process.exitCode=1}});
1
+ import{defineCommand as s}from"citty";import{log as e}from"../utils/output.js";const t="roll chat 仍处于 experimental 阶段,当前只提供命令骨架,不会执行会话编排、不会恢复 session,也不会隐式降级到 roll ask。";function o(){return{status:"unavailable",message:t}}function n(s){console.log(JSON.stringify(s,null,2))}export default s({meta:{description:"Experimental: 会话式入口骨架(当前不可用)"},args:{message:{type:"positional",description:"起始消息",required:!1},session:{type:"string",description:"继续已有会话的 session ID"},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){const t=o();s.json?n(t):e.warn(t.message),process.exitCode=1}});
@@ -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 时使用,点号分隔)",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.confirmThreshold 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 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,3 +1,14 @@
1
+ export interface CheckResult {
2
+ readonly name: string;
3
+ readonly status: "ok" | "warn" | "fail";
4
+ readonly message: string;
5
+ readonly fix?: string;
6
+ }
7
+ export interface DoctorFixResult {
8
+ readonly name: string;
9
+ readonly status: "applied" | "skipped" | "failed";
10
+ readonly message: string;
11
+ }
1
12
  export declare function isNodeVersionSupported(version: string): boolean;
2
13
  declare const _default: import("citty").CommandDef<{
3
14
  json: {
@@ -5,5 +16,29 @@ declare const _default: import("citty").CommandDef<{
5
16
  description: string;
6
17
  default: false;
7
18
  };
19
+ "fix-plan": {
20
+ type: "boolean";
21
+ description: string;
22
+ default: false;
23
+ };
24
+ fix: {
25
+ type: "boolean";
26
+ description: string;
27
+ default: false;
28
+ };
8
29
  }>;
9
30
  export default _default;
31
+ export declare function formatDoctorCheckLines(check: CheckResult, options: {
32
+ readonly fixPlan: boolean;
33
+ }): string[];
34
+ export declare function formatDoctorChecksForJsonOutput(checks: readonly CheckResult[], options: {
35
+ readonly fixPlan: boolean;
36
+ }): readonly CheckResult[];
37
+ export declare function formatDoctorJsonOutput(checks: readonly CheckResult[], options: {
38
+ readonly fixPlan: boolean;
39
+ readonly fixes: readonly DoctorFixResult[];
40
+ }): {
41
+ readonly checks: readonly CheckResult[];
42
+ readonly fixes: readonly DoctorFixResult[];
43
+ };
44
+ export declare function formatDoctorFixLines(fix: DoctorFixResult): string[];
@@ -1 +1 @@
1
- import{defineCommand as s}from"citty";import{existsSync as e}from"node:fs";import{getAgentEnvFromAgentsConfig as t,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{inspectConfigFile as o,loadAgentsConfig as a,loadConfig as r}from"../../config/loader.js";import{inspectAgentRuntimeEnvRequirements as i,summarizeAgentRuntimeEnvReport as m}from"../../config/runtime-env.js";import{inspectAgentRuntimeEnv as g}from"../../mcp/agent-diagnostics.js";import{AgentStore as c}from"../../registry/store.js";const l={ok:"✓",warn:"⚠",fail:"✗"},u={major:22,minor:6,patch:0};export function isNodeVersionSupported(s){const e=/^(\d+)\.(\d+)\.(\d+)/.exec(s);if(!e)return!1;const t=Number(e[1]),n=Number(e[2]),o=Number(e[3]);return t>u.major||!(t<u.major)&&(n>u.minor||!(n<u.minor)&&o>=u.patch)}export default s({meta:{description:"诊断系统状态"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){const u=[];let p;const f=process.versions.node;u.push(isNodeVersionSupported(f)?{name:"Node.js 版本",status:"ok",message:`v${f}`}:{name:"Node.js 版本",status:"fail",message:`v${f} (需要 ≥22.6.0)`});const d=o();let h;switch(d.status){case"not-found":u.push({name:"配置文件",status:"warn",message:"未找到,使用默认配置"}),p=r().config;break;case"valid":u.push({name:"配置文件",status:"ok",message:d.configPath}),p=d.config;break;case"needs-migration":u.push({name:"配置文件",status:"warn",message:`${d.configPath} (需要迁移,运行 roll config migrate)`});break;case"invalid":u.push({name:"配置文件",status:"fail",message:d.error.message})}try{h=a().agentsConfig}catch(s){u.push({name:"Agent 配置",status:"fail",message:s instanceof Error?s.message:String(s)})}if(p){const s=Object.keys(p.llm.providers);if(s.length>0){const e=s.filter(s=>{const e=p.llm.providers[s]?.apiKey??"";return e.startsWith("${")||0===e.length});e.length>0?u.push({name:"LLM Providers",status:"warn",message:`${s.join(", ")} (${e.join(", ")} API key 未设置)`}):u.push({name:"LLM Providers",status:"ok",message:s.join(", ")})}else u.push({name:"LLM Providers",status:"warn",message:"未配置任何 provider"})}else"needs-migration"===d.status&&u.push({name:"LLM Providers",status:"warn",message:"配置需要迁移,跳过完整 LLM 配置校验"});if(h){const s=h.dataDir;u.push(e(s)?{name:"Agent 数据目录",status:"ok",message:s}:{name:"Agent 数据目录",status:"warn",message:`${s} (不存在,首次 add 时创建)`});const o=new c(h.dataDir).list();u.push({name:"已注册 Agent",status:o.length>0?"ok":"warn",message:o.length>0?`${String(o.length)} 个 (${o.map(s=>s.skill.name).join(", ")})`:"无"});for(const s of o){const e=n(s.skill.name,s.skill.env,h.env);if(!e)continue;const o=await g(s,{agentsConfig:h}),a=i(e,t(h,s.skill.name),o),r=m(a);u.push({name:`Agent 环境变量 (${s.skill.name})`,status:r.status,message:r.message})}}const j=u.some(s=>"fail"===s.status);if(s.json)return console.log(JSON.stringify(u,null,2)),void(j&&(process.exitCode=1));console.log("Roll Agent 系统诊断\n");for(const s of u){const e=l[s.status];console.log(` ${e} ${s.name}: ${s.message}`)}console.log(j?"\n存在问题,请修复后重试。":"\n系统状态正常。"),j&&(process.exitCode=1)}});
1
+ import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as s,writeFileSync as n}from"node:fs";import{stringify as a}from"yaml";import{getAgentEnvFromAgentsConfig as o,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{inspectConfigFile as r,loadAgentsConfig as m,loadConfig as g,parseConfigDocument as u,validateConfigText as f}from"../../config/loader.js";import{applyKnownConfigMigrations as l}from"../../config/migration.js";import{inspectAgentRuntimeEnvRequirements as c,summarizeAgentRuntimeEnvReport as p}from"../../config/runtime-env.js";import{inspectAgentRuntimeEnv as d}from"../../mcp/agent-diagnostics.js";import{cleanupOrphanAgentRuntimeMetadata as h,inspectManagedAgentRuntime as x}from"../../registry/process-manager.js";import{AgentStore as k}from"../../registry/store.js";const $={ok:"✓",warn:"⚠",fail:"✗"},j={applied:"✓",skipped:"•",failed:"✗"},S={major:22,minor:6,patch:0};export function isNodeVersionSupported(e){const t=/^(\d+)\.(\d+)\.(\d+)/.exec(e);if(!t)return!1;const s=Number(t[1]),n=Number(t[2]),a=Number(t[3]);return s>S.major||!(s<S.major)&&(n>S.minor||!(n<S.minor)&&a>=S.patch)}export default e({meta:{description:"诊断 Roll 配置、Agent 注册表和运行时状态"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1},"fix-plan":{type:"boolean",description:"输出可执行修复建议(不自动修改)",default:!1},fix:{type:"boolean",description:"执行安全自动修复:配置自动迁移、创建 dataDir、清理孤儿 runtime 元数据",default:!1}},async run({args:e}){const s=[],n=[],a=!0===e.fix;let u;const f=process.versions.node;s.push(isNodeVersionSupported(f)?{name:"Node.js 版本",status:"ok",message:`v${f}`}:{name:"Node.js 版本",status:"fail",message:`v${f} (需要 ≥22.6.0)`,fix:"升级 Node.js 到 22.6.0 或更高版本"});let l,h=r();if("needs-migration"===h.status&&a){const e=v(h);n.push(e),"applied"===e.status&&(h=r())}switch(h.status){case"not-found":s.push({name:"配置文件",status:"warn",message:"未找到,使用默认配置",fix:"运行 `roll config init` 生成显式配置文件"}),u=g().config;break;case"valid":s.push({name:"配置文件",status:"ok",message:h.configPath}),u=h.config;break;case"needs-migration":s.push({name:"配置文件",status:"warn",message:`${h.configPath} (需要迁移,运行 roll config migrate)`,fix:"运行 `roll config migrate`,确认备份文件后提交配置变更"});break;case"invalid":s.push({name:"配置文件",status:"fail",message:h.error.message,fix:"修正配置文件语法或运行 `roll config migrate` 处理 breaking schema change"})}try{l=m().agentsConfig}catch(e){s.push({name:"Agent 配置",status:"fail",message:e instanceof Error?e.message:String(e),fix:"检查 `roll.config.yaml` 的 `agents` 段;如配置版本过旧,运行 `roll config migrate`"})}if(u){const e=Object.keys(u.llm.providers);if(e.length>0){const t=e.filter(e=>{const t=u.llm.providers[e]?.apiKey??"";return t.startsWith("${")||0===t.length});t.length>0?s.push({name:"LLM Providers",status:"warn",message:`${e.join(", ")} (${t.join(", ")} API key 未设置)`,fix:`配置 ${t.map(e=>`llm.providers.${e}.apiKey`).join(", ")} 或对应环境变量`}):s.push({name:"LLM Providers",status:"ok",message:e.join(", ")})}else s.push({name:"LLM Providers",status:"warn",message:"未配置任何 provider",fix:"运行 `roll config init` 或手动配置 `llm.providers`"})}else"needs-migration"===h.status&&s.push({name:"LLM Providers",status:"warn",message:"配置需要迁移,跳过完整 LLM 配置校验",fix:"先运行 `roll config migrate`,再重新执行 `roll doctor`"});if(l){const e=l.dataDir;let r=t(e);if(!r&&a){const s=P(e);n.push(s),r=t(e)}s.push(r?{name:"Agent 数据目录",status:"ok",message:e}:{name:"Agent 数据目录",status:"warn",message:`${e} (不存在,首次 add 时创建)`,fix:"运行 `roll doctor --fix` 创建目录,或手动创建该目录"});const m=new k(l.dataDir).list();s.push({name:"已注册 Agent",status:m.length>0?"ok":"warn",message:m.length>0?`${String(m.length)} 个 (${m.map(e=>e.skill.name).join(", ")})`:"无",...0===m.length?{fix:"运行 `roll agent add <path|git-url>` 注册 Agent"}:{}});for(const e of m){if("core-managed"===e.runtime.ownership){let t=x(e,l.dataDir);if(a&&t.issues.some(e=>"orphan-sidecar"===e.code)){const s=y(l.dataDir,e.skill.name);n.push(s),"applied"===s.status&&(t=x(e,l.dataDir))}(void 0!==t.pid||t.issues.length>0)&&s.push({name:`Agent runtime (${e.skill.name})`,status:t.issues.length>0?"warn":"ok",message:t.issues.length>0?t.issues.map(e=>e.message).join(";"):`PID ${String(t.pid)},runtime sidecar 与当前配置一致`,...t.issues.length>0?{fix:A(t.issues.map(e=>e.fix)).join(";")}:{}})}const t=i(e.skill.name,e.skill.env,l.env);if(!t)continue;const r=await d(e,{agentsConfig:l}),m=c(t,o(l,e.skill.name),r),g=p(m);s.push({name:`Agent 环境变量 (${e.skill.name})`,status:g.status,message:g.message,..."ok"===g.status?{}:{fix:`在 \`roll.config.yaml\` 的 \`agents.env.${e.skill.name}\` 或 shell 环境中配置缺失变量`}})}}const $=s.some(e=>"fail"===e.status),j=n.some(e=>"failed"===e.status),S=s.some(e=>"warn"===e.status),D=!0===e["fix-plan"]||a;if(e.json){const e=a?formatDoctorJsonOutput(s,{fixPlan:D,fixes:n}):formatDoctorChecksForJsonOutput(s,{fixPlan:D});return console.log(JSON.stringify(e,null,2)),void(($||j)&&(process.exitCode=1))}console.log("Roll Agent 系统诊断\n");for(const e of s)for(const t of formatDoctorCheckLines(e,{fixPlan:D}))console.log(t);if(a)if(console.log("\nFix 执行结果\n"),0===n.length)console.log(" ✓ 无可自动修复项");else for(const e of n)for(const t of formatDoctorFixLines(e))console.log(t);console.log($||j?"\n存在问题,请修复后重试。":S?"\n存在警告,可按 fix plan 处理。":"\n系统状态正常。"),($||j)&&(process.exitCode=1)}});export function formatDoctorCheckLines(e,t){const s=[` ${$[e.status]} ${e.name}: ${e.message}`];return t.fixPlan&&e.fix&&s.push(` fix: ${e.fix}`),s}export function formatDoctorChecksForJsonOutput(e,t){return t.fixPlan?e:e.map(e=>({name:e.name,status:e.status,message:e.message}))}export function formatDoctorJsonOutput(e,t){return{checks:formatDoctorChecksForJsonOutput(e,{fixPlan:t.fixPlan}),fixes:t.fixes}}export function formatDoctorFixLines(e){return[` ${j[e.status]} ${e.name}: ${e.message}`]}function v(e){if(!e.report.canAutoMigrate)return{name:"配置迁移",status:"skipped",message:"检测到需要人工处理的配置迁移项,未自动修改"};try{const t=u(e.raw,e.configPath),s=l(t);if(!s.ok)return{name:"配置迁移",status:"skipped",message:`无法自动迁移:${w(s.issues)}`};if(!s.changed)return{name:"配置迁移",status:"skipped",message:"配置文件已是最新格式"};const o=a(s.document,{lineWidth:0});f(o,e.configPath);const i=D(e.configPath);return n(i,e.raw,"utf-8"),n(e.configPath,o,"utf-8"),{name:"配置迁移",status:"applied",message:`已迁移 ${e.configPath},备份 ${i}`}}catch(e){return{name:"配置迁移",status:"failed",message:e instanceof Error?e.message:String(e)}}}function P(e){try{return s(e,{recursive:!0}),{name:"Agent 数据目录",status:"applied",message:`已创建 ${e}`}}catch(e){return{name:"Agent 数据目录",status:"failed",message:e instanceof Error?e.message:String(e)}}}function y(e,t){try{return h(e,t)?{name:`Agent runtime (${t})`,status:"applied",message:"已清理孤儿 runtime 元数据"}:{name:`Agent runtime (${t})`,status:"skipped",message:"检测到活动 PID,跳过 runtime 元数据清理"}}catch(e){return{name:`Agent runtime (${t})`,status:"failed",message:e instanceof Error?e.message:String(e)}}}function D(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 w(e){return e.map(e=>e.message).join(";")}function A(e){return[...new Set(e)]}
@@ -2,12 +2,12 @@ declare const _default: import("citty").CommandDef<{
2
2
  agent: {
3
3
  type: "positional";
4
4
  description: string;
5
- required: true;
5
+ required: false;
6
6
  };
7
7
  tool: {
8
8
  type: "positional";
9
9
  description: string;
10
- required: true;
10
+ required: false;
11
11
  };
12
12
  json: {
13
13
  type: "boolean";
@@ -20,6 +20,11 @@ declare const _default: import("citty").CommandDef<{
20
20
  description: string;
21
21
  default: false;
22
22
  };
23
+ bail: {
24
+ type: "boolean";
25
+ description: string;
26
+ default: false;
27
+ };
23
28
  "input-json": {
24
29
  type: "string";
25
30
  description: string;
@@ -28,9 +33,48 @@ declare const _default: import("citty").CommandDef<{
28
33
  type: "string";
29
34
  description: string;
30
35
  };
36
+ "batch-json": {
37
+ type: "string";
38
+ description: string;
39
+ };
40
+ "batch-file": {
41
+ type: "string";
42
+ description: string;
43
+ };
44
+ "batch-stdin": {
45
+ type: "boolean";
46
+ description: string;
47
+ default: false;
48
+ };
31
49
  }>;
32
50
  export default _default;
51
+ export interface RunBatchItem {
52
+ readonly agent: string;
53
+ readonly tool: string;
54
+ readonly input: Readonly<Record<string, unknown>>;
55
+ readonly label?: string;
56
+ }
57
+ interface RunToolBaseResult {
58
+ readonly index: number;
59
+ readonly agent: string;
60
+ readonly tool: string;
61
+ readonly label?: string;
62
+ }
63
+ export interface RunToolSuccessResult extends RunToolBaseResult {
64
+ readonly ok: true;
65
+ readonly result: unknown;
66
+ }
67
+ export interface RunToolFailureResult extends RunToolBaseResult {
68
+ readonly ok: false;
69
+ readonly error: string;
70
+ readonly result?: unknown;
71
+ }
72
+ export type RunToolResult = RunToolSuccessResult | RunToolFailureResult;
33
73
  export declare function parseExplicitToolInput(rawArgs: string[]): Record<string, unknown> | undefined;
74
+ interface ParseBatchInputOptions {
75
+ readonly readStdin?: () => string;
76
+ }
77
+ export declare function parseBatchInput(rawArgs: string[], options?: ParseBatchInputOptions): readonly RunBatchItem[] | undefined;
34
78
  export declare function resolveToolArgs(rawArgs: string[]): Record<string, unknown>;
35
79
  /**
36
80
  * 从 rawArgs 中解析 --key value 格式的参数。
@@ -1 +1 @@
1
- import{readFileSync as t}from"node:fs";import{defineCommand as o}from"citty";import{loadConfig as r}from"../../config/loader.js";import{getAgentEnv as e,getMissingAgentEnvRuntimeIssues as n,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{AgentStore as s}from"../../registry/store.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{createProviderModel as c}from"../../llm/providers.js";import{formatValidationIssuesMessage as f}from"../../tool-runtime/messages.js";import{preflightToolCall as p}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as u,normalizeListedTools as m}from"../utils/agent-tools.js";import{extractTextContent as g,formatToolResultForJsonOutput as d,isToolErrorResult as h}from"../utils/tool-results.js";import{log as j,redactToolArgsForLog as v}from"../utils/output.js";import{shouldSkipRuntimeReadinessForTool as y}from"../../config/runtime-env.js";export default o({meta:{description:"声明式调用 Agent 的指定 tool"},args:{agent:{type:"positional",description:"Agent 名称",required:!0},tool:{type:"positional",description:"Tool 名称",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1},"input-json":{type:"string",description:"以 JSON 字符串提供完整 tool 输入对象"},"input-file":{type:"string",description:"从 JSON 文件读取完整 tool 输入对象"}},async run({args:t,rawArgs:o}){const{config:w}=r(),S=new s(w.agents.dataDir).findByName(t.agent);if(!S)return j.error(`Agent "${t.agent}" 未注册。使用 \`roll agent list\` 查看已注册 Agent。`),void(process.exitCode=1);let N;try{N=resolveToolArgs(o)}catch(t){return j.error(t instanceof Error?t.message:String(t)),void(process.exitCode=1)}const $=new l;try{const o=w.llm.defaultProvider,r=w.llm.providers[o],s=r?c(o,w.llm.defaultModel,r.apiKey,r.baseUrl):void 0;j.info(`连接 Agent "${S.skill.name}"...`);const l=e(w,S.skill.name),A=a(S),b=await $.connect(S.skill.name,A,S.installPath,{...s?{samplingModel:s}:{},...l?{env:l}:{}}),x=m((await b.listTools()).tools),E=x.find(o=>o.name===t.tool);if(!E)return j.error(u(S.skill.name,t.tool,x)),void(process.exitCode=1);const k=i(S.skill.name,S.skill.env,w.agents.env),J=y(E.name)?[]:n(k),O=p(E,N,{runtimeIssues:J});if(!O.ok)return j.error(f(S.skill.name,t.tool,O.issues,O.runtimeIssues)),void(process.exitCode=1);j.info(`调用 ${S.skill.name}.${t.tool}`),j.debug(`调用参数: ${JSON.stringify(v(N))}`);const T=await b.callTool({name:t.tool,arguments:N});if(t.json)console.log(JSON.stringify(d(T),null,2));else for(const t of g(T.content))console.log(t);if(h(T))return j.error("tool 返回 isError=true"),void(process.exitCode=1);j.success("调用完成")}catch(t){const o=t instanceof Error?t.message:String(t),r=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";j.error(`${o}${r}`),process.exitCode=1}finally{await $.disconnectAll()}}});const w=new Set(["json","verbose","v","help","h","version"]),S=new Set(["config","input-json","input-file"]);function N(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function $(t,o){let r;try{r=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!N(r))throw new Error(`${o} 必须是 JSON object`);return r}function A(t,o){let r=0;for(;r<t.length&&!t[r]?.startsWith("--");)r++;for(;r<t.length;){if(t[r]!==`--${o}`){r++;continue}const e=t[r+1];if(!e||e.startsWith("--"))throw new Error(`选项 --${o} 需要提供值`);return e}}export function parseExplicitToolInput(o){const r=A(o,"input-json"),e=A(o,"input-file"),n=b(o);if([r,e,n].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 positional JSON、--input-json 和 --input-file");if(r)return $(r,"--input-json");if(e){return $(t(e,"utf-8"),`输入文件 ${e}`)}return n?$(n,"positional JSON input"):void 0}function b(t){const o=[];for(const r of t){if(r.startsWith("--"))break;o.push(r)}const r=o.slice(2);if(0!==r.length){if(1===r.length&&r[0]?.trim().startsWith("{"))return r[0];throw new Error("roll run 只接受 agent/tool 两个位置参数;tool 输入请使用 --key value、--input-json、--input-file,或第三个位置参数 JSON object")}}export function resolveToolArgs(t){return{...parseExplicitToolInput(t)??{},...parseToolArgs(t)}}export function parseToolArgs(t){const o={};let r=0;for(;r<t.length&&!t[r]?.startsWith("--");)r++;for(;r<t.length;){const e=t[r];if(!e?.startsWith("--")){r++;continue}const n=e.slice(2),i=t[r+1];if(w.has(n))r++;else if(S.has(n))r+=i&&!i.startsWith("--")?2:1;else if(!i||i.startsWith("--"))o[n]=!0,r++;else{const t=Number(i);o[n]=Number.isNaN(t)?i:t,r+=2}}return o}
1
+ import{readFileSync as t}from"node:fs";import{defineCommand as n}from"citty";import{loadConfig as o}from"../../config/loader.js";import{getAgentEnv as r,getMissingAgentEnvRuntimeIssues as e,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{AgentStore as s}from"../../registry/store.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{createProviderModel as c}from"../../llm/providers.js";import{formatValidationIssuesMessage as f}from"../../tool-runtime/messages.js";import{preflightToolCall as u}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as g}from"../utils/agent-tools.js";import{extractTextContent as h,formatToolResultForJsonOutput as m,isToolErrorResult as d}from"../utils/tool-results.js";import{log as b,redactToolArgsForLog as w}from"../utils/output.js";import{shouldSkipRuntimeReadinessForTool as y}from"../../config/runtime-env.js";export default n({meta:{description:"直接调用已注册 Agent 的指定 MCP tool"},args:{agent:{type:"positional",description:"Agent 名称",required:!1},tool:{type:"positional",description:"MCP tool 名称",required:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1},bail:{type:"boolean",description:"batch 模式下遇到第一条失败即停止",default:!1},"input-json":{type:"string",description:"以 JSON 字符串提供完整 tool 输入对象;不能与 --input-file 同用"},"input-file":{type:"string",description:"从 JSON 文件读取完整 tool 输入对象;不能与 --input-json 同用"},"batch-json":{type:"string",description:"以 JSON array 提供多条调用;每项包含 agent/tool/input"},"batch-file":{type:"string",description:"从 JSON 文件读取 batch array;不能与 --batch-json 同用"},"batch-stdin":{type:"boolean",description:"从 stdin 读取 batch array;不能与 --batch-json 或 --batch-file 同用",default:!1}},async run({args:t,rawArgs:n}){const{config:r}=o(),e=new s(r.agents.dataDir),i=new l,a=new Map;try{const o=P(r),s={store:e,config:r,clientManager:i,agentConnections:a,...o?{samplingModel:o}:{}};if(O(n)&&T(n)>0)return b.error("batch 模式不接受 agent/tool 位置参数;请在每个 batch item 中声明 agent tool"),void(process.exitCode=1);const l=parseBatchInput(n,{readStdin:C});if(l){const n=await D({items:l,...s,bail:!0===t.bail});return t.json?console.log(JSON.stringify(n.map(U),null,2)):Y(n),void(n.some(t=>!t.ok)&&(process.exitCode=1))}const c=I(t.agent,"agent"),f=I(t.tool,"tool");if(!c||!f)return b.error("roll run 需要提供 agent/tool 位置参数,或使用 --batch-json / --batch-file / --batch-stdin 进入 batch 模式"),void(process.exitCode=1);let u;try{u=resolveToolArgs(n)}catch(t){return b.error(t instanceof Error?t.message:String(t)),void(process.exitCode=1)}const p=await q({item:{agent:c,tool:f,input:u},index:0,...s});p.ok?(t.json?console.log(JSON.stringify(m(p.result),null,2)):K(p.result),b.success("调用完成")):(b.error(p.error),process.exitCode=1)}catch(t){const n=t instanceof Error?t.message:String(t),o=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";b.error(`${n}${o}`),process.exitCode=1}finally{await i.disconnectAll()}}});const j=new Set(["json","verbose","v","bail","help","h","version"]),$=new Set(["batch-stdin"]),S=new Set(["config","input-json","input-file","batch-json","batch-file"]);function v(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function E(t,n){let o;try{o=JSON.parse(t)}catch(t){throw new Error(`${n} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!v(o))throw new Error(`${n} 必须是 JSON object`);return o}function N(t,n){let o;try{o=JSON.parse(t)}catch(t){throw new Error(`${n} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!Array.isArray(o))throw new Error(`${n} 必须是 JSON array`);return o}function k(t,n){let o=0;for(;o<t.length&&!t[o]?.startsWith("--");)o++;for(;o<t.length;){if(t[o]!==`--${n}`){o++;continue}const r=t[o+1];if(!r||r.startsWith("--"))throw new Error(`选项 --${n} 需要提供值`);return r}}function J(t,n){let o=0;for(;o<t.length&&!t[o]?.startsWith("--");)o++;for(;o<t.length;){if(t[o]===`--${n}`)return!0;o++}return!1}function O(t){return x(t,"batch-json")||x(t,"batch-file")||x(t,"batch-stdin")}function x(t,n){let o=0;for(;o<t.length&&!t[o]?.startsWith("--");)o++;for(;o<t.length;){if(t[o]===`--${n}`)return!0;o++}return!1}export function parseExplicitToolInput(n){const o=k(n,"input-json"),r=k(n,"input-file"),e=M(n);if([o,r,e].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 positional JSON、--input-json 和 --input-file");if(o)return E(o,"--input-json");if(r){return E(t(r,"utf-8"),`输入文件 ${r}`)}return e?E(e,"positional JSON input"):void 0}export function parseBatchInput(n,o={}){const r=k(n,"batch-json"),e=k(n,"batch-file"),i=J(n,"batch-stdin"),s=k(n,"input-json"),l=k(n,"input-file");if([r,e,i?"--batch-stdin":void 0].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 --batch-json、--batch-file 和 --batch-stdin");if(!r&&!e&&!i)return;if(s||l)throw new Error("batch 模式不能同时使用 --input-json 或 --input-file");const a=r?"--batch-json":e?`batch 文件 ${e}`:"--batch-stdin",c=N(r??(e?t(e,"utf-8"):A(o)),a);if(0===c.length)throw new Error(`${a} 至少需要包含一条调用`);return c.map((t,n)=>W(t,n))}function A(t){if(!t.readStdin)throw new Error("--batch-stdin 需要可用的 stdin 读取器");return t.readStdin()}function C(){if(process.stdin.isTTY)throw new Error("--batch-stdin 需要从 stdin 管道或文件重定向读取 JSON array");return t(0,"utf-8")}function W(t,n){const o=`batch[${String(n)}]`;if(!v(t))throw new Error(`${o} 必须是 JSON object`);const r=t.agent,e=t.tool,i="input"in t?t.input:{},s=t.label;if("string"!=typeof r||0===r.trim().length)throw new Error(`${o}.agent 必须是非空字符串`);if("string"!=typeof e||0===e.trim().length)throw new Error(`${o}.tool 必须是非空字符串`);if(void 0!==i&&!v(i))throw new Error(`${o}.input 必须是 JSON object`);if(void 0!==s&&"string"!=typeof s)throw new Error(`${o}.label 必须是字符串`);return{agent:r,tool:e,input:i??{},...void 0!==s?{label:s}:{}}}function M(t){const n=[];for(const o of t){if(o.startsWith("--"))break;n.push(o)}const o=n.slice(2);if(0!==o.length){if(1===o.length&&o[0]?.trim().startsWith("{"))return o[0];throw new Error("roll run 只接受 agent/tool 两个位置参数;tool 输入请使用 --key value、--input-json、--input-file,或第三个位置参数 JSON object")}}function T(t){let n=0;for(const o of t){if(o.startsWith("--"))return n;n+=1}return n}export function resolveToolArgs(t){return{...parseExplicitToolInput(t)??{},...parseToolArgs(t)}}export function parseToolArgs(t){const n={};let o=0;for(;o<t.length&&!t[o]?.startsWith("--");)o++;for(;o<t.length;){const r=t[o];if(!r?.startsWith("--")){o++;continue}const e=r.slice(2),i=t[o+1];if(j.has(e)||$.has(e))o++;else if(S.has(e))o+=i&&!i.startsWith("--")?2:1;else if(!i||i.startsWith("--"))n[e]=!0,o++;else{const t=Number(i);n[e]=Number.isNaN(t)?i:t,o+=2}}return n}function I(t,n){if("string"==typeof t&&0!==t.trim().length)return t;b.error(`缺少必填位置参数:${n}`)}function P(t){const n=t.llm.defaultProvider,o=t.llm.providers[n];return o?c(n,t.llm.defaultModel,o.apiKey,o.baseUrl):void 0}async function B(t){const n=t.agentConnections.get(t.item.agent);if(n)return n;const o=t.store.findByName(t.item.agent);if(!o)return`Agent "${t.item.agent}" 未注册。使用 \`roll agent list\` 查看已注册 Agent。`;b.info(`连接 Agent "${o.skill.name}"...`);const e=r(t.config,o.skill.name),s=a(o),l=await t.clientManager.connect(o.skill.name,s,o.installPath,{...t.samplingModel?{samplingModel:t.samplingModel}:{},...e?{env:e}:{}}),c=g((await l.listTools()).tools),f={agent:o,client:l,tools:c,envReport:i(o.skill.name,o.skill.env,t.config.agents.env)};return t.agentConnections.set(t.item.agent,f),f}async function q(t){const n=R(t.item,t.index);try{const o=await B(t);if("string"==typeof o)return{...n,ok:!1,error:o};const r=o.tools.find(n=>n.name===t.item.tool);if(!r)return{...n,ok:!1,error:p(o.agent.skill.name,t.item.tool,o.tools)};const i=y(r.name)?[]:e(o.envReport),s=u(r,t.item.input,{runtimeIssues:i});if(!s.ok)return{...n,ok:!1,error:f(o.agent.skill.name,t.item.tool,s.issues,s.runtimeIssues)};b.info(`调用 ${o.agent.skill.name}.${t.item.tool}`),b.debug(`调用参数: ${JSON.stringify(w(t.item.input))}`);const l=await o.client.callTool({name:t.item.tool,arguments:t.item.input});return d(l)?{...n,ok:!1,error:"tool 返回 isError=true",result:l}:{...n,ok:!0,result:l}}catch(t){const o=t instanceof Error?t.message:String(t),r=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";return{...n,ok:!1,error:`${o}${r}`}}}function R(t,n){return{index:n,agent:t.agent,tool:t.tool,...t.label?{label:t.label}:{}}}async function D(t){const n=[];for(const[o,r]of t.items.entries()){const e=await q({...t,item:r,index:o});if(n.push(e),!e.ok&&t.bail)break}return n}function K(t){if("object"==typeof t&&null!==t&&"content"in t)for(const n of h(t.content))console.log(n);else"string"!=typeof t?console.log(JSON.stringify(t,null,2)):console.log(t)}function U(t){return t.ok||"result"in t?{...t,result:m(t.result)}:t}function Y(t){for(const n of t){const t=n.label?`[${String(n.index+1)}] ${n.label} (${n.agent}.${n.tool})`:`[${String(n.index+1)}] ${n.agent}.${n.tool}`;console.log(t),n.ok?K(n.result):console.log(n.error)}}
@@ -0,0 +1,18 @@
1
+ declare const _default: import("citty").CommandDef<{
2
+ name: {
3
+ type: "positional";
4
+ description: string;
5
+ required: true;
6
+ };
7
+ json: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: false;
11
+ };
12
+ "include-references": {
13
+ type: "boolean";
14
+ description: string;
15
+ default: false;
16
+ };
17
+ }>;
18
+ export default _default;
@@ -0,0 +1 @@
1
+ import{defineCommand as e}from"citty";import{loadAgentsConfig as n}from"../../config/loader.js";import{AgentStore as o}from"../../registry/store.js";import{resolveAgentSkillDocument as r}from"./skills-utils.js";export default e({meta:{description:"输出指定 Agent 的 skill 文档内容"},args:{name:{type:"positional",description:"Agent 名称",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},"include-references":{type:"boolean",description:"同时输出 SKILL.md 中引用的 references/* 文档",default:!1}},run({args:e}){const{agentsConfig:t}=n(),s=new o(t.dataDir).findByName(e.name);if(!s)return console.error(`✗ Agent "${e.name}" 未找到`),void(process.exitCode=1);const i=r(s,{includeReferences:!0===e["include-references"]});if(e.json)console.log(JSON.stringify(i,null,2));else if(console.log(i.content.trimEnd()),i.references&&i.references.length>0)for(const e of i.references)console.log(`\n--- ${e.relativePath} ---\n`),console.log(e.content.trimEnd())}});
@@ -0,0 +1,8 @@
1
+ declare const _default: import("citty").CommandDef<{
2
+ json: {
3
+ type: "boolean";
4
+ description: string;
5
+ default: false;
6
+ };
7
+ }>;
8
+ export default _default;
@@ -0,0 +1 @@
1
+ import{defineCommand as t}from"citty";import o from"cli-table3";import{loadAgentsConfig as r}from"../../config/loader.js";import{AgentStore as n}from"../../registry/store.js";import{formatLocationForDisplay as i,resolveTerminalColumns as e}from"../utils/terminal.js";import{listAgentSkills as s}from"./skills-utils.js";const l=6,a=2;export default t({meta:{description:"列出已注册 Agent 的 skill 文档来源"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1}},run({args:t}){const{agentsConfig:l}=r(),a=new n(l.dataDir),f=s(a.list());if(t.json)return void console.log(JSON.stringify(f,null,2));if(0===f.length)return void console.log("暂无已注册 Agent skill。可先运行 `roll agent add <path|git-url>`。");const g=c(e()),u=new o({head:["Name","Source","Path","Description"],colWidths:[...g],truncate:"…",style:{head:["cyan"]}});for(const t of f)u.push([t.name,t.source,i(t.path??"(registry snapshot)",m(g[2])),i(t.description,m(g[3]))]);console.log(u.toString())}});function c(t){const o=t-6;return[24,12,Math.max(24,o-24-12-34),34]}function m(t){return Math.max(1,t-2)}
@@ -0,0 +1,13 @@
1
+ declare const _default: import("citty").CommandDef<{
2
+ name: {
3
+ type: "positional";
4
+ description: string;
5
+ required: true;
6
+ };
7
+ json: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: false;
11
+ };
12
+ }>;
13
+ export default _default;
@@ -0,0 +1 @@
1
+ import{defineCommand as e}from"citty";import{loadAgentsConfig as o}from"../../config/loader.js";import{AgentStore as r}from"../../registry/store.js";import{resolveAgentSkillPath as s}from"./skills-utils.js";export default e({meta:{description:"输出指定 Agent 的 SKILL.md 文件路径"},args:{name:{type:"positional",description:"Agent 名称",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1}},run({args:e}){const{agentsConfig:t}=o(),n=new r(t.dataDir).findByName(e.name);if(!n)return console.error(`✗ Agent "${e.name}" 未找到`),void(process.exitCode=1);const i=s(n);e.json?console.log(JSON.stringify({name:n.skill.name,source:i?"filesystem":"registry",path:i??null},null,2)):i?console.log(i):console.error(`✗ Agent "${n.skill.name}" 没有可用的本地 SKILL.md 文件`),i||(process.exitCode=1)}});
@@ -0,0 +1,28 @@
1
+ import type { RegisteredAgent } from "../../types/agent.ts";
2
+ export type SkillDocumentSource = "filesystem" | "registry";
3
+ export interface AgentSkillReferenceDocument {
4
+ readonly relativePath: string;
5
+ readonly path: string;
6
+ readonly content: string;
7
+ }
8
+ export interface AgentSkillDocument {
9
+ readonly name: string;
10
+ readonly description: string;
11
+ readonly source: SkillDocumentSource;
12
+ readonly content: string;
13
+ readonly path?: string;
14
+ readonly references?: readonly AgentSkillReferenceDocument[];
15
+ }
16
+ export interface AgentSkillListItem {
17
+ readonly name: string;
18
+ readonly description: string;
19
+ readonly source: SkillDocumentSource;
20
+ readonly path?: string;
21
+ }
22
+ export interface ResolveAgentSkillDocumentOptions {
23
+ readonly includeReferences?: boolean;
24
+ }
25
+ export declare function getAgentSkillPath(agent: RegisteredAgent): string;
26
+ export declare function resolveAgentSkillPath(agent: RegisteredAgent): string | undefined;
27
+ export declare function resolveAgentSkillDocument(agent: RegisteredAgent, options?: ResolveAgentSkillDocumentOptions): AgentSkillDocument;
28
+ export declare function listAgentSkills(agents: readonly RegisteredAgent[]): readonly AgentSkillListItem[];
@@ -0,0 +1 @@
1
+ import{existsSync as t,readFileSync as e,realpathSync as n,statSync as i}from"node:fs";import{dirname as s,extname as r,isAbsolute as o,normalize as l,relative as c,resolve as a,sep as u}from"node:path";const f="SKILL.md",p=/(?:\.\/)?references\/[A-Za-z0-9._~!$&+,;=:@%/-]+/g,m=new Set([".md",".yaml",".yml",".json",".txt"]);export function getAgentSkillPath(t){return a(t.installPath,f)}export function resolveAgentSkillPath(e){const n=getAgentSkillPath(e);return t(n)?n:void 0}export function resolveAgentSkillDocument(t,n={}){const i=resolveAgentSkillPath(t);if(i){const s=e(i,"utf-8");return{name:t.skill.name,description:t.skill.description,source:"filesystem",path:i,content:s,...n.includeReferences?{references:d(i,s)}:{}}}return{name:t.skill.name,description:t.skill.description,source:"registry",content:h(t),...n.includeReferences?{references:[]}:{}}}export function listAgentSkills(t){return t.map(t=>{const e=resolveAgentSkillPath(t);return{name:t.skill.name,description:t.skill.description,source:e?"filesystem":"registry",...e?{path:e}:{}}})}function h(t){const e=["---",`name: ${k(t.skill.name)}`,`description: ${k(t.skill.description)}`];t.skill.license&&e.push(`license: ${k(t.skill.license)}`),t.skill.compatibility&&e.push(`compatibility: ${k(t.skill.compatibility)}`),e.push("---");const n=t.skillBody?.trim();return`${e.join("\n")}\n\n${n&&n.length>0?n:t.skill.description}\n`}function k(t){return JSON.stringify(t)}function d(t,e){const i=s(t),r=n(i),o=[];for(const t of g(e)){const e=S(i,r,t);e&&o.push(e)}return o}function g(t){const e=new Set;for(const n of t.matchAll(p)){const t=y(n[0]);t&&e.add(t)}return[...e].sort()}function y(t){const e=((t.startsWith("./")?t.slice(2):t).split("#",1)[0]??"").split("?",1)[0]??"",n=l(e);if(0!==n.length&&!o(n)&&!n.startsWith("..")&&"references"!==n&&n.startsWith(`references${u}`)&&m.has(r(n).toLowerCase()))return n}function S(s,r,l){const f=a(s,l);if(!t(f))return;const p=n(f),m=c(r,p);return m.startsWith("..")||o(m)||!i(p).isFile()?void 0:{relativePath:l.split(u).join("/"),path:p,content:e(p,"utf-8")}}
@@ -0,0 +1,2 @@
1
+ declare const _default: import("citty").CommandDef<import("citty").ArgsDef>;
2
+ export default _default;
@@ -0,0 +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,s,i,r,n){return s?e?".jsx":".js":!i||r&&n?i+r+"."+n.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";const s=import.meta.url.endsWith(".ts")?"ts":"js";function i(e){const i=new URL(`./${e}.${s}`,import.meta.url).href;return import(t(i)).then(t=>t.default)}export default e({meta:{description:"读取已注册 Agent 的 SKILL.md(list/get/path)"},subCommands:{list:()=>i("skills-list"),get:()=>i("skills-get"),path:()=>i("skills-path")}});
@@ -6,7 +6,7 @@ declare const _default: import("citty").CommandDef<{
6
6
  description: string;
7
7
  default: false;
8
8
  };
9
- skipBrowserSetup: {
9
+ "skip-browser-setup": {
10
10
  type: "boolean";
11
11
  description: string;
12
12
  default: false;
@@ -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}from"../../config/loader.js";import{getAgentPid as o,startAgent as l,stopAgentGracefully as c,waitForAgentReady as u}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as m}from"../../registry/dev-spawn.js";import{runAgentSetup as p}from"../../registry/runtime-setup.js";import{AgentStore as k}from"../../registry/store.js";import{discoverAgent as d}from"../../registry/discovery.js";import{inferAgentSourceType as f,readInstalledPackageManifest as g,resolveInstalledPackageRoot as $}from"../../registry/source.js";import{McpClientManager as h}from"../../mcp/client-manager.js";import{log as w,createSpinner as y}from"../utils/output.js";import{checkForUpdate as v,checkPublishedPackageUpdate as S,getCurrentVersion as V}from"../utils/update-checker.js";import{detectInstallCommand as b,formatPackageManagerError as j,runPackageManager as B}from"../utils/package-manager.js";const P=n(r);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function D(e,a){switch(e.status){case"needs-migration":{const t="post-update"===a?"升级后需要迁移本地配置":"检测到本地配置需要迁移";w.warn(`${t}: ${e.configPath}`);for(const a of e.report.issues)w.warn(` - ${a.message}`);e.report.canAutoMigrate&&w.info("建议命令: roll config migrate");break}case"invalid":w.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),w.warn(` - ${e.error.message}`),"post-update"===a&&w.info("请修复配置文件后再继续使用相关命令。")}}function A(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function N(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 T(e,a){if("installed-package"!==e.source?.type)return e;const t=$(e.source.installDir,e.source.packageName),r=g(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 E(e,a){const t=V();if(t===e)return w.info(`roll 已是最新版本 (v${t})`),!1;if(w.info(`roll v${t} → v${e}`),a)return w.info("[dry-run] 跳过实际更新"),!0;const r=y("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`]};try{return await B(n,{timeout:6e4}),r.succeed(`roll 已更新到 v${e}`),!0}catch(e){return r.fail("更新失败"),w.error(j(n,e)),!1}}async function C(e){const r=y(`更新 ${e.skill.name} (git pull)...`).start();try{await P("git",["pull"],{cwd:e.installPath,timeout:3e4}),r.succeed(`${e.skill.name} 代码已更新`);const n=a(e.installPath,"package.json");if(t(n)){const a=b(e.installPath);if(a){const t=y(`安装 ${e.skill.name} 依赖...`).start();try{await B(a,{cwd:e.installPath,timeout:6e4}),t.succeed(`${e.skill.name} 依赖已更新 (${a.command} ${a.args.join(" ")})`)}catch(r){return t.fail(`${e.skill.name} 依赖安装失败`),w.error(j(a,r)),!1}}else w.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(a){return r.fail(`${e.skill.name} 更新失败`),w.error(a instanceof Error?a.message:String(a)),!1}}async function M(e){if("installed-package"!==e.source?.type)return;const a=y(`更新 ${e.skill.name} (npm install)...`).start(),r={command:"npm",args:["install","--prefix",e.source.installDir,e.source.packageSpec]};try{await B(r,{timeout:12e4});const n=$(e.source.installDir,e.source.packageName);if(!t(n))throw new Error(`Installed package root not found: ${n}`);const s=g(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 w.error(j(r,t))}}async function x(e){const a=y(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),t=new h;try{const r=m(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} 刷新失败`),w.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:"仅检查可用更新,不执行",default:!1},skipBrowserSetup:{type:"boolean",description:"跳过浏览器运行时安装",default:!1}},async run({args:e}){const a=e.check,t=s();w.info("检查 roll 更新...");const r=await v({forceRefresh:!0});let n,l;r.hasUpdate?w.success(`roll 有新版本: v${r.current} → v${r.latest}`):w.info(`roll 已是最新版本 (v${r.current})`);let u=[];try{n=i().agentsConfig,l=new k(n.dataDir),u=l.list()}catch(e){w.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const m=[];for(const e of u){let a=e;"installed-package"===f(a)&&(a=T(a,l));const t=f(a);switch(t){case"git":m.push({name:a.skill.name,sourceType:t,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==a.source?.type){m.push({name:a.skill.name,sourceType:t,icon:"?",action:"无法检查已安装包版本"});break}const e=await S({packageName:a.source.packageName,packageSpec:a.source.packageSpec,...a.source.installedVersion?{currentVersion:a.source.installedVersion}:{}},{forceRefresh:!0});m.push({name:a.skill.name,sourceType:t,icon:A(e.status),action:N(e,a.source.packageSpec)});break}case"remote-manifest":m.push({name:a.skill.name,sourceType:t,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":m.push({name:a.skill.name,sourceType:t,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(u.length>0){w.info(`\n已注册 Agent (${u.length}):`);for(const e of m)w.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else n&&w.info("无已注册 Agent");if("needs-migration"!==t.status&&"invalid"!==t.status||(w.info(""),D(t,a?"check":"pre-update")),a)return;w.info("");let g=!1,$=!1;r.hasUpdate&&(g=await E(r.latest,!1),g||($=!0)),!g||"needs-migration"!==t.status&&"invalid"!==t.status||(w.info(""),D(t,"post-update"));let h=0,y=0;for(const a of u){if(!l||!n)break;switch(f(a)){case"git":{const e="core-managed"===a.runtime.ownership&&void 0!==o(n.dataDir,a.skill.name);if(await C(a))try{const t=d(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};l.replace(a.skill.name,r)?(await R(r,e,n.dataDir),h++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),y++}else y++;break}case"installed-package":{const t="core-managed"===a.runtime.ownership&&void 0!==o(n.dataDir,a.skill.name);if(t)try{await c(n.dataDir,a.skill.name)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),y++;break}const r=await M(a);if(r)try{const s=d(r.packageRoot),i="installed-package"===a.source?.type?{...a.source,packageName:r.packageName,...r.installedVersion?{installedVersion:r.installedVersion}:{}}:void 0,o={...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 p(o,{skipBrowserSetup:e.skipBrowserSetup});c.ok||(w.warn(`${o.skill.name} setup 失败:${c.message}`),c.retryCommand&&w.info(`重试命令: ${c.retryCommand}`));l.replace(a.skill.name,o)?c.ok?(t&&await I(o,n.dataDir),h++):(l.updateStatus(o.skill.name,"error"),y++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),y++}else{if(t)try{await I(a,n.dataDir)}catch{l.updateStatus(a.skill.name,"error")}y++}break}case"remote-manifest":try{const e=d(a.installPath),t={...a,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!l.replace(a.skill.name,t)){w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++;break}await x(t)?h++:y++}catch(e){w.warn(`${a.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),y++}break;case"local-path":{const e="core-managed"===a.runtime.ownership&&void 0!==o(n.dataDir,a.skill.name);try{const t=d(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};l.replace(a.skill.name,r)?(await R(r,e,n.dataDir),h++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),y++}break}}}w.info("");if(y+($?1:0)>0){process.exitCode=1;const e=$?"roll 更新失败":g?"roll ✓":"roll 无更新";return void w.warn(`更新完成但有失败:${e}${y>0?`,${y} 个 Agent 更新失败`:""}${h>0?`,${h} 个 Agent 已更新`:""}`)}g||h>0?w.success(`更新完成:${g?"roll ✓":"roll 无更新"}${h>0?`,${h} 个 Agent 已更新`:""}`):w.success("一切都已是最新版本")}});async function R(e,a,t){a&&"core-managed"===e.runtime.ownership&&(await c(t,e.skill.name),await I(e,t))}async function I(e,a){let t=!1;try{l(e,a),t=!0,await u(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(r){throw t&&await c(a,e.skill.name).catch(()=>{}),r}}
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}from"../../config/loader.js";import{getAgentPid as o,startAgent as l,stopAgentGracefully as c,waitForAgentReady as u}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as m}from"../../registry/dev-spawn.js";import{runAgentSetup as p}from"../../registry/runtime-setup.js";import{AgentStore as k}from"../../registry/store.js";import{discoverAgent as d}from"../../registry/discovery.js";import{inferAgentSourceType as g,readInstalledPackageManifest as f,resolveInstalledPackageRoot as $}from"../../registry/source.js";import{McpClientManager as h}from"../../mcp/client-manager.js";import{log as w,createSpinner as y}from"../utils/output.js";import{checkForUpdate as v,checkPublishedPackageUpdate as S,getCurrentVersion as V}from"../utils/update-checker.js";import{detectInstallCommand as b,formatPackageManagerError as P,runPackageManager as j}from"../utils/package-manager.js";const B=n(r);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function A(e,a){switch(e.status){case"needs-migration":{const t="post-update"===a?"升级后需要迁移本地配置":"检测到本地配置需要迁移";w.warn(`${t}: ${e.configPath}`);for(const a of e.report.issues)w.warn(` - ${a.message}`);e.report.canAutoMigrate&&w.info("建议命令: roll config migrate");break}case"invalid":w.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),w.warn(` - ${e.error.message}`),"post-update"===a&&w.info("请修复配置文件后再继续使用相关命令。")}}function D(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function N(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 T(e,a){if("installed-package"!==e.source?.type)return e;const t=$(e.source.installDir,e.source.packageName),r=f(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 E(e,a){const t=V();if(t===e)return w.info(`roll 已是最新版本 (v${t})`),!1;if(w.info(`roll v${t} → v${e}`),a)return w.info("[dry-run] 跳过实际更新"),!0;const r=y("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`]};try{return await j(n,{timeout:6e4}),r.succeed(`roll 已更新到 v${e}`),!0}catch(e){return r.fail("更新失败"),w.error(P(n,e)),!1}}async function C(e){const r=y(`更新 ${e.skill.name} (git pull)...`).start();try{await B("git",["pull"],{cwd:e.installPath,timeout:3e4}),r.succeed(`${e.skill.name} 代码已更新`);const n=a(e.installPath,"package.json");if(t(n)){const a=b(e.installPath);if(a){const t=y(`安装 ${e.skill.name} 依赖...`).start();try{await j(a,{cwd:e.installPath,timeout:6e4}),t.succeed(`${e.skill.name} 依赖已更新 (${a.command} ${a.args.join(" ")})`)}catch(r){return t.fail(`${e.skill.name} 依赖安装失败`),w.error(P(a,r)),!1}}else w.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(a){return r.fail(`${e.skill.name} 更新失败`),w.error(a instanceof Error?a.message:String(a)),!1}}async function M(e){if("installed-package"!==e.source?.type)return;const a=y(`更新 ${e.skill.name} (npm install)...`).start(),r={command:"npm",args:["install","--prefix",e.source.installDir,e.source.packageSpec]};try{await j(r,{timeout:12e4});const n=$(e.source.installDir,e.source.packageName);if(!t(n))throw new Error(`Installed package root not found: ${n}`);const s=f(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 w.error(P(r,t))}}async function x(e){const a=y(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),t=new h;try{const r=m(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} 刷新失败`),w.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();w.info("检查 roll 更新...");const r=await v({forceRefresh:!0});let n,l;r.hasUpdate?w.success(`roll 有新版本: v${r.current} → v${r.latest}`):w.info(`roll 已是最新版本 (v${r.current})`);let u=[];try{n=i().agentsConfig,l=new k(n.dataDir),u=l.list()}catch(e){w.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const m=[];for(const e of u){let a=e;"installed-package"===g(a)&&(a=T(a,l));const t=g(a);switch(t){case"git":m.push({name:a.skill.name,sourceType:t,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==a.source?.type){m.push({name:a.skill.name,sourceType:t,icon:"?",action:"无法检查已安装包版本"});break}const e=await S({packageName:a.source.packageName,packageSpec:a.source.packageSpec,...a.source.installedVersion?{currentVersion:a.source.installedVersion}:{}},{forceRefresh:!0});m.push({name:a.skill.name,sourceType:t,icon:D(e.status),action:N(e,a.source.packageSpec)});break}case"remote-manifest":m.push({name:a.skill.name,sourceType:t,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":m.push({name:a.skill.name,sourceType:t,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(u.length>0){w.info(`\n已注册 Agent (${u.length}):`);for(const e of m)w.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else n&&w.info("无已注册 Agent");if("needs-migration"!==t.status&&"invalid"!==t.status||(w.info(""),A(t,a?"check":"pre-update")),a)return;w.info("");let f=!1,$=!1;r.hasUpdate&&(f=await E(r.latest,!1),f||($=!0)),!f||"needs-migration"!==t.status&&"invalid"!==t.status||(w.info(""),A(t,"post-update"));let h=0,y=0;for(const a of u){if(!l||!n)break;switch(g(a)){case"git":{const e="core-managed"===a.runtime.ownership&&void 0!==o(n.dataDir,a.skill.name);if(await C(a))try{const t=d(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};l.replace(a.skill.name,r)?(await R(r,e,n.dataDir),h++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),y++}else y++;break}case"installed-package":{const t="core-managed"===a.runtime.ownership&&void 0!==o(n.dataDir,a.skill.name);if(t)try{await c(n.dataDir,a.skill.name)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),y++;break}const r=await M(a);if(r)try{const s=d(r.packageRoot),i="installed-package"===a.source?.type?{...a.source,packageName:r.packageName,...r.installedVersion?{installedVersion:r.installedVersion}:{}}:void 0,o={...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 p(o,{skipBrowserSetup:e["skip-browser-setup"]});c.ok||(w.warn(`${o.skill.name} setup 失败:${c.message}`),c.retryCommand&&w.info(`重试命令: ${c.retryCommand}`));l.replace(a.skill.name,o)?c.ok?(t&&await I(o,n.dataDir),h++):(l.updateStatus(o.skill.name,"error"),y++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),y++}else{if(t)try{await I(a,n.dataDir)}catch{l.updateStatus(a.skill.name,"error")}y++}break}case"remote-manifest":try{const e=d(a.installPath),t={...a,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!l.replace(a.skill.name,t)){w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++;break}await x(t)?h++:y++}catch(e){w.warn(`${a.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),y++}break;case"local-path":{const e="core-managed"===a.runtime.ownership&&void 0!==o(n.dataDir,a.skill.name);try{const t=d(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};l.replace(a.skill.name,r)?(await R(r,e,n.dataDir),h++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){l.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),y++}break}}}w.info("");if(y+($?1:0)>0){process.exitCode=1;const e=$?"roll 更新失败":f?"roll ✓":"roll 无更新";return void w.warn(`更新完成但有失败:${e}${y>0?`,${y} 个 Agent 更新失败`:""}${h>0?`,${h} 个 Agent 已更新`:""}`)}f||h>0?w.success(`更新完成:${f?"roll ✓":"roll 无更新"}${h>0?`,${h} 个 Agent 已更新`:""}`):w.success("一切都已是最新版本")}});async function R(e,a,t){a&&"core-managed"===e.runtime.ownership&&(await c(t,e.skill.name),await I(e,t))}async function I(e,a){let t=!1;try{l(e,a),t=!0,await u(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(r){throw t&&await c(a,e.skill.name).catch(()=>{}),r}}
package/dist/cli/index.js CHANGED
@@ -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,o,n,a){return r?e?".jsx":".js":!o||n&&a?o+n+"."+a.toLowerCase()+"js":t}):t};import{defineCommand as e,runMain as r}from"citty";import o from"chalk";import{checkForUpdate as n,getCurrentVersion as a}from"./utils/update-checker.js";import{resolveLogLevelFromArgv as s,setLogLevel as c}from"./utils/output.js";const i=a(),l=import.meta.url.endsWith(".ts")?"ts":"js";function u(e){const r=new URL(`./commands/${e}.${l}`,import.meta.url).href;return import(t(r)).then(t=>t.default)}const m=e({meta:{name:"roll",version:i,description:"花卷 Agent — 轻量级 Agent 编排系统"},args:{verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},subCommands:{agent:()=>u("agent"),run:()=>u("run"),ask:()=>u("ask"),chat:()=>u("chat"),config:()=>u("config"),doctor:()=>u("doctor"),update:()=>u("update")}}),p=n({allowNetwork:!1}).catch(()=>{});c(s(process.argv.slice(2))),r(m).then(()=>{p.then(t=>{t?.hasUpdate&&console.error(`\n${o.yellow("⬆")} roll ${o.green(`v${t.latest}`)} available (current: v${t.current}). Run ${o.cyan("roll update")} to upgrade.`)}).catch(()=>{})});
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,o,s,n){return r?e?".jsx":".js":!o||s&&n?o+s+"."+n.toLowerCase()+"js":t}):t};import{defineCommand as e,runMain as r}from"citty";import o from"chalk";import{checkForUpdate as s,getCurrentVersion as n}from"./utils/update-checker.js";import{resolveLogLevelFromArgv as a,setLogLevel as i}from"./utils/output.js";const c=n(),l=import.meta.url.endsWith(".ts")?"ts":"js";function u(e){const r=new URL(`./commands/${e}.${l}`,import.meta.url).href;return import(t(r)).then(t=>t.default)}const m=e({meta:{name:"roll",version:c,description:"花卷 Agent — 轻量级 Agent 编排系统"},args:{verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},subCommands:{agent:()=>u("agent"),run:()=>u("run"),ask:()=>u("ask"),chat:()=>u("chat"),config:()=>u("config"),skills:()=>u("skills"),doctor:()=>u("doctor"),update:()=>u("update")}}),p=s({allowNetwork:!1}).catch(()=>{});i(a(process.argv.slice(2))),r(m).then(()=>{p.then(t=>{t?.hasUpdate&&console.error(`\n${o.yellow("⬆")} roll ${o.green(`v${t.latest}`)} available (current: v${t.current}). Run ${o.cyan("roll update")} to upgrade.`)}).catch(()=>{})});
@@ -1,8 +1,35 @@
1
1
  import type { RegisteredAgent } from "../types/agent.ts";
2
+ declare const RUNTIME_SIDECAR_SCHEMA_VERSION: 1;
3
+ export type ManagedAgentRuntimeIssueCode = "missing-sidecar" | "invalid-sidecar" | "orphan-sidecar" | "pid-mismatch" | "version-mismatch" | "endpoint-mismatch";
4
+ export interface ManagedAgentRuntimeSidecar {
5
+ readonly schemaVersion: typeof RUNTIME_SIDECAR_SCHEMA_VERSION;
6
+ readonly agentName: string;
7
+ readonly pid: number;
8
+ readonly coreVersion: string;
9
+ readonly startedAt: string;
10
+ readonly endpoint?: string;
11
+ }
12
+ export interface ManagedAgentRuntimeIssue {
13
+ readonly code: ManagedAgentRuntimeIssueCode;
14
+ readonly message: string;
15
+ readonly fix: string;
16
+ }
17
+ export interface ManagedAgentRuntimeInspection {
18
+ readonly pid?: number;
19
+ readonly sidecar?: ManagedAgentRuntimeSidecar;
20
+ readonly expectedCoreVersion: string;
21
+ readonly expectedEndpoint?: string;
22
+ readonly issues: readonly ManagedAgentRuntimeIssue[];
23
+ }
24
+ /** 仅在没有活动 PID 时清理 runtime 元数据。 */
25
+ export declare function cleanupOrphanAgentRuntimeMetadata(dataDir: string, agentName: string): boolean;
2
26
  /** Agent 日志文件路径 */
3
27
  export declare function getAgentLogPath(dataDir: string, agentName: string): string;
4
28
  /** 读取 Agent 的 PID,如果不存在或进程已死则返回 undefined */
5
29
  export declare function getAgentPid(dataDir: string, agentName: string): number | undefined;
30
+ export declare function getRollCoreVersion(): string;
31
+ export declare function writeAgentRuntimeSidecar(agent: RegisteredAgent, dataDir: string, pid: number): void;
32
+ export declare function inspectManagedAgentRuntime(agent: RegisteredAgent, dataDir: string): ManagedAgentRuntimeInspection;
6
33
  /** 检查 core-managed Agent 对应的 MCP endpoint 是否已就绪。 */
7
34
  export declare function probeAgentEndpoint(agent: RegisteredAgent, options?: {
8
35
  readonly timeoutMs?: number;
@@ -26,3 +53,4 @@ export declare function stopAgentGracefully(dataDir: string, agentName: string,
26
53
  readonly timeoutMs?: number;
27
54
  readonly intervalMs?: number;
28
55
  }): Promise<boolean>;
56
+ export {};
@@ -1 +1 @@
1
- import{spawn as t}from"node:child_process";import{closeSync as n,existsSync as r,mkdirSync as e,openSync as o,readFileSync as i,unlinkSync as a,writeFileSync as s}from"node:fs";import{resolve as c,dirname as m}from"node:path";import{McpClientManager as u}from"../mcp/client-manager.js";import{resolveDevSpawnSpec as p}from"./dev-spawn.js";import{inferAgentSourceType as l}from"./source.js";function d(t,n){return c(t,"pids",`${n}.pid`)}function g(t,n){const e=d(t,n);r(e)&&a(e)}export function getAgentLogPath(t,n){return c(t,"logs",`${n}.log`)}function f(t){try{return process.kill(t,0),!0}catch{return!1}}export function getAgentPid(t,n){const e=d(t,n);if(!r(e))return;const o=Number(i(e,"utf-8").trim());if(!Number.isNaN(o)&&f(o))return o;g(t,n)}export async function probeAgentEndpoint(t,n={}){const r=new u;try{const e=await r.connect(t.skill.name,t.transport,t.installPath,void 0!==n.timeoutMs?{timeoutMs:n.timeoutMs}:{});await e.listTools()}finally{await r.disconnectAll()}}export async function waitForAgentReady(t,n={}){const r=n.startupTimeoutMs??15e3,e=n.probeTimeoutMs??2e3,o=n.intervalMs??500,i=Date.now()+r;let a;for(;Date.now()<i;)try{return void await probeAgentEndpoint(t,{timeoutMs:e})}catch(t){a=t,await h(o)}throw new Error(`Agent "${t.skill.name}" did not become ready within ${r}ms${a?`: ${a instanceof Error?a.message:String(a)}`:""}`)}export function startAgent(i,a,c){if("streamable-http"===i.transport.type&&"core-managed"!==i.runtime.ownership)throw new Error(`Agent "${i.skill.name}" 使用 streamable-http 传输且非 core-managed,请手动启动服务。\n 端点: ${i.transport.endpoint}`);const u=getAgentPid(a,i.skill.name);if(void 0!==u)throw new Error(`Agent "${i.skill.name}" 已在运行 (PID: ${String(u)})`);const p=w(i),l=getAgentLogPath(a,i.skill.name),g=m(l);r(g)||e(g,{recursive:!0});const f=o(l,"a"),h=t(p.command,[...p.args??[]],{cwd:i.installPath,detached:!0,stdio:["ignore",f,f],...c?{env:{...process.env,...c}}:{}});if(n(f),h.unref(),!h.pid)throw new Error(`Failed to start agent "${i.skill.name}"`);const A=d(a,i.skill.name),y=m(A);return r(y)||e(y,{recursive:!0}),s(A,String(h.pid),"utf-8"),h.pid}export function stopAgent(t,n){const r=getAgentPid(t,n);if(void 0===r)return!1;try{process.kill(r,"SIGTERM")}catch{}return g(t,n),!0}export async function stopAgentGracefully(t,n,r={}){const e=getAgentPid(t,n);if(void 0===e)return!1;try{process.kill(e,"SIGTERM")}catch{return g(t,n),!0}const o=r.timeoutMs??15e3,i=r.intervalMs??200,a=Date.now()+o;for(;Date.now()<a;){if(!f(e))return g(t,n),!0;await h(i)}throw new Error(`Agent "${n}" did not stop within ${o}ms`)}function w(t){if("stdio"===t.transport.type){const n=p(t.transport.command,t.transport.args,t.installPath,l(t));return n||{command:t.transport.command,...t.transport.args?{args:t.transport.args}:{}}}if("core-managed"===t.runtime.ownership){const n=p(t.runtime.start.command,t.runtime.start.args,t.installPath,l(t));return n||{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 h(t){return new Promise(n=>{setTimeout(n,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 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)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/core",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",