@roll-agent/core 0.3.3 → 0.4.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 e}from"citty";import{existsSync as t,mkdirSync as r,statSync as i}from"node:fs";import{resolve as s}from"node:path";import{execFile as o}from"node:child_process";import{promisify as n}from"node:util";import{inspectAgentEnvRequirements as a}from"../../config/helpers.js";import{loadAgentsConfig as l}from"../../config/loader.js";import{discoverAgent as p}from"../../registry/discovery.js";import{startAgent as m,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,resolveInstalledPackageRoot as k,sanitizeInstallId as y}from"../../registry/source.js";import{log as h}from"../utils/output.js";const w=n(o);function S(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}=l(),n=e.package;if(S(n))return h.error(`Git URL 请使用 \`roll agent add ${n}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const a=s(n);if(t(a)&&i(a).isDirectory())return h.error(`本地源码目录请使用 \`roll agent add ${n}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const v=f(n),j=s(o.dataDir,"installed",y(v));t(j)||r(j,{recursive:!0}),h.info(`安装 ${n}...`);try{await w("npm",["install","--prefix",j,n],{timeout:12e4})}catch(e){return h.error(`安装失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}const C=k(j,v);if(!t(C))return h.error(`安装完成但未找到包目录: ${C}`),void(process.exitCode=1);h.info("解析已安装 Agent 的 SKILL.md...");const x=p(C),A=new u(o.dataDir),b={skill:x.skill,transport:x.transport,runtime:x.runtime,installPath:C,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:v,packageSpec:n,installDir:j},...x.skillBody.length>0?{skillBody:x.skillBody}:{}};"core-managed"===b.runtime.ownership&&b.runtime.setup?.playwright&&!e.skipBrowserSetup&&h.info(`即将安装浏览器运行时 (${b.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const B=await g(b,{skipBrowserSetup:e.skipBrowserSetup});B.ok?B.skipped?h.info(B.message):h.success(B.message):(h.warn(`Agent setup 失败:${B.message}`),B.retryCommand&&h.info(`重试命令: ${B.retryCommand}`));const D=A.findByName(x.skill.name);try{const t="core-managed"===D?.runtime.ownership&&"online"===D.status;if("installed-package"===D?.source?.type?A.replace(D.skill.name,b):A.add(b),!B.ok)return A.updateStatus(x.skill.name,"error"),void(process.exitCode=1);if("core-managed"===b.runtime.ownership&&!e.noStart){t&&await c(o.dataDir,b.skill.name),A.updateStatus(b.skill.name,"starting");let e=!1;try{m(b,o.dataDir),e=!0,await d(b,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),A.updateStatus(b.skill.name,"online")}catch(t){return e&&await c(o.dataDir,b.skill.name).catch(()=>{}),A.updateStatus(b.skill.name,"error"),h.error(`Agent "${x.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}h.success(`Agent "${x.skill.name}" 安装并注册成功`),$(x.skill.name,x.skill.env,o.env)}catch(e){h.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function $(e,t,r){const i=a(e,t,r);if(i)return i.missingRequired.length>0?(h.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),void h.info(`请在 roll.config.yaml 的 agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(h.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),h.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{execFile as o}from"node:child_process";import{promisify as n}from"node:util";import{inspectAgentEnvRequirements as a}from"../../config/helpers.js";import{loadAgentsConfig as l}from"../../config/loader.js";import{discoverAgent as p}from"../../registry/discovery.js";import{startAgent as m,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";const v=n(o);function S(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}=l(),n=e.package;if(S(n))return w.error(`Git URL 请使用 \`roll agent add ${n}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const a=s(n);if(t(a)&&i(a).isDirectory())return w.error(`本地源码目录请使用 \`roll agent add ${n}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const j=f(n),C=s(o.dataDir,"installed",h(j));t(C)||r(C,{recursive:!0}),w.info(`安装 ${n}...`);try{await v("npm",["install","--prefix",C,n],{timeout:12e4})}catch(e){return w.error(`安装失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}const x=y(C,j);if(!t(x))return w.error(`安装完成但未找到包目录: ${x}`),void(process.exitCode=1);const A=k(x);w.info("解析已安装 Agent 的 SKILL.md...");const b=p(x),B=new u(o.dataDir),D={skill:b.skill,transport:b.transport,runtime:b.runtime,installPath:x,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:A?.name??j,packageSpec:n,installDir:C,...A?.version?{installedVersion:A.version}:{}},...b.skillBody.length>0?{skillBody:b.skillBody}:{}};"core-managed"===D.runtime.ownership&&D.runtime.setup?.playwright&&!e.skipBrowserSetup&&w.info(`即将安装浏览器运行时 (${D.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const W=await g(D,{skipBrowserSetup:e.skipBrowserSetup});W.ok?W.skipped?w.info(W.message):w.success(W.message):(w.warn(`Agent setup 失败:${W.message}`),W.retryCommand&&w.info(`重试命令: ${W.retryCommand}`));const q=B.findByName(b.skill.name);try{const t="core-managed"===q?.runtime.ownership&&"online"===q.status;if("installed-package"===q?.source?.type?B.replace(q.skill.name,D):B.add(D),!W.ok)return B.updateStatus(b.skill.name,"error"),void(process.exitCode=1);if("core-managed"===D.runtime.ownership&&!e.noStart){t&&await c(o.dataDir,D.skill.name),B.updateStatus(D.skill.name,"starting");let e=!1;try{m(D,o.dataDir),e=!0,await d(D,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),B.updateStatus(D.skill.name,"online")}catch(t){return e&&await c(o.dataDir,D.skill.name).catch(()=>{}),B.updateStatus(D.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.skill.name,b.skill.env,o.env)}catch(e){w.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function $(e,t,r){const i=a(e,t,r);if(i)return i.missingRequired.length>0?(w.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),void w.info(`请在 roll.config.yaml 的 agents.env.${e} 中显式配置这些项。`)):void(i.processEnvOnlyRequired.length>0&&(w.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),w.info(`建议将这些项写入 roll.config.yaml 的 agents.env.${e}。`)))}
@@ -1 +1 @@
1
- import{defineCommand as t}from"citty";import{resolve as e}from"node:path";import{existsSync as a,readFileSync as r}from"node:fs";import{execFile as n}from"node:child_process";import{promisify as s}from"node:util";import{inspectConfigFile as i,loadAgentsConfig as o}from"../../config/loader.js";import{getAgentPid as l,startAgent as c,stopAgentGracefully as m,waitForAgentReady as p}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as u}from"../../registry/dev-spawn.js";import{runAgentSetup as f}from"../../registry/runtime-setup.js";import{AgentStore as k}from"../../registry/store.js";import{discoverAgent as g}from"../../registry/discovery.js";import{inferAgentSourceType as d,resolveInstalledPackageRoot as $}from"../../registry/source.js";import{McpClientManager as h}from"../../mcp/client-manager.js";import{log as y,createSpinner as w}from"../utils/output.js";import{checkForUpdate as S,getCurrentVersion as b}from"../utils/update-checker.js";const v=s(n);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";function j(t,e){switch(t.status){case"needs-migration":{const a="post-update"===e?"升级后需要迁移本地配置":"检测到本地配置需要迁移";y.warn(`${a}: ${t.configPath}`);for(const e of t.report.issues)y.warn(` - ${e.message}`);t.report.canAutoMigrate&&y.info("建议命令: roll config migrate");break}case"invalid":y.warn(`本地配置存在问题: ${t.configPath??"(unknown path)"}`),y.warn(` - ${t.error.message}`),"post-update"===e&&y.info("请修复配置文件后再继续使用相关命令。")}}async function B(t,e){const a=b();if(a===t)return y.info(`roll 已是最新版本 (v${a})`),!1;if(y.info(`roll v${a} → v${t}`),e)return y.info("[dry-run] 跳过实际更新"),!0;const r=w("正在更新 @roll-agent/core...").start();try{return await v("npm",["install","-g",`@roll-agent/core@${t}`],{timeout:6e4}),r.succeed(`roll 已更新到 v${t}`),!0}catch(t){return r.fail("更新失败"),y.error(t instanceof Error?t.message:String(t)),!1}}async function P(t){const r=w(`更新 ${t.skill.name} (git pull)...`).start();try{await v("git",["pull"],{cwd:t.installPath,timeout:3e4}),r.succeed(`${t.skill.name} 代码已更新`);const n=e(t.installPath,"package.json");if(a(n)){const e=detectInstallCommand(t.installPath);if(e){const a=w(`安装 ${t.skill.name} 依赖...`).start();try{await v(e.command,e.args,{cwd:t.installPath,timeout:6e4}),a.succeed(`${t.skill.name} 依赖已更新 (${e.command} ${e.args.join(" ")})`)}catch(e){return a.fail(`${t.skill.name} 依赖安装失败`),y.error(e instanceof Error?e.message:String(e)),!1}}else y.warn(`${t.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(e){return r.fail(`${t.skill.name} 更新失败`),y.error(e instanceof Error?e.message:String(e)),!1}}async function A(t){if("installed-package"!==t.source?.type)return!1;const e=w(`更新 ${t.skill.name} (npm install)...`).start();try{await v("npm",["install","--prefix",t.source.installDir,t.source.packageSpec],{timeout:12e4});const r=$(t.source.installDir,t.source.packageName);if(!a(r))throw new Error(`Installed package root not found: ${r}`);return e.succeed(`${t.skill.name} 已重新安装`),!0}catch(a){return e.fail(`${t.skill.name} 更新失败`),y.error(a instanceof Error?a.message:String(a)),!1}}async function E(t){const e=w(`刷新 ${t.skill.name} (MCP tools/list)...`).start(),a=new h;try{const r=u(t),n=await a.connect(t.skill.name,r,t.installPath),{tools:s}=await n.listTools();return e.succeed(`${t.skill.name} 元数据已刷新(${s.length} 个 tool)`),!0}catch(a){return e.fail(`${t.skill.name} 刷新失败`),y.error(a instanceof Error?a.message:String(a)),!1}finally{await a.disconnectAll()}}export default t({meta:{description:"更新 roll 及已注册的 Agent"},args:{check:{type:"boolean",description:"仅检查可用更新,不执行",default:!1},skipBrowserSetup:{type:"boolean",description:"跳过浏览器运行时安装",default:!1}},async run({args:t}){const e=t.check,a=i();y.info("检查 roll 更新...");const r=await S({forceRefresh:!0});let n,s;r.hasUpdate?y.success(`roll 有新版本: v${r.current} → v${r.latest}`):y.info(`roll 已是最新版本 (v${r.current})`);let c=[];try{n=o().agentsConfig,s=new k(n.dataDir),c=s.list()}catch(t){y.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${t instanceof Error?t.message:String(t)}`)}const m=[];for(const t of c){const e=d(t);let a;switch(e){case"git":a="git pull + 重新安装依赖";break;case"installed-package":a="重新安装 npm 包";break;case"remote-manifest":a="刷新本地 manifest + MCP 元数据";break;case"local-path":a="刷新本地 SKILL/manifest"}m.push({name:t.skill.name,sourceType:e,action:a})}if(c.length>0){y.info(`\n已注册 Agent (${c.length}):`);for(const t of m){const e="local-path"===t.sourceType?"⏭":"⬆";y.info(` ${e} ${t.name} [${t.sourceType}] — ${t.action}`)}}else n&&y.info("无已注册 Agent");if("needs-migration"!==a.status&&"invalid"!==a.status||(y.info(""),j(a,e?"check":"pre-update")),e)return;y.info("");let p=!1,u=!1;r.hasUpdate&&(p=await B(r.latest,!1),p||(u=!0)),!p||"needs-migration"!==a.status&&"invalid"!==a.status||(y.info(""),j(a,"post-update"));let h=0,w=0;for(const e of c){if(!s||!n)break;switch(d(e)){case"git":{const t="core-managed"===e.runtime.ownership&&void 0!==l(n.dataDir,e.skill.name);if(await P(e))try{const a=g(e.installPath),r={...e,skill:a.skill,transport:a.transport,runtime:a.runtime,...a.skillBody.length>0?{skillBody:a.skillBody}:{}};s.replace(e.skill.name,r)?(await D(r,t,n.dataDir),h++):(y.warn(`${e.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(t){s.updateStatus(e.skill.name,"error"),y.warn(`${e.skill.name} metadata 刷新或重启失败: ${t instanceof Error?t.message:String(t)}`),w++}else w++;break}case"installed-package":{const a="core-managed"===e.runtime.ownership&&void 0!==l(n.dataDir,e.skill.name);if(await A(e))try{const r="installed-package"===e.source?.type?$(e.source.installDir,e.source.packageName):e.installPath,i=g(r),o={...e,skill:i.skill,transport:i.transport,runtime:i.runtime,installPath:r,...i.skillBody.length>0?{skillBody:i.skillBody}:{}},l=await f(o,{skipBrowserSetup:t.skipBrowserSetup});l.ok||(y.warn(`${o.skill.name} setup 失败:${l.message}`),l.retryCommand&&y.info(`重试命令: ${l.retryCommand}`));s.replace(e.skill.name,o)?l.ok?(await D(o,a,n.dataDir),h++):(s.updateStatus(o.skill.name,"error"),w++):(y.warn(`${e.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(t){s.updateStatus(e.skill.name,"error"),y.warn(`${e.skill.name} metadata 刷新、setup 或重启失败: ${t instanceof Error?t.message:String(t)}`),w++}else w++;break}case"remote-manifest":try{const t=g(e.installPath),a={...e,skill:t.skill,transport:t.transport,runtime:t.runtime,...t.skillBody.length>0?{skillBody:t.skillBody}:{}};if(!s.replace(e.skill.name,a)){y.warn(`${e.skill.name} 已从注册表中移除,跳过元数据刷新`),w++;break}await E(a)?h++:w++}catch(t){y.warn(`${e.skill.name} manifest 刷新失败: ${t instanceof Error?t.message:String(t)}`),w++}break;case"local-path":{const t="core-managed"===e.runtime.ownership&&void 0!==l(n.dataDir,e.skill.name);try{const a=g(e.installPath),r={...e,skill:a.skill,transport:a.transport,runtime:a.runtime,...a.skillBody.length>0?{skillBody:a.skillBody}:{}};s.replace(e.skill.name,r)?(await D(r,t,n.dataDir),h++):(y.warn(`${e.skill.name} 已从注册表中移除,跳过元数据刷新`),w++)}catch(t){s.updateStatus(e.skill.name,"error"),y.warn(`${e.skill.name} 本地 metadata 刷新或重启失败: ${t instanceof Error?t.message:String(t)}`),w++}break}}}y.info("");if(w+(u?1:0)>0){process.exitCode=1;const t=u?"roll 更新失败":p?"roll ✓":"roll 无更新";return void y.warn(`更新完成但有失败:${t}${w>0?`,${w} 个 Agent 更新失败`:""}${h>0?`,${h} 个 Agent 已更新`:""}`)}p||h>0?y.success(`更新完成:${p?"roll ✓":"roll 无更新"}${h>0?`,${h} 个 Agent 已更新`:""}`):y.success("一切都已是最新版本")}});export function detectInstallCommand(t){const r=e(t,"package.json");if(a(r)){const t=M(r);if(t)return{command:t,args:["install"]}}const n=[["pnpm-lock.yaml","pnpm"],["package-lock.json","npm"],["npm-shrinkwrap.json","npm"],["yarn.lock","yarn"],["bun.lock","bun"],["bun.lockb","bun"]];for(const[r,s]of n)if(a(e(t,r)))return{command:s,args:["install"]}}async function D(t,e,a){if(!e||"core-managed"!==t.runtime.ownership)return;await m(a,t.skill.name);let r=!1;try{c(t,a),r=!0,await p(t,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(e){throw r&&await m(a,t.skill.name).catch(()=>{}),e}}function M(t){try{const e=JSON.parse(r(t,"utf-8"));if("string"!=typeof e.packageManager||0===e.packageManager.length)return;const a=e.packageManager.split("@",1)[0];return"pnpm"===a||"npm"===a||"yarn"===a||"bun"===a?a:void 0}catch{return}}
1
+ import{defineCommand as e}from"citty";import{resolve as a}from"node:path";import{existsSync as t,readFileSync as r}from"node:fs";import{execFile as n}from"node:child_process";import{promisify as s}from"node:util";import{inspectConfigFile as i,loadAgentsConfig as o}from"../../config/loader.js";import{getAgentPid as l,startAgent as c,stopAgentGracefully as u,waitForAgentReady as m}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as p}from"../../registry/dev-spawn.js";import{runAgentSetup as k}from"../../registry/runtime-setup.js";import{AgentStore as g}from"../../registry/store.js";import{discoverAgent as d}from"../../registry/discovery.js";import{inferAgentSourceType as f,readInstalledPackageManifest as $,resolveInstalledPackageRoot as h}from"../../registry/source.js";import{McpClientManager as y}from"../../mcp/client-manager.js";import{log as w,createSpinner as v}from"../utils/output.js";import{checkForUpdate as S,checkPublishedPackageUpdate as b,getCurrentVersion as V}from"../utils/update-checker.js";const j=s(n);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";function B(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 P(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function D(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 E(e,a){if("installed-package"!==e.source?.type)return e;const t=h(e.source.installDir,e.source.packageName),r=$(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 N(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=v("正在更新 @roll-agent/core...").start();try{return await j("npm",["install","-g",`@roll-agent/core@${e}`],{timeout:6e4}),r.succeed(`roll 已更新到 v${e}`),!0}catch(e){return r.fail("更新失败"),w.error(e instanceof Error?e.message:String(e)),!1}}async function A(e){const r=v(`更新 ${e.skill.name} (git pull)...`).start();try{await j("git",["pull"],{cwd:e.installPath,timeout:3e4}),r.succeed(`${e.skill.name} 代码已更新`);const n=a(e.installPath,"package.json");if(t(n)){const a=detectInstallCommand(e.installPath);if(a){const t=v(`安装 ${e.skill.name} 依赖...`).start();try{await j(a.command,a.args,{cwd:e.installPath,timeout:6e4}),t.succeed(`${e.skill.name} 依赖已更新 (${a.command} ${a.args.join(" ")})`)}catch(a){return t.fail(`${e.skill.name} 依赖安装失败`),w.error(a instanceof Error?a.message:String(a)),!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 T(e){if("installed-package"!==e.source?.type)return;const a=v(`更新 ${e.skill.name} (npm install)...`).start();try{await j("npm",["install","--prefix",e.source.installDir,e.source.packageSpec],{timeout:12e4});const r=h(e.source.installDir,e.source.packageName);if(!t(r))throw new Error(`Installed package root not found: ${r}`);const n=$(r);return a.succeed(`${e.skill.name} 已重新安装`),{packageRoot:r,packageName:n?.name??e.source.packageName,...n?.version?{installedVersion:n.version}:{}}}catch(t){return a.fail(`${e.skill.name} 更新失败`),void w.error(t instanceof Error?t.message:String(t))}}async function M(e){const a=v(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),t=new y;try{const r=p(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=i();w.info("检查 roll 更新...");const r=await S({forceRefresh:!0});let n,s;r.hasUpdate?w.success(`roll 有新版本: v${r.current} → v${r.latest}`):w.info(`roll 已是最新版本 (v${r.current})`);let c=[];try{n=o().agentsConfig,s=new g(n.dataDir),c=s.list()}catch(e){w.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const m=[];for(const e of c){let a=e;"installed-package"===f(a)&&(a=E(a,s));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 b({packageName:a.source.packageName,packageSpec:a.source.packageSpec,...a.source.installedVersion?{currentVersion:a.source.installedVersion}:{}});m.push({name:a.skill.name,sourceType:t,icon:P(e.status),action:D(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(c.length>0){w.info(`\n已注册 Agent (${c.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(""),B(t,a?"check":"pre-update")),a)return;w.info("");let p=!1,$=!1;r.hasUpdate&&(p=await N(r.latest,!1),p||($=!0)),!p||"needs-migration"!==t.status&&"invalid"!==t.status||(w.info(""),B(t,"post-update"));let h=0,y=0;for(const a of c){if(!s||!n)break;switch(f(a)){case"git":{const e="core-managed"===a.runtime.ownership&&void 0!==l(n.dataDir,a.skill.name);if(await A(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}:{}};s.replace(a.skill.name,r)?(await C(r,e,n.dataDir),h++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){s.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!==l(n.dataDir,a.skill.name);if(t)try{await u(n.dataDir,a.skill.name)}catch(e){s.updateStatus(a.skill.name,"error"),w.warn(`${a.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),y++;break}const r=await T(a);if(r)try{const i=d(r.packageRoot),o="installed-package"===a.source?.type?{...a.source,packageName:r.packageName,...r.installedVersion?{installedVersion:r.installedVersion}:{}}:void 0,l={...a,skill:i.skill,transport:i.transport,runtime:i.runtime,installPath:r.packageRoot,...o?{source:o}:{},...i.skillBody.length>0?{skillBody:i.skillBody}:{}},c=await k(l,{skipBrowserSetup:e.skipBrowserSetup});c.ok||(w.warn(`${l.skill.name} setup 失败:${c.message}`),c.retryCommand&&w.info(`重试命令: ${c.retryCommand}`));s.replace(a.skill.name,l)?c.ok?(t&&await x(l,n.dataDir),h++):(s.updateStatus(l.skill.name,"error"),y++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){s.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 x(a,n.dataDir)}catch{s.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(!s.replace(a.skill.name,t)){w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++;break}await M(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!==l(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}:{}};s.replace(a.skill.name,r)?(await C(r,e,n.dataDir),h++):(w.warn(`${a.skill.name} 已从注册表中移除,跳过元数据刷新`),y++)}catch(e){s.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 更新失败":p?"roll ✓":"roll 无更新";return void w.warn(`更新完成但有失败:${e}${y>0?`,${y} 个 Agent 更新失败`:""}${h>0?`,${h} 个 Agent 已更新`:""}`)}p||h>0?w.success(`更新完成:${p?"roll ✓":"roll 无更新"}${h>0?`,${h} 个 Agent 已更新`:""}`):w.success("一切都已是最新版本")}});export function detectInstallCommand(e){const r=a(e,"package.json");if(t(r)){const e=I(r);if(e)return{command:e,args:["install"]}}const n=[["pnpm-lock.yaml","pnpm"],["package-lock.json","npm"],["npm-shrinkwrap.json","npm"],["yarn.lock","yarn"],["bun.lock","bun"],["bun.lockb","bun"]];for(const[r,s]of n)if(t(a(e,r)))return{command:s,args:["install"]}}async function C(e,a,t){a&&"core-managed"===e.runtime.ownership&&(await u(t,e.skill.name),await x(e,t))}async function x(e,a){let t=!1;try{c(e,a),t=!0,await m(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(r){throw t&&await u(a,e.skill.name).catch(()=>{}),r}}function I(e){try{const a=JSON.parse(r(e,"utf-8"));if("string"!=typeof a.packageManager||0===a.packageManager.length)return;const t=a.packageManager.split("@",1)[0];return"pnpm"===t||"npm"===t||"yarn"===t||"bun"===t?t:void 0}catch{return}}
@@ -1,23 +1,27 @@
1
- interface CheckForUpdateOptions {
2
- /** 强制忽略缓存,始终尝试联网查询 */
1
+ export declare const PUBLISHED_PACKAGE_UPDATE_STATUSES: readonly ["up-to-date", "update-available", "pinned-behind", "unsupported-spec", "unknown"];
2
+ export type PublishedPackageUpdateStatus = (typeof PUBLISHED_PACKAGE_UPDATE_STATUSES)[number];
3
+ interface PackageVersionQueryOptions {
3
4
  readonly forceRefresh?: boolean;
4
- /** 是否允许联网查询 npm registry */
5
5
  readonly allowNetwork?: boolean;
6
6
  }
7
- /** npm registry 查询最新版本 */
8
- export declare function fetchLatestVersion(): Promise<string | undefined>;
9
- /** 获取当前安装的版本 */
10
- export declare function getCurrentVersion(): string;
7
+ export interface PublishedPackageUpdateInfo {
8
+ readonly packageName: string;
9
+ readonly currentVersion?: string;
10
+ readonly latestVersion?: string;
11
+ readonly status: PublishedPackageUpdateStatus;
12
+ }
11
13
  export interface UpdateInfo {
12
14
  readonly current: string;
13
15
  readonly latest: string;
14
16
  readonly hasUpdate: boolean;
15
17
  }
16
- /**
17
- * 检查是否有新版本可用。
18
- *
19
- * 使用 24h 文件缓存避免频繁网络请求。
20
- * 设计为不抛异常 — 任何失败静默返回无更新。
21
- */
22
- export declare function checkForUpdate(options?: CheckForUpdateOptions): Promise<UpdateInfo>;
18
+ export declare function fetchLatestPublishedVersion(packageName: string, options?: PackageVersionQueryOptions): Promise<string | undefined>;
19
+ export declare function getCurrentVersion(): string;
20
+ export declare function isNewerVersion(latest: string, current: string): boolean;
21
+ export declare function checkPublishedPackageUpdate(input: {
22
+ packageName: string;
23
+ packageSpec: string;
24
+ currentVersion?: string;
25
+ }, options?: PackageVersionQueryOptions): Promise<PublishedPackageUpdateInfo>;
26
+ export declare function checkForUpdate(options?: PackageVersionQueryOptions): Promise<UpdateInfo>;
23
27
  export {};
@@ -1 +1 @@
1
- import{readFileSync as t,writeFileSync as e,mkdirSync as r,existsSync as n}from"node:fs";import{resolve as o,dirname as s}from"node:path";import{execFile as c}from"node:child_process";import{promisify as i}from"node:util";import{homedir as a}from"node:os";const u=i(c),f=864e5,l="@roll-agent/core";function p(){return o(a(),".roll-agent","update-check.json")}function d(t){return"object"==typeof t&&null!==t&&("latestVersion"in t&&"checkedAt"in t&&("string"==typeof t.latestVersion&&"number"==typeof t.checkedAt))}function h(){const e=p();if(n(e))try{const r=t(e,"utf-8"),n=JSON.parse(r);if(d(n))return n}catch{}}function m(t){try{const o=p(),c=s(o);n(c)||r(c,{recursive:!0});const i={latestVersion:t,checkedAt:Date.now()};e(o,JSON.stringify(i),"utf-8")}catch{}}export async function fetchLatestVersion(){try{const{stdout:t}=await u("npm",["view",l,"version"],{timeout:5e3,encoding:"utf-8"}),e=t.trim();return e.length>0?e:void 0}catch{return}}export function getCurrentVersion(){try{const e=o(import.meta.dirname,"../../../package.json"),r=t(e,"utf-8"),n=JSON.parse(r);if("object"==typeof n&&null!==n&&"version"in n){const t=n.version;if("string"==typeof t)return t}}catch{}return"0.0.0"}function V(t,e){const r=t=>t.replace(/^v/,"").split(".").map(Number),n=r(t),o=r(e);for(let t=0;t<3;t++){const e=n[t]??0,r=o[t]??0;if(e>r)return!0;if(e<r)return!1}return!1}export async function checkForUpdate(t={}){const e=t.forceRefresh??!1,r=t.allowNetwork??!0,n=getCurrentVersion(),o=h();if(!e&&o&&Date.now()-o.checkedAt<f)return{current:n,latest:o.latestVersion,hasUpdate:V(o.latestVersion,n)};if(!r)return o?{current:n,latest:o.latestVersion,hasUpdate:V(o.latestVersion,n)}:{current:n,latest:n,hasUpdate:!1};const s=await fetchLatestVersion();return s?(m(s),{current:n,latest:s,hasUpdate:V(s,n)}):o?{current:n,latest:o.latestVersion,hasUpdate:V(o.latestVersion,n)}:{current:n,latest:n,hasUpdate:!1}}
1
+ import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as r}from"node:fs";import{resolve as s,dirname as o}from"node:path";import{execFile as a}from"node:child_process";import{promisify as i}from"node:util";import{homedir as c}from"node:os";const u=i(a),p=864e5,f="@roll-agent/core";export const PUBLISHED_PACKAGE_UPDATE_STATUSES=["up-to-date","update-available","pinned-behind","unsupported-spec","unknown"];function l(){return s(c(),".roll-agent","update-check.json")}function h(t){return"object"==typeof t&&null!==t&&("latestVersion"in t&&"checkedAt"in t&&("string"==typeof t.latestVersion&&t.latestVersion.length>0&&"number"==typeof t.checkedAt))}function d(t){return"object"==typeof t&&null!==t&&"packages"in t&&("object"==typeof t.packages&&null!==t.packages&&Object.values(t.packages).every(t=>h(t)))}function g(t){return h(t)}function k(){const e=l();if(r(e))try{const n=t(e,"utf-8"),r=JSON.parse(n);if(d(r))return r;if(g(r))return{packages:{[f]:{latestVersion:r.latestVersion,checkedAt:r.checkedAt}}}}catch{}}function m(t){try{const s=l(),a=o(s);r(a)||n(a,{recursive:!0}),e(s,JSON.stringify(t),"utf-8")}catch{}}function V(t,e){const n=k();m({packages:{...n?.packages??{},[t]:{latestVersion:e,checkedAt:Date.now()}}})}function y(t){return k()?.packages[t]}async function w(t){try{const{stdout:e}=await u("npm",["view",t,"version","--json"],{timeout:5e3,encoding:"utf-8"}),n=e.trim();if(0===n.length)return;const r=JSON.parse(n);return"string"==typeof r&&r.length>0?r:void 0}catch{return}}export async function fetchLatestPublishedVersion(t,e={}){const n=e.forceRefresh??!1,r=e.allowNetwork??!0,s=y(t);if(!n&&s&&Date.now()-s.checkedAt<p)return s.latestVersion;if(!r)return s?.latestVersion;const o=await w(t);return o?(V(t,o),o):s?.latestVersion}export function getCurrentVersion(){try{const e=s(import.meta.dirname,"../../../package.json"),n=t(e,"utf-8"),r=JSON.parse(n);if("object"==typeof r&&null!==r&&"version"in r){const t=r.version;if("string"==typeof t)return t}}catch{}return"0.0.0"}export function isNewerVersion(t,e){const n=t=>t.replace(/^v/,"").split(".").map(Number),r=n(t),s=n(e);for(let t=0;t<3;t+=1){const e=r[t]??0,n=s[t]??0;if(e>n)return!0;if(e<n)return!1}return!1}function N(t,e){return!["file:","git+","http://","https://","link:","workspace:","npm:"].some(t=>e.startsWith(t))&&(!(e.startsWith(".")||e.startsWith("/")||e.startsWith("~")||e.endsWith(".tgz")||e.endsWith(".tar.gz"))&&(e===t||e.startsWith(`${t}@`)))}function b(t,e){if(e.startsWith(`${t}@`))return e.slice(t.length+1)}function v(t,e){const n=b(t,e);if(!n)return!1;const r=n.replace(/^v/,"");return/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r)}export async function checkPublishedPackageUpdate(t,e={}){const{packageName:n,packageSpec:r,currentVersion:s}=t;if(!N(n,r))return{packageName:n,...s?{currentVersion:s}:{},status:"unsupported-spec"};if(!s)return{packageName:n,status:"unknown"};const o=await fetchLatestPublishedVersion(n,e);return o?isNewerVersion(o,s)?v(n,r)?{packageName:n,currentVersion:s,latestVersion:o,status:"pinned-behind"}:{packageName:n,currentVersion:s,latestVersion:o,status:"update-available"}:{packageName:n,currentVersion:s,latestVersion:o,status:"up-to-date"}:{packageName:n,currentVersion:s,status:"unknown"}}export async function checkForUpdate(t={}){const e=getCurrentVersion(),n=await fetchLatestPublishedVersion(f,t)??e;return{current:e,latest:n,hasUpdate:isNewerVersion(n,e)}}
@@ -1,4 +1,8 @@
1
1
  import type { AgentSource, AgentSourceType, RegisteredAgent } from "../types/agent.ts";
2
+ export interface InstalledPackageManifestInfo {
3
+ readonly name?: string;
4
+ readonly version?: string;
5
+ }
2
6
  /** 推断 Agent 来源类型,兼容旧 store 数据。 */
3
7
  export declare function inferAgentSourceType(agent: RegisteredAgent): AgentSourceType;
4
8
  /** 适合展示到 CLI 的来源标签。 */
@@ -13,3 +17,4 @@ export declare function sanitizeInstallId(input: string): string;
13
17
  export declare function parsePackageName(packageSpec: string): string;
14
18
  /** 计算 `npm install --prefix` 后包在 node_modules 中的根目录。 */
15
19
  export declare function resolveInstalledPackageRoot(installDir: string, packageName: string): string;
20
+ export declare function readInstalledPackageManifest(packageRoot: string): InstalledPackageManifestInfo | undefined;
@@ -1 +1 @@
1
- import{existsSync as t,readFileSync as e,readdirSync as n}from"node:fs";import{resolve as r}from"node:path";const o="SKILL.md";export function inferAgentSourceType(t){return t.source?t.source.type:inferAgentSourceFromInstallPath(t.installPath,t.transport)?.type??"local-path"}export function formatAgentSourceType(t){switch(t){case"git":return"git";case"installed-package":return"installed";case"remote-manifest":return"remote";case"local-path":return"local-path"}}export function getAgentLocation(t){return"streamable-http"===t.transport.type?t.transport.endpoint:t.installPath}export function inferAgentSourceFromInstallPath(e,n){if(t(r(e,".git"))){const t=i(e);return t?{type:"git",url:t}:{type:"git"}}return t(r(e,o))?{type:"local-path",path:e}:"streamable-http"===n.type?{type:"remote-manifest",endpoint:n.endpoint}:void 0}function i(n){const o=r(n,".git","config");if(t(o))try{const t=e(o,"utf-8").match(/\[remote "origin"\]([\s\S]*?)(?:\n\[|$)/),n=t?.[1]?.match(/^\s*url\s*=\s*(.+)$/m);return n?.[1]?.trim()}catch{return}}export function sanitizeInstallId(t){const e=t.trim().replace(/^@/,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase();return e.length>0?e:"agent"}export function parsePackageName(t){if(t.startsWith("@")){const e=t.indexOf("/");if(-1===e)return t;const n=t.indexOf("@",e+1);return-1===n?t:t.slice(0,n)}const e=t.indexOf("@");return-1===e?t:t.slice(0,e)}export function resolveInstalledPackageRoot(e,n){const o=r(e,"node_modules",...n.split("/"));if(t(o))return o;const i=s(e);if(i){const n=r(e,"node_modules",...i.split("/"));if(t(n))return n}return c(e)??o}function s(n){const o=r(n,"package.json");if(t(o))try{const t=JSON.parse(e(o,"utf-8")),n=Object.keys(t.dependencies??{});return 1===n.length?n[0]:void 0}catch{return}}function c(e){const n=r(e,"node_modules");if(!t(n))return;const i=a(n).filter(e=>t(r(e,"package.json"))&&t(r(e,o)));return 1===i.length?i[0]:void 0}function a(t){const e=[];for(const o of n(t,{withFileTypes:!0})){if(!o.isDirectory()||".bin"===o.name)continue;const i=r(t,o.name);if(o.name.startsWith("@"))for(const t of n(i,{withFileTypes:!0}))t.isDirectory()&&e.push(r(i,t.name));else e.push(i)}return e}
1
+ import{existsSync as t,readFileSync as e,readdirSync as n}from"node:fs";import{resolve as r}from"node:path";const o="SKILL.md";export function inferAgentSourceType(t){return t.source?t.source.type:inferAgentSourceFromInstallPath(t.installPath,t.transport)?.type??"local-path"}export function formatAgentSourceType(t){switch(t){case"git":return"git";case"installed-package":return"installed";case"remote-manifest":return"remote";case"local-path":return"local-path"}}export function getAgentLocation(t){return"streamable-http"===t.transport.type?t.transport.endpoint:t.installPath}export function inferAgentSourceFromInstallPath(e,n){if(t(r(e,".git"))){const t=i(e);return t?{type:"git",url:t}:{type:"git"}}return t(r(e,o))?{type:"local-path",path:e}:"streamable-http"===n.type?{type:"remote-manifest",endpoint:n.endpoint}:void 0}function i(n){const o=r(n,".git","config");if(t(o))try{const t=e(o,"utf-8").match(/\[remote "origin"\]([\s\S]*?)(?:\n\[|$)/),n=t?.[1]?.match(/^\s*url\s*=\s*(.+)$/m);return n?.[1]?.trim()}catch{return}}export function sanitizeInstallId(t){const e=t.trim().replace(/^@/,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase();return e.length>0?e:"agent"}export function parsePackageName(t){if(t.startsWith("@")){const e=t.indexOf("/");if(-1===e)return t;const n=t.indexOf("@",e+1);return-1===n?t:t.slice(0,n)}const e=t.indexOf("@");return-1===e?t:t.slice(0,e)}export function resolveInstalledPackageRoot(e,n){const o=r(e,"node_modules",...n.split("/"));if(t(o))return o;const i=s(e);if(i){const n=r(e,"node_modules",...i.split("/"));if(t(n))return n}return a(e)??o}export function readInstalledPackageManifest(n){const o=r(n,"package.json");if(t(o))try{const t=JSON.parse(e(o,"utf-8")),n="string"==typeof t.name&&t.name.length>0?t.name:void 0,r="string"==typeof t.version&&t.version.length>0?t.version:void 0;if(!n&&!r)return;return{...n?{name:n}:{},...r?{version:r}:{}}}catch{return}}function s(n){const o=r(n,"package.json");if(t(o))try{const t=JSON.parse(e(o,"utf-8")),n=Object.keys(t.dependencies??{});return 1===n.length?n[0]:void 0}catch{return}}function a(e){const n=r(e,"node_modules");if(!t(n))return;const i=c(n).filter(e=>t(r(e,"package.json"))&&t(r(e,o)));return 1===i.length?i[0]:void 0}function c(t){const e=[];for(const o of n(t,{withFileTypes:!0})){if(!o.isDirectory()||".bin"===o.name)continue;const i=r(t,o.name);if(o.name.startsWith("@"))for(const t of n(i,{withFileTypes:!0}))t.isDirectory()&&e.push(r(i,t.name));else e.push(i)}return e}
@@ -1 +1 @@
1
- import{readFileSync as t,writeFileSync as e,existsSync as n,mkdirSync as r}from"node:fs";import{resolve as i,dirname as s}from"node:path";import{inferAgentSourceFromInstallPath as a}from"./source.js";import{AGENT_STATUSES as o,AGENT_STORE_SCHEMA_VERSION as u,createDefaultRuntimeForTransport as c}from"../types/agent.js";const l="agents.json";export class AgentStore{storePath;constructor(t){this.storePath=i(t,l)}list(){return this.load().agents}findByName(t){return this.list().find(e=>e.skill.name===t)}add(t){const e=[...this.list()];if(-1!==e.findIndex(e=>e.skill.name===t.skill.name))throw new Error(`Agent "${t.skill.name}" is already registered`);e.push(t),this.save(e)}remove(t){const e=this.list(),n=e.filter(e=>e.skill.name!==t);return n.length!==e.length&&(this.save([...n]),!0)}replace(t,e){const n=[...this.list()],r=n.findIndex(e=>e.skill.name===t);if(-1===r)return!1;if(-1!==n.findIndex((t,n)=>n!==r&&t.skill.name===e.skill.name))throw new Error(`Agent "${e.skill.name}" is already registered`);return n[r]=e,this.save(n),!0}updateStatus(t,e){const n=this.list().map(n=>n.skill.name===t?{...n,status:e}:n);this.save([...n])}save(t){const i=s(this.storePath);n(i)||r(i,{recursive:!0});const a={schemaVersion:u,agents:t};e(this.storePath,JSON.stringify(a,null,2),"utf-8")}load(){if(!n(this.storePath))return p();let e;try{const n=t(this.storePath,"utf-8");e=JSON.parse(n)}catch{return p()}return f(e)}}function p(){return{schemaVersion:u,agents:[]}}function f(t){if(Array.isArray(t))return{schemaVersion:u,agents:t.flatMap(t=>{const e=d(t);return e?[e]:[]})};if(!D(t))return p();const e=t.agents;return Array.isArray(e)?{schemaVersion:t.schemaVersion===u?u:2,agents:e.flatMap(t=>{const e=d(t);return e?[e]:[]})}:p()}function d(t){if(!D(t))return;const e=m(t.skill),n=k(t.transport),r=j(t.installPath);if(!e||!n||!r)return;const i=v(t.runtime,n),s=S(t.source,r,n),a=j(t.registeredAt)??new Date(0).toISOString(),o=P(t.status),u=j(t.skillBody);return{skill:e,transport:n,runtime:i,installPath:r,registeredAt:a,status:o,...s?{source:s}:{},...u?{skillBody:u}:{}}}function m(t){if(!D(t))return;const e=j(t.name),n=j(t.description);if(!e||!n)return;const r=j(t.license),i=j(t.compatibility),s=g(t.env);return{name:e,description:n,...r?{license:r}:{},...i?{compatibility:i}:{},metadata:h(t.metadata),...s?{env:s}:{}}}function h(t){if(!D(t))return{};const e={};for(const[n,r]of Object.entries(t))e[n]=String(r);return e}function g(t){if(!D(t))return;const e=y(t.required),n=y(t.optional);return e||n?{...e?{required:e}:{},...n?{optional:n}:{}}:void 0}function y(t){if(!Array.isArray(t))return;const e=t.flatMap(t=>{if(!D(t))return[];const e=j(t.name);if(!e)return[];const n=j(t.purpose),r=j(t.example),i=j(t.default);return[{name:e,...n?{purpose:n}:{},...r?{example:r}:{},...i?{default:i}:{}}]});return e.length>0?e:void 0}function k(t){if(!D(t))return;const e=j(t.type);if("stdio"===e){const n=j(t.command);if(!n)return;const r=x(t.args);return r?{type:e,command:n,args:r}:{type:e,command:n}}if("streamable-http"===e){const n=j(t.endpoint);if(!n)return;return{type:e,endpoint:n}}}function v(t,e){if(!D(t))return c(e);const n=j(t.ownership);if("on-demand"===n)return{ownership:n};if("external-managed"===n)return{ownership:n};if("core-managed"===n&&"streamable-http"===e.type){const e=w(t.start),r=A(t.endpoint);if(e&&r){const i=b(t.setup);return i?{ownership:n,start:e,endpoint:r,setup:i}:{ownership:n,start:e,endpoint:r}}}return c(e)}function w(t){if(!D(t))return;const e=j(t.command);if(!e)return;const n=x(t.args);return n?{command:e,args:n}:{command:e}}function A(t){if(!D(t))return;const e=j(t.path),n="number"==typeof t.port?t.port:void 0;return e&&void 0!==n&&Number.isInteger(n)?{path:e,port:n}:void 0}function b(t){if(!D(t))return;const e=t.playwright;if(!D(e))return;const n=x(e.browsers);return n&&0!==n.length?{playwright:{browsers:n}}:void 0}function S(t,e,n){if(!D(t))return a(e,n);const r=j(t.type);if(!r)return a(e,n);switch(r){case"git":return I(j(t.url));case"local-path":return{type:r,path:j(t.path)??e};case"installed-package":{const e=j(t.packageName),n=j(t.packageSpec),i=j(t.installDir);if(!e||!n||!i)return;return{type:r,packageName:e,packageSpec:n,installDir:i}}case"remote-manifest":{const e=j(t.endpoint)??("streamable-http"===n.type?n.endpoint:void 0);return e?{type:r,endpoint:e}:void 0}default:return N(r,t,e,n)}}function N(t,e,n,r){switch(t){case"git":return I(j(e.url));case"local":return{type:"local-path",path:j(e.path)??n};case"installed":{const t=j(e.packageName),n=j(e.packageSpec),r=j(e.installDir);if(!t||!n||!r)return;return{type:"installed-package",packageName:t,packageSpec:n,installDir:r}}case"remote":{const t=a(n,r);if(t&&"remote-manifest"!==t.type)return t;const i=j(e.endpoint)??("streamable-http"===r.type?r.endpoint:void 0);return i?{type:"remote-manifest",endpoint:i}:void 0}}}function P(t){return"string"==typeof t&&o.includes(t)?t:"idle"}function x(t){if(!Array.isArray(t))return;return t.filter(t=>"string"==typeof t)}function j(t){return"string"==typeof t&&t.length>0?t:void 0}function D(t){return"object"==typeof t&&null!==t}function I(t){return t?{type:"git",url:t}:{type:"git"}}
1
+ import{readFileSync as t,writeFileSync as e,existsSync as n,mkdirSync as r}from"node:fs";import{resolve as i,dirname as s}from"node:path";import{inferAgentSourceFromInstallPath as a}from"./source.js";import{AGENT_STATUSES as o,AGENT_STORE_SCHEMA_VERSION as u,createDefaultRuntimeForTransport as c}from"../types/agent.js";const l="agents.json";export class AgentStore{storePath;constructor(t){this.storePath=i(t,l)}list(){return this.load().agents}findByName(t){return this.list().find(e=>e.skill.name===t)}add(t){const e=[...this.list()];if(-1!==e.findIndex(e=>e.skill.name===t.skill.name))throw new Error(`Agent "${t.skill.name}" is already registered`);e.push(t),this.save(e)}remove(t){const e=this.list(),n=e.filter(e=>e.skill.name!==t);return n.length!==e.length&&(this.save([...n]),!0)}replace(t,e){const n=[...this.list()],r=n.findIndex(e=>e.skill.name===t);if(-1===r)return!1;if(-1!==n.findIndex((t,n)=>n!==r&&t.skill.name===e.skill.name))throw new Error(`Agent "${e.skill.name}" is already registered`);return n[r]=e,this.save(n),!0}updateStatus(t,e){const n=this.list().map(n=>n.skill.name===t?{...n,status:e}:n);this.save([...n])}save(t){const i=s(this.storePath);n(i)||r(i,{recursive:!0});const a={schemaVersion:u,agents:t};e(this.storePath,JSON.stringify(a,null,2),"utf-8")}load(){if(!n(this.storePath))return p();let e;try{const n=t(this.storePath,"utf-8");e=JSON.parse(n)}catch{return p()}return f(e)}}function p(){return{schemaVersion:u,agents:[]}}function f(t){if(Array.isArray(t))return{schemaVersion:u,agents:t.flatMap(t=>{const e=d(t);return e?[e]:[]})};if(!j(t))return p();const e=t.agents;return Array.isArray(e)?{schemaVersion:t.schemaVersion===u?u:2,agents:e.flatMap(t=>{const e=d(t);return e?[e]:[]})}:p()}function d(t){if(!j(t))return;const e=m(t.skill),n=k(t.transport),r=x(t.installPath);if(!e||!n||!r)return;const i=v(t.runtime,n),s=S(t.source,r,n),a=x(t.registeredAt)??new Date(0).toISOString(),o=N(t.status),u=x(t.skillBody);return{skill:e,transport:n,runtime:i,installPath:r,registeredAt:a,status:o,...s?{source:s}:{},...u?{skillBody:u}:{}}}function m(t){if(!j(t))return;const e=x(t.name),n=x(t.description);if(!e||!n)return;const r=x(t.license),i=x(t.compatibility),s=g(t.env);return{name:e,description:n,...r?{license:r}:{},...i?{compatibility:i}:{},metadata:h(t.metadata),...s?{env:s}:{}}}function h(t){if(!j(t))return{};const e={};for(const[n,r]of Object.entries(t))e[n]=String(r);return e}function g(t){if(!j(t))return;const e=y(t.required),n=y(t.optional);return e||n?{...e?{required:e}:{},...n?{optional:n}:{}}:void 0}function y(t){if(!Array.isArray(t))return;const e=t.flatMap(t=>{if(!j(t))return[];const e=x(t.name);if(!e)return[];const n=x(t.purpose),r=x(t.example),i=x(t.default);return[{name:e,...n?{purpose:n}:{},...r?{example:r}:{},...i?{default:i}:{}}]});return e.length>0?e:void 0}function k(t){if(!j(t))return;const e=x(t.type);if("stdio"===e){const n=x(t.command);if(!n)return;const r=P(t.args);return r?{type:e,command:n,args:r}:{type:e,command:n}}if("streamable-http"===e){const n=x(t.endpoint);if(!n)return;return{type:e,endpoint:n}}}function v(t,e){if(!j(t))return c(e);const n=x(t.ownership);if("on-demand"===n)return{ownership:n};if("external-managed"===n)return{ownership:n};if("core-managed"===n&&"streamable-http"===e.type){const e=w(t.start),r=A(t.endpoint);if(e&&r){const i=b(t.setup);return i?{ownership:n,start:e,endpoint:r,setup:i}:{ownership:n,start:e,endpoint:r}}}return c(e)}function w(t){if(!j(t))return;const e=x(t.command);if(!e)return;const n=P(t.args);return n?{command:e,args:n}:{command:e}}function A(t){if(!j(t))return;const e=x(t.path),n="number"==typeof t.port?t.port:void 0;return e&&void 0!==n&&Number.isInteger(n)?{path:e,port:n}:void 0}function b(t){if(!j(t))return;const e=t.playwright;if(!j(e))return;const n=P(e.browsers);return n&&0!==n.length?{playwright:{browsers:n}}:void 0}function S(t,e,n){if(!j(t))return a(e,n);const r=x(t.type);if(!r)return a(e,n);switch(r){case"git":return D(x(t.url));case"local-path":return{type:r,path:x(t.path)??e};case"installed-package":{const e=x(t.packageName),n=x(t.packageSpec),i=x(t.installDir),s=x(t.installedVersion);if(!e||!n||!i)return;return{type:r,packageName:e,packageSpec:n,installDir:i,...s?{installedVersion:s}:{}}}case"remote-manifest":{const e=x(t.endpoint)??("streamable-http"===n.type?n.endpoint:void 0);return e?{type:r,endpoint:e}:void 0}default:return V(r,t,e,n)}}function V(t,e,n,r){switch(t){case"git":return D(x(e.url));case"local":return{type:"local-path",path:x(e.path)??n};case"installed":{const t=x(e.packageName),n=x(e.packageSpec),r=x(e.installDir),i=x(e.installedVersion);if(!t||!n||!r)return;return{type:"installed-package",packageName:t,packageSpec:n,installDir:r,...i?{installedVersion:i}:{}}}case"remote":{const t=a(n,r);if(t&&"remote-manifest"!==t.type)return t;const i=x(e.endpoint)??("streamable-http"===r.type?r.endpoint:void 0);return i?{type:"remote-manifest",endpoint:i}:void 0}}}function N(t){return"string"==typeof t&&o.includes(t)?t:"idle"}function P(t){if(!Array.isArray(t))return;return t.filter(t=>"string"==typeof t)}function x(t){return"string"==typeof t&&t.length>0?t:void 0}function j(t){return"object"==typeof t&&null!==t}function D(t){return t?{type:"git",url:t}:{type:"git"}}
@@ -49,6 +49,7 @@ export interface InstalledAgentSource {
49
49
  readonly packageName: string;
50
50
  readonly packageSpec: string;
51
51
  readonly installDir: string;
52
+ readonly installedVersion?: string;
52
53
  }
53
54
  export interface RemoteAgentSource {
54
55
  readonly type: "remote-manifest";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/core",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",