@roll-agent/core 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{defineCommand as o}from"citty";import{getAgentEnvFromAgentsConfig as e,inspectAgentEnvRequirements as s}from"../../config/helpers.js";import{formatAgentEnvDeclarationSource as n,formatAgentEnvRuntimeStatus as t,formatAgentRuntimeVerification as l,inspectAgentRuntimeEnvRequirements as r}from"../../config/runtime-env.js";import{loadAgentsConfig as i}from"../../config/loader.js";import{inspectAgentRuntimeEnv as c}from"../../mcp/agent-diagnostics.js";import{formatAgentSourceType as a,getAgentLocation as g,inferAgentSourceType as m}from"../../registry/source.js";import{AgentStore as p}from"../../registry/store.js";export default o({meta:{description:"查看 Agent 详情"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:o}){const{agentsConfig:f}=i(),$=new p(f.dataDir).findByName(o.name);if(!$)return console.error(`✗ Agent "${o.name}" 未找到`),void(process.exitCode=1);if(console.log(`名称: ${$.skill.name}`),console.log(`描述: ${$.skill.description}`),console.log(`状态: ${$.status}`),console.log(`来源: ${a(m($))}`),console.log(`传输: ${$.transport.type}`),console.log(`位置: ${g($)}`),console.log(`注册时间: ${$.registeredAt}`),"installed-package"===$.source?.type&&(console.log(`安装包: ${$.source.packageSpec}`),console.log(`安装目录: ${$.source.installDir}`)),Object.keys($.skill.metadata).length>0){console.log("元数据:");for(const[o,e]of Object.entries($.skill.metadata))console.log(` ${o}: ${e}`)}const d=s($.skill.name,$.skill.env,f.env);if(d){const o=await c($,{agentsConfig:f}),s=r(d,e(f,$.skill.name),o);console.log("环境变量:"),console.log(` 运行态校验: ${l(s)}`);for(const o of s.items){const e=n(o),s=o.required?"必填":"可选",l=t(o);console.log(` ${o.name}: [${s}] ${e}${l?`;运行态: ${l}`:""}`),o.purpose&&console.log(` 用途: ${o.purpose}`)}}}});
1
+ import{defineCommand as o}from"citty";import{getAgentEnv as e,getAgentEnvFromAgentsConfig as s,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{formatAgentEnvDeclarationSource as t,formatAgentEnvRuntimeStatus as r,formatAgentRuntimeVerification as l,inspectAgentRuntimeEnvRequirements as i}from"../../config/runtime-env.js";import{loadAgentsConfig as c,loadConfig as a}from"../../config/loader.js";import{inspectAgentRuntimeEnv as g}from"../../mcp/agent-diagnostics.js";import{formatAgentSourceType as m,getAgentLocation as p,inferAgentSourceType as f}from"../../registry/source.js";import{AgentStore as $}from"../../registry/store.js";export default o({meta:{description:"查看 Agent 详情"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:o}){const{agentsConfig:a}=c(),u=new $(a.dataDir).findByName(o.name);if(!u)return console.error(`✗ Agent "${o.name}" 未找到`),void(process.exitCode=1);if(console.log(`名称: ${u.skill.name}`),console.log(`描述: ${u.skill.description}`),console.log(`状态: ${u.status}`),console.log(`来源: ${m(f(u))}`),console.log(`传输: ${u.transport.type}`),console.log(`位置: ${p(u)}`),console.log(`注册时间: ${u.registeredAt}`),"installed-package"===u.source?.type&&(console.log(`安装包: ${u.source.packageSpec}`),console.log(`安装目录: ${u.source.installDir}`)),Object.keys(u.skill.metadata).length>0){console.log("元数据:");for(const[o,e]of Object.entries(u.skill.metadata))console.log(` ${o}: ${e}`)}const k=d(),y=n(u.skill.name,u.skill.env,a.env);if(y){const o=await g(u,void 0!==k?{config:k}:{agentsConfig:a}),n=i(y,void 0!==k?e(k,u.skill.name):s(a,u.skill.name),o);console.log("环境变量:"),console.log(` 运行态校验: ${l(n)}`);for(const o of n.items){const e=t(o),s=o.required?"必填":"可选",n=r(o);console.log(` ${o.name}: [${s}] ${e}${n?`;运行态: ${n}`:""}`),o.purpose&&console.log(` 用途: ${o.purpose}`)}}}});function d(){try{return a().config}catch{return}}
@@ -1 +1 @@
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
+ import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as r,statSync as i}from"node:fs";import{resolve as s}from"node:path";import{getAgentEnv as n,inspectAgentEnvRequirements as o}from"../../config/helpers.js";import{loadAgentsConfig as a,loadConfig as l}from"../../config/loader.js";import{discoverAgent as m}from"../../registry/discovery.js";import{startAgent as p,stopAgentGracefully as c,waitForAgentReady as d}from"../../registry/process-manager.js";import{runAgentSetup as g}from"../../registry/runtime-setup.js";import{AgentStore as u}from"../../registry/store.js";import{parsePackageName as f,readInstalledPackageManifest as k,resolveInstalledPackageRoot as y,sanitizeInstallId as h}from"../../registry/source.js";import{log as w}from"../utils/output.js";import{formatPackageManagerError as v,runPackageManager as $}from"../utils/package-manager.js";function j(e){return e.startsWith("git@")||e.startsWith("git+")||e.startsWith("github:")||e.startsWith("gitlab:")||e.startsWith("bitbucket:")||e.endsWith(".git")}export default e({meta:{description:"安装已发布的 Agent npm 包并注册到本地"},args:{package:{type:"positional",description:"npm 包名、版本范围或 .tgz 路径(源码目录请用 roll agent add)",required:!0},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1},start:{type:"boolean",description:"安装后不自动启动 core-managed Agent",default:!0}},async run({args:e}){const{agentsConfig:o}=a(),S=e.package;if(j(S))return w.error(`Git URL 请使用 \`roll agent add ${S}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const C=s(S);if(t(C)&&i(C).isDirectory())return w.error(`本地源码目录请使用 \`roll agent add ${S}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const x=f(S),A=s(o.dataDir,"installed",h(x));t(A)||r(A,{recursive:!0}),w.info(`安装 ${S}...`);const D={command:"npm",args:["install","--prefix",A,S]};try{await $(D,{timeout:12e4})}catch(e){return w.error(`安装失败: ${v(D,e)}`),void(process.exitCode=1)}const W=y(A,x);if(!t(W))return w.error(`安装完成但未找到包目录: ${W}`),void(process.exitCode=1);const q=k(W);w.info("解析已安装 Agent 的 SKILL.md...");const B=m(W),R=new u(o.dataDir),E={skill:B.skill,transport:B.transport,runtime:B.runtime,installPath:W,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:q?.name??x,packageSpec:S,installDir:A,...q?.version?{installedVersion:q.version}:{}},...B.skillBody.length>0?{skillBody:B.skillBody}:{}};"core-managed"===E.runtime.ownership&&E.runtime.setup?.playwright&&!e["skip-browser-setup"]&&w.info(`即将安装浏览器运行时 (${E.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const L=await g(E,{skipBrowserSetup:e["skip-browser-setup"]});L.ok?L.skipped?w.info(L.message):w.success(L.message):(w.warn(`Agent setup 失败:${L.message}`),L.retryCommand&&w.info(`重试命令: ${L.retryCommand}`));const O=R.findByName(B.skill.name);try{const t="core-managed"===O?.runtime.ownership&&"online"===O.status;if("installed-package"===O?.source?.type?R.replace(O.skill.name,E):R.add(E),!L.ok)return R.updateStatus(B.skill.name,"error"),void(process.exitCode=1);if("core-managed"===E.runtime.ownership&&e.start){t&&await c(o.dataDir,E.skill.name),R.updateStatus(E.skill.name,"starting");let e=!1;try{p(E,o.dataDir,n(l().config,E.skill.name)),e=!0,await d(E,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),R.updateStatus(E.skill.name,"online")}catch(t){return e&&await c(o.dataDir,E.skill.name).catch(()=>{}),R.updateStatus(E.skill.name,"error"),w.error(`Agent "${B.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}w.success(`Agent "${B.skill.name}" 安装并注册成功`),b(B.skill.name,B.skill.env,o.env)}catch(e){w.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function b(e,t,r){const i=o(e,t,r);if(i)return i.missingRequired.length>0?(w.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),void w.info(`请在 roll.config.yaml 的 agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(w.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),w.info(`建议将这些项写入 roll.config.yaml 的 agents.env.${e}。`)))}
@@ -0,0 +1,67 @@
1
+ import type { BrowserConfig } from "../../config/schema.ts";
2
+ declare const BROWSER_DATA_KINDS: readonly ["profiles", "sessions"];
3
+ type BrowserDataKind = (typeof BROWSER_DATA_KINDS)[number];
4
+ export interface BrowserDataClearTarget {
5
+ readonly instanceId: string;
6
+ readonly kind: BrowserDataKind;
7
+ readonly path: string;
8
+ readonly exists: boolean;
9
+ }
10
+ export interface BrowserDataClearResult extends BrowserDataClearTarget {
11
+ readonly status: "deleted" | "missing" | "skipped" | "failed";
12
+ readonly message?: string;
13
+ }
14
+ interface BrowserDataClearOptions {
15
+ readonly instanceId?: string;
16
+ readonly kinds: readonly BrowserDataKind[];
17
+ }
18
+ interface BrowserDataClearSafetyOptions {
19
+ readonly protectedSubtreeRoots?: readonly string[];
20
+ }
21
+ declare const _default: import("citty").CommandDef<{
22
+ instance: {
23
+ type: "positional";
24
+ description: string;
25
+ required: false;
26
+ };
27
+ profiles: {
28
+ type: "boolean";
29
+ description: string;
30
+ default: false;
31
+ };
32
+ sessions: {
33
+ type: "boolean";
34
+ description: string;
35
+ default: false;
36
+ };
37
+ all: {
38
+ type: "boolean";
39
+ description: string;
40
+ default: false;
41
+ };
42
+ yes: {
43
+ type: "boolean";
44
+ alias: string;
45
+ description: string;
46
+ default: false;
47
+ };
48
+ force: {
49
+ type: "boolean";
50
+ description: string;
51
+ default: false;
52
+ };
53
+ json: {
54
+ type: "boolean";
55
+ description: string;
56
+ default: false;
57
+ };
58
+ }>;
59
+ export default _default;
60
+ export declare function resolveBrowserDataKinds(input: {
61
+ readonly all: boolean;
62
+ readonly profiles: boolean;
63
+ readonly sessions: boolean;
64
+ }): readonly BrowserDataKind[];
65
+ export declare function buildBrowserDataClearPlan(browserConfig: BrowserConfig, options: BrowserDataClearOptions): readonly BrowserDataClearTarget[];
66
+ export declare function clearBrowserDataTargets(targets: readonly BrowserDataClearTarget[], options?: BrowserDataClearSafetyOptions): readonly BrowserDataClearResult[];
67
+ export declare function assertSafeBrowserDataPath(path: string, options?: BrowserDataClearSafetyOptions): void;
@@ -0,0 +1 @@
1
+ import{defineCommand as s}from"citty";import{existsSync as e,lstatSync as r,rmSync as o}from"node:fs";import{homedir as t}from"node:os";import{isAbsolute as n,join as i,parse as a,relative as l,resolve as c}from"node:path";import{loadConfig as f}from"../../config/loader.js";import{getAgentPid as u}from"../../registry/process-manager.js";import{log as d}from"../utils/output.js";const p="browser-use-agent",g=i(t(),".roll-agent","browser","sessions"),w=["profiles","sessions"];export default s({meta:{description:"清理 browser.instances 声明的 Chrome profile / Roll session 数据"},args:{instance:{type:"positional",description:"可选 browserInstance;不传则处理所有已声明实例",required:!1},profiles:{type:"boolean",description:"只清理 Chrome profile 目录(browser.instances[*].user-data-dir)",default:!1},sessions:{type:"boolean",description:"只清理 Roll session 快照目录(browser.instances[*].sessions-dir)",default:!1},all:{type:"boolean",description:"清理 profile + sessions;未指定 --profiles/--sessions 时默认就是 all",default:!1},yes:{type:"boolean",alias:"y",description:"确认执行删除;不加时只输出 dry-run 计划",default:!1},force:{type:"boolean",description:"browser-use-agent 仍在运行时也继续删除(不建议)",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){try{const{config:e}=f(),r=$(s.instance,"instance"),o=resolveBrowserDataKinds({all:!0===s.all,profiles:!0===s.profiles,sessions:!0===s.sessions}),t=buildBrowserDataClearPlan(e.browser,{...void 0!==r?{instanceId:r}:{},kinds:o});if(!0===s.yes){const r=u(e.agents.dataDir,p);if(void 0!==r&&!0!==s.force){const e=`${p} 仍在运行 (PID: ${String(r)}),请先运行 \`roll agent stop browser-use-agent\`,或确认风险后加 --force`;return s.json?console.log(JSON.stringify({ok:!1,message:e,targets:t},null,2)):d.error(e),void(process.exitCode=1)}const o=clearBrowserDataTargets(t,{protectedSubtreeRoots:[e.agents.dataDir]});return s.json?console.log(JSON.stringify({ok:o.every(s=>"failed"!==s.status),results:o},null,2)):v(o),void(o.some(s=>"failed"===s.status)&&(process.exitCode=1))}s.json?console.log(JSON.stringify({dryRun:!0,targets:t},null,2)):D(t)}catch(e){const r=e instanceof Error?e.message:String(e);s.json?console.log(JSON.stringify({ok:!1,message:r},null,2)):d.error(r),process.exitCode=1}}});export function resolveBrowserDataKinds(s){return s.all||!s.profiles&&!s.sessions?w:[...s.profiles?["profiles"]:[],...s.sessions?["sessions"]:[]]}export function buildBrowserDataClearPlan(s,e){if(0===Object.entries(s.instances).length)return[];return b(s,e.instanceId).flatMap(([s,r])=>{const o=[];return e.kinds.includes("profiles")&&o.push(y(s,"profiles",r.userDataDir)),e.kinds.includes("sessions")&&o.push(y(s,"sessions",r.sessionsDir??i(g,s))),o})}export function clearBrowserDataTargets(s,t={}){const n=new Set;return s.map(s=>{try{if(assertSafeBrowserDataPath(s.path,t),n.has(s.path))return{...s,status:"skipped",message:"同一路径已在本次命令中处理"};if(n.add(s.path),!e(s.path))return{...s,status:"missing"};const i=r(s.path);if(!i.isDirectory()&&!i.isSymbolicLink())throw new Error(`拒绝删除非目录路径: ${s.path}`);return o(s.path,{recursive:!0,force:!0}),{...s,exists:!1,status:"deleted"}}catch(e){return{...s,status:"failed",message:e instanceof Error?e.message:String(e)}}})}export function assertSafeBrowserDataPath(s,e={}){const r=c(s),o=a(r).root,n=c(t(),".roll-agent"),i=c(n,"browser"),l=[o,c(t()),c(process.cwd()),n,i,c(i,"profiles"),c(i,"sessions")];for(const s of l)if(m(r,s))throw new Error(`拒绝删除危险路径: ${r}`);for(const s of e.protectedSubtreeRoots??[])if(h(r,c(s)))throw new Error(`拒绝删除受保护数据目录下的路径: ${r}`)}function m(s,e){return h(e,s)}function h(s,e){const r=l(e,s);return""===r||!r.startsWith("..")&&!n(r)}function b(s,e){if(void 0===e)return Object.entries(s.instances);const r=s.instances[e];if(void 0===r){const r=Object.keys(s.instances);throw new Error(`browserInstance "${e}" 未声明`+(r.length>0?`;可用实例: ${r.join(", ")}`:""))}return[[e,r]]}function y(s,r,o){const t=c(o);return{instanceId:s,kind:r,path:t,exists:e(t)}}function $(s,e){if(void 0===s)return;if("string"!=typeof s)throw new Error(`${e} 必须是字符串`);const r=s.trim();return r.length>0?r:void 0}function D(s){if(0!==s.length){d.info("Browser data clear plan (dry-run, 未删除任何文件):");for(const e of s)d.info(S(e));d.info("确认无误后追加 --yes 执行删除;运行中的 browser-use-agent 建议先 stop。")}else d.warn("未配置 browser.instances,没有可清理的 browser 数据目录。")}function v(s){if(0===s.length)return void d.warn("未配置 browser.instances,没有可清理的 browser 数据目录。");for(const e of s){const s=`${S(e)} -> ${e.status}${void 0!==e.message?` (${e.message})`:""}`;"failed"===e.status?d.error(s):d.info(s)}0===s.filter(s=>"failed"===s.status).length&&d.success("Browser 数据清理完成。")}function S(s){return`${s.instanceId} ${s.kind}: ${s.path} [${s.exists?"exists":"missing"}]`}
@@ -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,r,s,n,o){return r?e?".jsx":".js":!s||n&&o?s+n+"."+o.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";const r=import.meta.url.endsWith(".ts")?"ts":"js";function s(e){const s=new URL(`./${e}.${r}`,import.meta.url).href;return import(t(s)).then(t=>t.default)}export default e({meta:{description:"管理浏览器运行时数据"},subCommands:{"clear-data":()=>s("browser-clear-data")}});
@@ -3,6 +3,7 @@ export interface CheckResult {
3
3
  readonly status: "ok" | "warn" | "fail";
4
4
  readonly message: string;
5
5
  readonly fix?: string;
6
+ readonly details?: unknown;
6
7
  }
7
8
  export interface DoctorFixResult {
8
9
  readonly name: string;
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as s,writeFileSync as n}from"node:fs";import{stringify as o}from"yaml";import{getAgentEnvFromAgentsConfig as a,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{inspectConfigFile as r,loadAgentsConfig as l,loadConfig as m,parseConfigDocument as u,validateConfigText as g}from"../../config/loader.js";import{applyKnownConfigMigrations as c}from"../../config/migration.js";import{inspectAgentRuntimeEnvRequirements as f,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 $}from"../../registry/store.js";const k={ok:"✓",warn:"⚠",fail:"✗"},y={applied:"✓",skipped:"•",failed:"✗"},w={major:22,minor:6,patch:0};function S(e){const t="Browser security (browser-use-agent)";if("unverified"===e.status)return{name:t,status:"warn",message:`未校验: ${e.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const s=e.payload.security;if(void 0===s)return{name:t,status:"warn",message:"browser_status.security 未返回,无法确认浏览器安全策略",fix:"升级并重启 browser-use-agent"};const n=e.payload.policyWarnings??[];return{name:t,status:n.length>0?"warn":"ok",message:j(s,e.payload.toolPolicy,n),...n.length>0?{fix:"检查 BROWSER_SECURITY_JSON 与 BROWSER_USE_POLICY_JSON 的组合;Boss 日常编排建议 browser actionPolicy=log"}:{}}}function j(e,t,s){const n=e.domainAllowlist.length>0?e.domainAllowlist.join(", "):"none",o=P(t),a=s.length>0?`; warnings=${s.map(e=>e.code).join(", ")}`:"";return`actionPolicy=${e.actionPolicy}; domainAllowlist=${n}; maxPageContentBytes=${String(e.maxPageContentBytes)}; maxSnapshotNodes=${String(e.maxSnapshotNodes)}; toolPolicy=${o}`+a}function P(e){if(void 0===e)return"unavailable";const t=Object.entries(e.tools),s=t.length>0?t.map(([e,t])=>`${e}:${t.policy}`).join(","):"none";return`approvalTtlMs=${String(e.approvalTtlMs)},tools=${s}`}export function isNodeVersionSupported(e){const t=/^(\d+)\.(\d+)\.(\d+)/.exec(e);if(!t)return!1;const s=Number(t[1]),n=Number(t[2]),o=Number(t[3]);return s>w.major||!(s<w.major)&&(n>w.minor||!(n<w.minor)&&o>=w.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=[],o=!0===e.fix;let u;const g=process.versions.node;s.push(isNodeVersionSupported(g)?{name:"Node.js 版本",status:"ok",message:`v${g}`}:{name:"Node.js 版本",status:"fail",message:`v${g} (需要 ≥22.6.0)`,fix:"升级 Node.js 到 22.6.0 或更高版本"});let c,h=r();if("needs-migration"===h.status&&o){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=m().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{c=l().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(c){const e=c.dataDir;let r=t(e);if(!r&&o){const s=b(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 l=new $(c.dataDir).list();s.push({name:"已注册 Agent",status:l.length>0?"ok":"warn",message:l.length>0?`${String(l.length)} 个 (${l.map(e=>e.skill.name).join(", ")})`:"无",...0===l.length?{fix:"运行 `roll agent add <path|git-url>` 注册 Agent"}:{}});for(const e of l){let t;const r=async()=>(t??=await d(e,{agentsConfig:c}),t);if("core-managed"===e.runtime.ownership){let t=x(e,c.dataDir);if(o&&t.issues.some(e=>"orphan-sidecar"===e.code)){const s=A(c.dataDir,e.skill.name);n.push(s),"applied"===s.status&&(t=x(e,c.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:N(t.issues.map(e=>e.fix)).join(";")}:{}})}const l=i(e.skill.name,e.skill.env,c.env);if(!l){"browser-use-agent"===e.skill.name&&s.push(S(await r()));continue}t=await r();const m=f(l,a(c,e.skill.name),t),u=p(m);s.push({name:`Agent 环境变量 (${e.skill.name})`,status:u.status,message:u.message,..."ok"===u.status?{}:{fix:`在 \`roll.config.yaml\` 的 \`agents.env.${e.skill.name}\` 或 shell 环境中配置缺失变量`}}),"browser-use-agent"===e.skill.name&&s.push(S(t))}}const k=s.some(e=>"fail"===e.status),y=n.some(e=>"failed"===e.status),w=s.some(e=>"warn"===e.status),j=!0===e["fix-plan"]||o;if(e.json){const e=o?formatDoctorJsonOutput(s,{fixPlan:j,fixes:n}):formatDoctorChecksForJsonOutput(s,{fixPlan:j});return console.log(JSON.stringify(e,null,2)),void((k||y)&&(process.exitCode=1))}console.log("Roll Agent 系统诊断\n");for(const e of s)for(const t of formatDoctorCheckLines(e,{fixPlan:j}))console.log(t);if(o)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(k||y?"\n存在问题,请修复后重试。":w?"\n存在警告,可按 fix plan 处理。":"\n系统状态正常。"),(k||y)&&(process.exitCode=1)}});export function formatDoctorCheckLines(e,t){const s=[` ${k[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[` ${y[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=c(t);if(!s.ok)return{name:"配置迁移",status:"skipped",message:`无法自动迁移:${L(s.issues)}`};if(!s.changed)return{name:"配置迁移",status:"skipped",message:"配置文件已是最新格式"};const a=o(s.document,{lineWidth:0});g(a,e.configPath);const i=D(e.configPath);return n(i,e.raw,"utf-8"),n(e.configPath,a,"utf-8"),{name:"配置迁移",status:"applied",message:`已迁移 ${e.configPath},备份 ${i}`}}catch(e){return{name:"配置迁移",status:"failed",message:e instanceof Error?e.message:String(e)}}}function b(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 A(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 L(e){return e.map(e=>e.message).join(";")}function N(e){return[...new Set(e)]}
1
+ import{defineCommand as e}from"citty";import{existsSync as s,mkdirSync as t,writeFileSync as n}from"node:fs";import{stringify as a}from"yaml";import{getAgentEnv as o,getAgentEnvFromAgentsConfig as r,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{collectBrowserConfigWarnings as l}from"../../config/browser-inspection.js";import{inspectConfigFile as c,loadAgentsConfig as u,loadConfig as g,parseConfigDocument as m,validateConfigText as f}from"../../config/loader.js";import{applyKnownConfigMigrations as p}from"../../config/migration.js";import{inspectAgentRuntimeEnvRequirements as d,summarizeAgentRuntimeEnvReport as h}from"../../config/runtime-env.js";import{inspectAgentRuntimeEnv as w}from"../../mcp/agent-diagnostics.js";import{cleanupOrphanAgentRuntimeMetadata as b,inspectManagedAgentRuntime as x}from"../../registry/process-manager.js";import{AgentStore as k}from"../../registry/store.js";const $={ok:"✓",warn:"⚠",fail:"✗"},y={applied:"✓",skipped:"•",failed:"✗"},S={major:22,minor:6,patch:0};function j(e){const s="Browser security (browser-use-agent)";if("unverified"===e.status)return{name:s,status:"warn",message:`未校验: ${e.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const t=e.payload.security;if(void 0===t)return{name:s,status:"warn",message:"browser_status.security 未返回,无法确认浏览器安全策略",fix:"升级并重启 browser-use-agent"};const n=e.payload.policyWarnings??[];return{name:s,status:n.length>0?"warn":"ok",message:I(t,e.payload.toolPolicy,n),...n.length>0?{fix:"检查 BROWSER_SECURITY_JSON 与 BROWSER_USE_POLICY_JSON 的组合;Boss 日常编排建议 browser actionPolicy=log"}:{}}}function v(e,s){const t=l(e,s);if(0!==t.length)return{name:"Browser config (browser.instances)",status:"warn",message:t.join(";"),fix:"在 roll.config.yaml 中配置 browser.default-instance,并移除 agents.env.browser-use-agent 下已废弃的 BROWSER_CDP_* / BROWSER_USER_DATA_DIR 等实例身份 env",details:{type:"browser-config",warnings:t}}}function P(e,s){const t="Browser runtime (browser-use-agent)",n=Object.keys(e.instances);if(0===n.length)return{name:t,status:"ok",message:"跳过(未配置 browser.instances,使用 legacy 单实例运行时)"};if("unverified"===s.status)return{name:t,status:"warn",message:`未校验: ${s.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const a=s.payload.instances??[];if(0===a.length)return{name:t,status:"warn",message:"browser_status.instances 未返回,无法确认浏览器实例运行态",fix:"升级并重启 browser-use-agent"};const o=new Set(a.map(e=>e.id)),r=n.filter(e=>!o.has(e)),i=a.filter(A),l=a.filter(e=>"missing"===e.tracking.source),c={type:"browser-runtime",declaredInstanceIds:n,...void 0!==e.defaultInstance?{defaultInstanceId:e.defaultInstance}:{},runtimeInstances:a,missingInstanceIds:r,unhealthyInstanceIds:i.map(e=>e.id),missingTrackingInstanceIds:l.map(e=>e.id)},u=r.length>0||i.length>0||l.length>0?"warn":"ok";return{name:t,status:u,details:c,message:[`declared=${n.join(",")}`,`runtime=${a.map(D).join(";")}`,...r.length>0?[`missing=${r.join(",")}`]:[],...l.length>0?[`trackingMissing=${l.map(e=>e.id).join(",")}`]:[]].join(" "),..."warn"===u?{fix:"检查 browser.instances、BROWSER_INSTANCES_JSON、CDP 端口和 profile 目录;必要时重启 browser-use-agent"}:{}}}function A(e){return!(e.cdp.versionReachable&&e.cdp.listReachable&&e.profile.exists&&e.profile.writable)}function D(e){return`${e.id}:cdp=${e.cdp.versionReachable&&e.cdp.listReachable?"ok":"warn"},profile=${e.profile.exists&&e.profile.writable?"ok":"warn"},tracking=${e.tracking.source}`}function I(e,s,t){const n=e.domainAllowlist.length>0?e.domainAllowlist.join(", "):"none",a=O(s),o=t.length>0?`; warnings=${t.map(e=>e.code).join(", ")}`:"";return`actionPolicy=${e.actionPolicy}; foregroundPolicy=${e.foregroundPolicy}; domainAllowlist=${n}; maxPageContentBytes=${String(e.maxPageContentBytes)}; maxSnapshotNodes=${String(e.maxSnapshotNodes)}; toolPolicy=${a}`+o}function O(e){if(void 0===e)return"unavailable";const s=Object.entries(e.tools),t=s.length>0?s.map(([e,s])=>`${e}:${s.policy}`).join(","):"none";return`approvalTtlMs=${String(e.approvalTtlMs)},tools=${t}`}export function isNodeVersionSupported(e){const s=/^(\d+)\.(\d+)\.(\d+)/.exec(e);if(!s)return!1;const t=Number(s[1]),n=Number(s[2]),a=Number(s[3]);return t>S.major||!(t<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 t=[],n=[],a=!0===e.fix;let l;const m=process.versions.node;t.push(isNodeVersionSupported(m)?{name:"Node.js 版本",status:"ok",message:`v${m}`}:{name:"Node.js 版本",status:"fail",message:`v${m} (需要 ≥22.6.0)`,fix:"升级 Node.js 到 22.6.0 或更高版本"});let f,p=c();if("needs-migration"===p.status&&a){const e=R(p);n.push(e),"applied"===e.status&&(p=c())}switch(p.status){case"not-found":t.push({name:"配置文件",status:"warn",message:"未找到,使用默认配置",fix:"运行 `roll config init` 生成显式配置文件"}),l=g().config;break;case"valid":t.push({name:"配置文件",status:"ok",message:p.configPath}),l=p.config;break;case"needs-migration":t.push({name:"配置文件",status:"warn",message:`${p.configPath} (需要迁移,运行 roll config migrate)`,fix:"运行 `roll config migrate`,确认备份文件后提交配置变更"});break;case"invalid":t.push({name:"配置文件",status:"fail",message:p.error.message,fix:"修正配置文件语法或运行 `roll config migrate` 处理 breaking schema change"})}try{f=u().agentsConfig}catch(e){t.push({name:"Agent 配置",status:"fail",message:e instanceof Error?e.message:String(e),fix:"检查 `roll.config.yaml` 的 `agents` 段;如配置版本过旧,运行 `roll config migrate`"})}if(l){const e=Object.keys(l.llm.providers);if(e.length>0){const s=e.filter(e=>{const s=l.llm.providers[e]?.apiKey??"";return s.startsWith("${")||0===s.length});s.length>0?t.push({name:"LLM Providers",status:"warn",message:`${e.join(", ")} (${s.join(", ")} API key 未设置)`,fix:`配置 ${s.map(e=>`llm.providers.${e}.apiKey`).join(", ")} 或对应环境变量`}):t.push({name:"LLM Providers",status:"ok",message:e.join(", ")})}else t.push({name:"LLM Providers",status:"warn",message:"未配置任何 provider",fix:"运行 `roll config init` 或手动配置 `llm.providers`"})}else"needs-migration"===p.status&&t.push({name:"LLM Providers",status:"warn",message:"配置需要迁移,跳过完整 LLM 配置校验",fix:"先运行 `roll config migrate`,再重新执行 `roll doctor`"});if(f){const e=f.dataDir;let c=s(e);if(!c&&a){const t=N(e);n.push(t),c=s(e)}t.push(c?{name:"Agent 数据目录",status:"ok",message:e}:{name:"Agent 数据目录",status:"warn",message:`${e} (不存在,首次 add 时创建)`,fix:"运行 `roll doctor --fix` 创建目录,或手动创建该目录"});const u=new k(f.dataDir).list();if(t.push({name:"已注册 Agent",status:u.length>0?"ok":"warn",message:u.length>0?`${String(u.length)} 个 (${u.map(e=>e.skill.name).join(", ")})`:"无",...0===u.length?{fix:"运行 `roll agent add <path|git-url>` 注册 Agent"}:{}}),l&&Object.keys(l.browser.instances).length>0&&!u.some(e=>"browser-use-agent"===e.skill.name)&&t.push({name:"Browser runtime (browser-use-agent)",status:"fail",message:"已配置 browser.instances,但 browser-use-agent 未注册",fix:"运行 `roll agent add agents/browser-use` 或安装并注册 browser-use-agent",details:{type:"browser-runtime",declaredInstanceIds:Object.keys(l.browser.instances),missingAgent:"browser-use-agent"}}),l&&Object.keys(l.browser.instances).length>0){const e=v(l.browser,o(l,"browser-use-agent"));e&&t.push(e)}for(const e of u){let s;const c=async()=>l?(s??=await w(e,{config:l}),s):{status:"unverified",reason:"connection-failed",message:"无法校验运行态: 配置未加载"};if("core-managed"===e.runtime.ownership){let s=x(e,f.dataDir);if(a&&s.issues.some(e=>"orphan-sidecar"===e.code)){const t=C(f.dataDir,e.skill.name);n.push(t),"applied"===t.status&&(s=x(e,f.dataDir))}(void 0!==s.pid||s.issues.length>0)&&t.push({name:`Agent runtime (${e.skill.name})`,status:s.issues.length>0?"warn":"ok",message:s.issues.length>0?s.issues.map(e=>e.message).join(";"):`PID ${String(s.pid)},runtime sidecar 与当前配置一致`,...s.issues.length>0?{fix:E(s.issues.map(e=>e.fix)).join(";")}:{}})}const u=i(e.skill.name,e.skill.env,f.env);if(!u){"browser-use-agent"===e.skill.name&&(s=await c(),l&&t.push(P(l.browser,s)),t.push(j(s)));continue}s=await c();const g=void 0!==l?o(l,e.skill.name):r(f,e.skill.name),m=d(u,g,s),p=h(m);t.push({name:`Agent 环境变量 (${e.skill.name})`,status:p.status,message:p.message,..."ok"===p.status?{}:{fix:`在 \`roll.config.yaml\` 的 \`agents.env.${e.skill.name}\` 或 shell 环境中配置缺失变量`}}),"browser-use-agent"===e.skill.name&&(l&&t.push(P(l.browser,s)),t.push(j(s)))}}const b=t.some(e=>"fail"===e.status),$=n.some(e=>"failed"===e.status),y=t.some(e=>"warn"===e.status),S=!0===e["fix-plan"]||a;if(e.json){const e=a?formatDoctorJsonOutput(t,{fixPlan:S,fixes:n}):formatDoctorChecksForJsonOutput(t,{fixPlan:S});return console.log(JSON.stringify(e,null,2)),void((b||$)&&(process.exitCode=1))}console.log("Roll Agent 系统诊断\n");for(const e of t)for(const s of formatDoctorCheckLines(e,{fixPlan:S}))console.log(s);if(a)if(console.log("\nFix 执行结果\n"),0===n.length)console.log(" ✓ 无可自动修复项");else for(const e of n)for(const s of formatDoctorFixLines(e))console.log(s);console.log(b||$?"\n存在问题,请修复后重试。":y?"\n存在警告,可按 fix plan 处理。":"\n系统状态正常。"),(b||$)&&(process.exitCode=1)}});export function formatDoctorCheckLines(e,s){const t=[` ${$[e.status]} ${e.name}: ${e.message}`];return s.fixPlan&&e.fix&&t.push(` fix: ${e.fix}`),t}export function formatDoctorChecksForJsonOutput(e,s){return s.fixPlan?e:e.map(({fix:e,...s})=>s)}export function formatDoctorJsonOutput(e,s){return{checks:formatDoctorChecksForJsonOutput(e,{fixPlan:s.fixPlan}),fixes:s.fixes}}export function formatDoctorFixLines(e){return[` ${y[e.status]} ${e.name}: ${e.message}`]}function R(e){if(!e.report.canAutoMigrate)return{name:"配置迁移",status:"skipped",message:"检测到需要人工处理的配置迁移项,未自动修改"};try{const s=m(e.raw,e.configPath),t=p(s);if(!t.ok)return{name:"配置迁移",status:"skipped",message:`无法自动迁移:${_(t.issues)}`};if(!t.changed)return{name:"配置迁移",status:"skipped",message:"配置文件已是最新格式"};const o=a(t.document,{lineWidth:0});f(o,e.configPath);const r=L(e.configPath);return n(r,e.raw,"utf-8"),n(e.configPath,o,"utf-8"),{name:"配置迁移",status:"applied",message:`已迁移 ${e.configPath},备份 ${r}`}}catch(e){return{name:"配置迁移",status:"failed",message:e instanceof Error?e.message:String(e)}}}function N(e){try{return t(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 C(e,s){try{return b(e,s)?{name:`Agent runtime (${s})`,status:"applied",message:"已清理孤儿 runtime 元数据"}:{name:`Agent runtime (${s})`,status:"skipped",message:"检测到活动 PID,跳过 runtime 元数据清理"}}catch(e){return{name:`Agent runtime (${s})`,status:"failed",message:e instanceof Error?e.message:String(e)}}}function L(e){const s=new Date;return`${e}.bak.${[s.getFullYear().toString().padStart(4,"0"),(s.getMonth()+1).toString().padStart(2,"0"),s.getDate().toString().padStart(2,"0"),"-",s.getHours().toString().padStart(2,"0"),s.getMinutes().toString().padStart(2,"0"),s.getSeconds().toString().padStart(2,"0")].join("")}`}function _(e){return e.map(e=>e.message).join(";")}function E(e){return[...new Set(e)]}
@@ -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 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}}
1
+ import{defineCommand as e}from"citty";import{resolve as a}from"node:path";import{existsSync as t}from"node:fs";import{execFile as r}from"node:child_process";import{promisify as n}from"node:util";import{inspectConfigFile as s,loadAgentsConfig as i,loadConfig as o}from"../../config/loader.js";import{getAgentEnv as l}from"../../config/helpers.js";import{getAgentPid as c,startAgent as u,stopAgentGracefully as m,waitForAgentReady as p}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as k}from"../../registry/dev-spawn.js";import{runAgentSetup as d}from"../../registry/runtime-setup.js";import{AgentStore as g}from"../../registry/store.js";import{discoverAgent as f}from"../../registry/discovery.js";import{inferAgentSourceType as $,readInstalledPackageManifest as h,resolveInstalledPackageRoot as w}from"../../registry/source.js";import{McpClientManager as y}from"../../mcp/client-manager.js";import{log as v,createSpinner as S}from"../utils/output.js";import{checkForUpdate as V,checkPublishedPackageUpdate as b,getCurrentVersion as j}from"../utils/update-checker.js";import{detectInstallCommand as P,formatPackageManagerError as B,runPackageManager as A}from"../utils/package-manager.js";const D=n(r);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function N(e,a){switch(e.status){case"needs-migration":{const t="post-update"===a?"升级后需要迁移本地配置":"检测到本地配置需要迁移";v.warn(`${t}: ${e.configPath}`);for(const a of e.report.issues)v.warn(` - ${a.message}`);e.report.canAutoMigrate&&v.info("建议命令: roll config migrate");break}case"invalid":v.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),v.warn(` - ${e.error.message}`),"post-update"===a&&v.info("请修复配置文件后再继续使用相关命令。")}}function T(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function E(e,a){switch(e.status){case"up-to-date":return e.currentVersion?`已是最新版本 (v${e.currentVersion})`:"已是最新版本";case"update-available":return e.currentVersion&&e.latestVersion?`可更新 v${e.currentVersion} → v${e.latestVersion}`:"检测到可用更新";case"pinned-behind":return e.currentVersion&&e.latestVersion?`固定版本 v${e.currentVersion};latest=v${e.latestVersion}`:"固定版本,需手动调整 package spec";case"unsupported-spec":return`不支持检查此 package spec: ${a}`;case"unknown":return e.currentVersion?`无法检查最新版本 (current=v${e.currentVersion})`:"无法检查最新版本"}}function C(e,a){if("installed-package"!==e.source?.type)return e;const t=w(e.source.installDir,e.source.packageName),r=h(t);if(!r)return e;const n={...e.source,...r.name?{packageName:r.name}:{},...r.version?{installedVersion:r.version}:{}},s=n.packageName!==e.source.packageName||n.installedVersion!==e.source.installedVersion,i=t!==e.installPath;if(!s&&!i)return e;const o={...e,installPath:t,source:n};return a&&a.replace(e.skill.name,o),o}async function M(e,a){const t=j();if(t===e)return v.info(`roll 已是最新版本 (v${t})`),!1;if(v.info(`roll v${t} → v${e}`),a)return v.info("[dry-run] 跳过实际更新"),!0;const r=S("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`]};try{return await A(n,{timeout:6e4}),r.succeed(`roll 已更新到 v${e}`),!0}catch(e){return r.fail("更新失败"),v.error(B(n,e)),!1}}async function x(e){const r=S(`更新 ${e.skill.name} (git pull)...`).start();try{await D("git",["pull"],{cwd:e.installPath,timeout:3e4}),r.succeed(`${e.skill.name} 代码已更新`);const n=a(e.installPath,"package.json");if(t(n)){const a=P(e.installPath);if(a){const t=S(`安装 ${e.skill.name} 依赖...`).start();try{await A(a,{cwd:e.installPath,timeout:6e4}),t.succeed(`${e.skill.name} 依赖已更新 (${a.command} ${a.args.join(" ")})`)}catch(r){return t.fail(`${e.skill.name} 依赖安装失败`),v.error(B(a,r)),!1}}else v.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(a){return r.fail(`${e.skill.name} 更新失败`),v.error(a instanceof Error?a.message:String(a)),!1}}async function R(e){if("installed-package"!==e.source?.type)return;const a=S(`更新 ${e.skill.name} (npm install)...`).start(),r={command:"npm",args:["install","--prefix",e.source.installDir,e.source.packageSpec]};try{await A(r,{timeout:12e4});const n=w(e.source.installDir,e.source.packageName);if(!t(n))throw new Error(`Installed package root not found: ${n}`);const s=h(n);return a.succeed(`${e.skill.name} 已重新安装`),{packageRoot:n,packageName:s?.name??e.source.packageName,...s?.version?{installedVersion:s.version}:{}}}catch(t){return a.fail(`${e.skill.name} 更新失败`),void v.error(B(r,t))}}async function I(e){const a=S(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),t=new y;try{const r=k(e),n=await t.connect(e.skill.name,r,e.installPath),{tools:s}=await n.listTools();return a.succeed(`${e.skill.name} 元数据已刷新(${s.length} 个 tool)`),!0}catch(t){return a.fail(`${e.skill.name} 刷新失败`),v.error(t instanceof Error?t.message:String(t)),!1}finally{await t.disconnectAll()}}export default e({meta:{description:"检查并更新 roll 及已注册 Agent"},args:{check:{type:"boolean",description:"仅检查 roll/Agent 可用更新,不执行安装或刷新",default:!1},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1}},async run({args:e}){const a=e.check,t=s();v.info("检查 roll 更新...");const r=await V({forceRefresh:!0});let n,o;r.hasUpdate?v.success(`roll 有新版本: v${r.current} → v${r.latest}`):v.info(`roll 已是最新版本 (v${r.current})`);let l=[];try{n=i().agentsConfig,o=new g(n.dataDir),l=o.list()}catch(e){v.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const u=[];for(const e of l){let a=e;"installed-package"===$(a)&&(a=C(a,o));const t=$(a);switch(t){case"git":u.push({name:a.skill.name,sourceType:t,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==a.source?.type){u.push({name:a.skill.name,sourceType:t,icon:"?",action:"无法检查已安装包版本"});break}const e=await b({packageName:a.source.packageName,packageSpec:a.source.packageSpec,...a.source.installedVersion?{currentVersion:a.source.installedVersion}:{}},{forceRefresh:!0});u.push({name:a.skill.name,sourceType:t,icon:T(e.status),action:E(e,a.source.packageSpec)});break}case"remote-manifest":u.push({name:a.skill.name,sourceType:t,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":u.push({name:a.skill.name,sourceType:t,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(l.length>0){v.info(`\n已注册 Agent (${l.length}):`);for(const e of u)v.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else n&&v.info("无已注册 Agent");if("needs-migration"!==t.status&&"invalid"!==t.status||(v.info(""),N(t,a?"check":"pre-update")),a)return;v.info("");let p=!1,k=!1;r.hasUpdate&&(p=await M(r.latest,!1),p||(k=!0)),!p||"needs-migration"!==t.status&&"invalid"!==t.status||(v.info(""),N(t,"post-update"));let h=0,w=0;for(const a of l){if(!o||!n)break;switch($(a)){case"git":{const e="core-managed"===a.runtime.ownership&&void 0!==c(n.dataDir,a.skill.name);if(await x(a))try{const t=f(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};o.replace(a.skill.name,r)?(await L(r,e,n.dataDir),h++):(v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),w++}else w++;break}case"installed-package":{const t="core-managed"===a.runtime.ownership&&void 0!==c(n.dataDir,a.skill.name);if(t)try{await m(n.dataDir,a.skill.name)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),w++;break}const r=await R(a);if(r)try{const s=f(r.packageRoot),i="installed-package"===a.source?.type?{...a.source,packageName:r.packageName,...r.installedVersion?{installedVersion:r.installedVersion}:{}}:void 0,l={...a,skill:s.skill,transport:s.transport,runtime:s.runtime,installPath:r.packageRoot,...i?{source:i}:{},...s.skillBody.length>0?{skillBody:s.skillBody}:{}},c=await d(l,{skipBrowserSetup:e["skip-browser-setup"]});c.ok||(v.warn(`${l.skill.name} setup 失败:${c.message}`),c.retryCommand&&v.info(`重试命令: ${c.retryCommand}`));o.replace(a.skill.name,l)?c.ok?(t&&await U(l,n.dataDir),h++):(o.updateStatus(l.skill.name,"error"),w++):(v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),w++}else{if(t)try{await U(a,n.dataDir)}catch{o.updateStatus(a.skill.name,"error")}w++}break}case"remote-manifest":try{const e=f(a.installPath),t={...a,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!o.replace(a.skill.name,t)){v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++;break}await I(t)?h++:w++}catch(e){v.warn(`${a.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),w++}break;case"local-path":{const e="core-managed"===a.runtime.ownership&&void 0!==c(n.dataDir,a.skill.name);try{const t=f(a.installPath),r={...a,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};o.replace(a.skill.name,r)?(await L(r,e,n.dataDir),h++):(v.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(e){o.updateStatus(a.skill.name,"error"),v.warn(`${a.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),w++}break}}}v.info("");if(w+(k?1:0)>0){process.exitCode=1;const e=k?"roll 更新失败":p?"roll ✓":"roll 无更新";return void v.warn(`更新完成但有失败:${e}${w>0?`,${w} 个 Agent 更新失败`:""}${h>0?`,${h} 个 Agent 已更新`:""}`)}p||h>0?v.success(`更新完成:${p?"roll ✓":"roll 无更新"}${h>0?`,${h} 个 Agent 已更新`:""}`):v.success("一切都已是最新版本")}});async function L(e,a,t){a&&"core-managed"===e.runtime.ownership&&(await m(t,e.skill.name),await U(e,t))}async function U(e,a){let t=!1;try{u(e,a,l(o().config,e.skill.name)),t=!0,await p(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(r){throw t&&await m(a,e.skill.name).catch(()=>{}),r}}
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,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
+ 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"),browser:()=>u("browser"),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(()=>{})});
@@ -0,0 +1,4 @@
1
+ import type { BrowserConfig } from "./schema.ts";
2
+ export declare const LEGACY_BROWSER_IDENTITY_ENV_KEYS: readonly ["BROWSER_CDP_PORT", "BROWSER_USER_DATA_DIR", "BROWSER_CDP_URL", "BROWSER_SESSIONS_DIR"];
3
+ export type LegacyBrowserIdentityEnvKey = (typeof LEGACY_BROWSER_IDENTITY_ENV_KEYS)[number];
4
+ export declare function collectBrowserConfigWarnings(browserConfig: BrowserConfig, agentEnv: Readonly<Record<string, string>> | undefined, inheritedEnv?: NodeJS.ProcessEnv): readonly string[];
@@ -0,0 +1 @@
1
+ export const LEGACY_BROWSER_IDENTITY_ENV_KEYS=["BROWSER_CDP_PORT","BROWSER_USER_DATA_DIR","BROWSER_CDP_URL","BROWSER_SESSIONS_DIR"];export function collectBrowserConfigWarnings(n,e,t=process.env){const s=Object.keys(n.instances);if(0===s.length)return[];const r=[];s.length>1&&void 0===n.defaultInstance&&r.push(`browser.instances 声明了 ${String(s.length)} 个实例,但未配置 browser.default-instance;未显式传 browserInstance 的 tool 调用会返回 needs_input`);const o=LEGACY_BROWSER_IDENTITY_ENV_KEYS.filter(n=>{const s=e?.[n],r=t[n];return void 0!==s&&s.length>0||void 0!==r&&r.length>0});return o.length>0&&r.push(`browser.instances 已配置,agents.env 或 shell 中的 ${o.join(", ")} 会被忽略;实例身份以 browser.instances 为准`),r}
@@ -1 +1 @@
1
- export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:"claude-sonnet-4-20250514",providers:{}},ask:{},agents:{dataDir:"~/.roll-agent/agents"}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
1
+ export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:"claude-sonnet-4-20250514",providers:{}},ask:{},agents:{dataDir:"~/.roll-agent/agents"},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
@@ -1 +1 @@
1
- const e=/\$\{[^}]+\}/;function n(e,n){return e?.[n]}export function getAgentEnvFromAgentsConfig(e,r){return t(n(e.env,r))}export function getAgentEnv(e,n){return getAgentEnvFromAgentsConfig(e.agents,n)}export function inspectAgentEnvRequirements(e,i,s,u=process.env){if(!i)return;const o=t(n(s,e)),g=[...r(i.required,!0,o,u),...r(i.optional,!1,o,u)];return 0!==g.length?{items:g,missingRequired:g.filter(e=>e.required&&"missing"===e.source),processEnvOnlyRequired:g.filter(e=>e.required&&"process.env"===e.source)}:void 0}export function getMissingAgentEnvRuntimeIssues(e){return e?e.missingRequired.map(e=>({category:"env",code:"missing_required_env",name:e.name,message:`必填环境变量 ${e.name} 未配置`,...e.purpose?{purpose:e.purpose}:{},...e.example?{example:e.example}:{}})):[]}function r(e,n,r,t){return e?e.map(e=>{const i=r?.[e.name];if(void 0!==i&&i.length>0)return{...e,required:n,source:"agents.env"};const s=t[e.name];return"string"==typeof s&&s.length>0?{...e,required:n,source:"process.env"}:e.default?{...e,required:n,source:"default"}:{...e,required:n,source:"missing"}}):[]}function t(e){if(!e)return;const n=Object.entries(e).filter(([,e])=>i(e));return 0!==n.length?Object.fromEntries(n):void 0}function i(n){return"string"==typeof n&&n.length>0&&!e.test(n)}
1
+ const e=/\$\{[^}]+\}/;function n(e,n){return e?.[n]}export function getAgentEnvFromAgentsConfig(e,r){return t(n(e.env,r))}export function getAgentEnv(e,n){const r=getAgentEnvFromAgentsConfig(e.agents,n);return"browser-use-agent"!==n||0===Object.keys(e.browser.instances).length?r:{...r??{},BROWSER_INSTANCES_JSON:JSON.stringify(e.browser)}}export function inspectAgentEnvRequirements(e,s,i,o=process.env){if(!s)return;const u=t(n(i,e)),g=[...r(s.required,!0,u,o),...r(s.optional,!1,u,o)];return 0!==g.length?{items:g,missingRequired:g.filter(e=>e.required&&"missing"===e.source),processEnvOnlyRequired:g.filter(e=>e.required&&"process.env"===e.source)}:void 0}export function getMissingAgentEnvRuntimeIssues(e){return e?e.missingRequired.map(e=>({category:"env",code:"missing_required_env",name:e.name,message:`必填环境变量 ${e.name} 未配置`,...e.purpose?{purpose:e.purpose}:{},...e.example?{example:e.example}:{}})):[]}function r(e,n,r,t){return e?e.map(e=>{const s=r?.[e.name];if(void 0!==s&&s.length>0)return{...e,required:n,source:"agents.env"};const i=t[e.name];return"string"==typeof i&&i.length>0?{...e,required:n,source:"process.env"}:e.default?{...e,required:n,source:"default"}:{...e,required:n,source:"missing"}}):[]}function t(e){if(!e)return;const n=Object.entries(e).filter(([,e])=>s(e));return 0!==n.length?Object.fromEntries(n):void 0}function s(n){return"string"==typeof n&&n.length>0&&!e.test(n)}
@@ -1 +1 @@
1
- const e={kind:"leaf"},r={kind:"object",fields:{apiKey:e,baseUrl:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:r}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,r)=>r.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function o(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(r,n=CONFIG_KEY_CODEC){if(Array.isArray(r))return r.map(e=>decodeFromYaml(e,n));if(!o(r))return r;if("object"===n.kind){const o={};for(const[t,a]of Object.entries(r)){const r=kebabToCamel(t),c=n.fields[r]??e;o[r]=decodeFromYaml(a,c)}return o}if("record"===n.kind){const e={};for(const[o,t]of Object.entries(r))e[o]=decodeFromYaml(t,n.value);return e}return r}function n(r,o){const n=[];let t=CONFIG_KEY_CODEC;for(const a of r)if("object"===t.kind){const r=kebabToCamel(a),c=t.fields[r];c?(n.push("camel"===o?r:camelToKebab(r)),t=c):(n.push(a),t=e)}else"record"===t.kind?(n.push(a),t=t.value):n.push(a);return n}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
1
+ const e={kind:"leaf"},r={kind:"object",fields:{apiKey:e,baseUrl:e}},o={kind:"object",fields:{platform:e,mode:e,headless:e,cdpUrl:e,cdpHost:e,cdpPort:e,channel:e,executablePath:e,userDataDir:e,sessionsDir:e,args:e,profileName:e,windowBounds:{kind:"object",fields:{x:e,y:e,width:e,height:e}},trackingAgentId:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:r}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}},browser:{kind:"object",fields:{defaultInstance:e,instances:{kind:"record",value:o}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,r)=>r.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function t(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(r,o=CONFIG_KEY_CODEC){if(Array.isArray(r))return r.map(e=>decodeFromYaml(e,o));if(!t(r))return r;if("object"===o.kind){const t={};for(const[n,d]of Object.entries(r)){const r=kebabToCamel(n),a=o.fields[r]??e;t[r]=decodeFromYaml(d,a)}return t}if("record"===o.kind){const e={};for(const[t,n]of Object.entries(r))e[t]=decodeFromYaml(n,o.value);return e}return r}function n(r,o){const t=[];let n=CONFIG_KEY_CODEC;for(const d of r)if("object"===n.kind){const r=kebabToCamel(d),a=n.fields[r];a?(t.push("camel"===o?r:camelToKebab(r)),n=a):(t.push(d),n=e)}else"record"===n.kind?(t.push(d),n=n.value):t.push(d);return t}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
@@ -1 +1 @@
1
- import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,rollConfigSchema as i}from"./schema.js";import{DEFAULT_CONFIG as a,CONFIG_FILE_NAMES as s}from"./defaults.js";import{decodeFromYaml as f}from"./key-codec.js";import{detectKnownConfigMigrations as c,formatConfigMigrationError as u}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(l(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function l(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function d(n){return l(n)&&"number"==typeof n.line&&"number"==typeof n.col}function p(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(d)}function h(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(p(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function m(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of s){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function w(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)}}}const $={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(h(r,n),{cause:n instanceof Error?n:void 0})}if(!l(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function y(n,r,t={}){const o=parseConfigDocument(n,r),e=c(o,t);if(e.needsMigration)throw new Error(u(r,e));return o}export function validateConfigText(n,r){const t=y(n,r),o=g(f(t));if(!l(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=C(a,o),s=i.safeParse(e);if(!s.success){const n=s.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return w(s.data)}function P(n,r){const t=y(n,r,{scope:"agents"}),o=g(f(t));if(!l(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=l(o.agents)?o.agents:{},s=C(a.agents,i),c=e.safeParse(s);if(!c.success){const n=c.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...c.data,dataDir:v(c.data.dataDir)}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:w(a),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...a.agents,dataDir:v(a.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:P(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??m(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:$.notFound,configPath:void 0};if(!r(o))return{status:$.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:$.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const a=c(i);if(a.needsMigration)return{status:$.needsMigration,configPath:o,raw:e,report:a};try{return{status:$.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:$.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function C(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=C(r,e)}return t}
1
+ import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,rollConfigSchema as i}from"./schema.js";import{DEFAULT_CONFIG as s,CONFIG_FILE_NAMES as a}from"./defaults.js";import{decodeFromYaml as f}from"./key-codec.js";import{detectKnownConfigMigrations as c,formatConfigMigrationError as u}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(l(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function l(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function d(n){return l(n)&&"number"==typeof n.line&&"number"==typeof n.col}function p(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(d)}function h(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(p(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function m(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of a){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function w(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:v(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:v(r.sessionsDir)}:{}}]))}}}const $={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(h(r,n),{cause:n instanceof Error?n:void 0})}if(!l(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function y(n,r,t={}){const o=parseConfigDocument(n,r),e=c(o,t);if(e.needsMigration)throw new Error(u(r,e));return o}export function validateConfigText(n,r){const t=y(n,r),o=g(f(t));if(!l(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=E(s,o),a=i.safeParse(e);if(!a.success){const n=a.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return w(a.data)}function P(n,r){const t=y(n,r,{scope:"agents"}),o=g(f(t));if(!l(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=l(o.agents)?o.agents:{},a=E(s.agents,i),c=e.safeParse(a);if(!c.success){const n=c.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...c.data,dataDir:v(c.data.dataDir)}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:w(s),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...s.agents,dataDir:v(s.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:P(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??m(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:$.notFound,configPath:void 0};if(!r(o))return{status:$.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:$.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=c(i);if(s.needsMigration)return{status:$.needsMigration,configPath:o,raw:e,report:s};try{return{status:$.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:$.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function E(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=E(r,e)}return t}