@roll-agent/core 0.15.0 → 0.17.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.
- package/dist/cli/chat/ink/app.js +1 -1
- package/dist/cli/chat/ink/commands.js +1 -1
- package/dist/cli/chat/ink/display-width.d.ts +1 -0
- package/dist/cli/chat/ink/display-width.js +1 -0
- package/dist/cli/chat/ink/editor-keymap.d.ts +29 -0
- package/dist/cli/chat/ink/editor-keymap.js +1 -0
- package/dist/cli/chat/ink/history-item.js +1 -1
- package/dist/cli/chat/ink/line-buffer.d.ts +21 -0
- package/dist/cli/chat/ink/line-buffer.js +1 -0
- package/dist/cli/chat/ink/live-region.js +1 -1
- package/dist/cli/chat/ink/markdown.d.ts +0 -1
- package/dist/cli/chat/ink/markdown.js +1 -1
- package/dist/cli/chat/ink/run-ink-repl.d.ts +1 -1
- package/dist/cli/chat/ink/run-ink-repl.js +1 -1
- package/dist/cli/chat/ink/state.d.ts +6 -0
- package/dist/cli/chat/ink/state.js +1 -1
- package/dist/cli/chat/ink/status-line.js +1 -1
- package/dist/cli/chat/ink/text-prompt.d.ts +4 -0
- package/dist/cli/chat/ink/text-prompt.js +1 -1
- package/dist/cli/chat/ink/use-session.d.ts +1 -0
- package/dist/cli/chat/ink/use-session.js +1 -1
- package/dist/cli/commands/agent-add.js +1 -1
- package/dist/cli/commands/agent-env-guidance.d.ts +2 -0
- package/dist/cli/commands/agent-env-guidance.js +1 -0
- package/dist/cli/commands/agent-install.d.ts +5 -0
- package/dist/cli/commands/agent-install.js +1 -1
- package/dist/cli/commands/agent-list.d.ts +5 -0
- package/dist/cli/commands/agent-list.js +1 -1
- package/dist/cli/commands/ask.js +1 -1
- package/dist/cli/commands/chat.d.ts +3 -0
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/config-guidance.d.ts +21 -0
- package/dist/cli/commands/config-guidance.js +1 -1
- package/dist/cli/commands/config-prompts.d.ts +6 -0
- package/dist/cli/commands/config-prompts.js +1 -1
- package/dist/cli/commands/config-setup.d.ts +6 -1
- package/dist/cli/commands/config-setup.js +1 -1
- package/dist/cli/commands/config.d.ts +1 -0
- package/dist/cli/commands/config.js +1 -1
- package/dist/cli/commands/run.js +1 -1
- package/dist/cli/commands/setup.d.ts +38 -0
- package/dist/cli/commands/setup.js +1 -0
- package/dist/cli/commands/skills-install.d.ts +30 -0
- package/dist/cli/commands/skills-install.js +1 -0
- package/dist/cli/commands/skills.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/utils/catalog-status.d.ts +20 -0
- package/dist/cli/utils/catalog-status.js +1 -0
- package/dist/cli/utils/chat-renderer.d.ts +1 -0
- package/dist/cli/utils/chat-renderer.js +1 -1
- package/dist/cli/utils/git-source.d.ts +7 -0
- package/dist/cli/utils/git-source.js +1 -0
- package/dist/cli/utils/tool-format.d.ts +1 -0
- package/dist/cli/utils/tool-format.js +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/helpers.d.ts +17 -0
- package/dist/config/helpers.js +1 -1
- package/dist/config/key-codec.js +1 -1
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/migration.d.ts +5 -1
- package/dist/config/migration.js +1 -1
- package/dist/config/runtime-env.d.ts +30 -30
- package/dist/config/schema.d.ts +250 -0
- package/dist/config/schema.js +1 -1
- package/dist/llm/providers.d.ts +12 -0
- package/dist/llm/providers.js +1 -1
- package/dist/mcp/client-manager.d.ts +5 -1
- package/dist/mcp/client-manager.js +1 -1
- package/dist/mcp/sampling-handler.d.ts +14 -2
- package/dist/mcp/sampling-handler.js +1 -1
- package/dist/registry/catalog-discovery.d.ts +30 -0
- package/dist/registry/catalog-discovery.js +1 -0
- package/dist/registry/catalog.d.ts +42 -0
- package/dist/registry/catalog.js +1 -0
- package/dist/registry/discovery.js +1 -1
- package/dist/registry/index.d.ts +4 -0
- package/dist/registry/index.js +1 -1
- package/dist/registry/install.d.ts +69 -0
- package/dist/registry/install.js +1 -0
- package/dist/skills/documents.js +1 -1
- package/dist/skills/install.d.ts +17 -0
- package/dist/skills/install.js +1 -0
- package/dist/skills/library.d.ts +6 -0
- package/dist/skills/library.js +1 -1
- package/dist/skills/orchestrators.d.ts +39 -0
- package/dist/skills/orchestrators.js +1 -0
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as
|
|
1
|
+
import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as l}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"runtime.approval.default",title:"Chat 工具确认默认策略",purpose:"`roll chat` 调用工具时的默认确认策略。`guarded` 使用内置读写启发式,`auto` 默认放行非破坏性工具,`deny` 默认拒绝。",defaultBehavior:`默认值为 \`${e.runtime.approval.default}\`。此配置不影响 \`roll ask\`。`,example:`runtime:\n approval:\n default: ${e.runtime.approval.default}`},{path:"runtime.approval.overrides",title:"Chat 工具确认精确覆盖",purpose:"`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。",defaultBehavior:"未配置时回退到 `runtime.approval.default`。",example:"runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto"},{path:"runtime.shell.enabled",title:"Chat 内建 shell 工具",purpose:"开启后 `roll chat` 注册内建 shell 工具:macOS/Linux 为 `roll__bash`,Windows 原生为 `roll__powershell`(需 PowerShell 7+)。命令继承 roll 进程环境变量;默认确认策略可由 `runtime.approval` 及精确 override 覆盖。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.enabled)}\`(关闭)。Windows 未检测到 pwsh 7+ 时会跳过注册。`,example:"runtime:\n shell:\n enabled: true",setupCommand:"roll config setup shell"},{path:"runtime.shell.auto-approve-safe",title:"只读命令免确认",purpose:"开启后用内建规则分类器识别 POSIX known-safe 只读命令(如 `ls`、`cat`、`git status`),在 `guarded`/`auto` 策略下免确认执行;dangerous(`rm -rf`、`sudo`)与无法识别的命令默认仍需确认。工具级显式 approval override 优先;Windows PowerShell 当前全部按 unknown 处理。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.autoApproveSafe)}\`。关闭后 POSIX 回归默认逐条确认;Windows PowerShell 默认逐条确认,显式 approval override 可覆盖。`,example:"runtime:\n shell:\n enabled: true\n auto-approve-safe: false",setupCommand:"roll config setup shell"},{path:"runtime.shell.session.enabled",title:"Chat 会话式长命令执行",purpose:"开启后在 POSIX shell 后端注册 `roll__exec_command` + `roll__exec_poll` 工具:命令在后台会话执行、跨轮存活,模型轮询进度并读取退出码,适合超过单轮超时的长脚本。Windows 原生 session exec 暂未支持。仅交互 REPL 与 `--server` 长驻模式注册(单条消息 / `--json` 单轮会话随进程结束,不提供该工具);`--server` 下 `exec_command` 需 `runtime.approval.overrides` 里 `roll.exec_command: auto` 显式授权。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.session.enabled)}\`(关闭)。需同时开启 runtime.shell.enabled;背景会话随 roll chat 进程退出而终止。`,example:"runtime:\n shell:\n enabled: true\n session:\n enabled: true\n approval:\n overrides:\n roll.exec_command: auto",setupCommand:"roll config setup shell"},{path:"skills.dirs",title:"Chat 额外 skill 目录",purpose:"`roll chat` 除自动发现项目与用户级 `.agents/skills`(`npx skills add` 的标准安装位置)及已注册 Agent 的 SKILL.md 外,额外加载的 skill 目录。目录可以直接包含 SKILL.md,也可以是多个 skill 子目录的集合。",defaultBehavior:"默认为空。标准路径 `<项目>/.agents/skills` 与 `~/.agents/skills` 始终自动发现。",example:"skills:\n dirs:\n - ./openclaw-roll-core-skill-template"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=l(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>t(e.path,r))}function t(e,t){const r=l(e.split(".")).join(".");if(r===t)return!0;const a=r.split("."),n=t.split(".");return a.length===n.length&&a.every((e,l)=>e.startsWith("<")||e===n[l])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
|
|
@@ -33,5 +33,11 @@ export interface ConfigPromptAdapter {
|
|
|
33
33
|
readonly message: string;
|
|
34
34
|
readonly initialValue?: boolean;
|
|
35
35
|
}): Promise<boolean>;
|
|
36
|
+
multiselect<Value extends string>(options: {
|
|
37
|
+
readonly message: string;
|
|
38
|
+
readonly options: readonly PromptOption<Value>[];
|
|
39
|
+
readonly initialValues?: readonly Value[];
|
|
40
|
+
readonly required?: boolean;
|
|
41
|
+
}): Promise<readonly Value[]>;
|
|
36
42
|
}
|
|
37
43
|
export declare const clackPromptAdapter: ConfigPromptAdapter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cancel as e,confirm as
|
|
1
|
+
import{cancel as e,confirm as a,intro as t,isCancel as l,log as i,multiselect as u,outro as n,password as o,select as r,text as s}from"@clack/prompts";const p=process.stderr;export class ConfigSetupCancelledError extends Error{constructor(){super("配置向导已取消")}}export const clackPromptAdapter={intro(e){t(e,{output:p})},outro(e){n(e,{output:p})},info(e){i.info(e,{output:p})},warn(e){i.warn(e,{output:p})},async select(e){const a=e.options.map(e=>e.hint?{value:e.value,label:e.label,hint:e.hint}:{value:e.value,label:e.label});return c(await r({message:e.message,options:a,output:p,...e.initialValue?{initialValue:e.initialValue}:{}}))},async text(e){const a=e.placeholder??e.defaultValue;return c(await s({message:e.message,output:p,...a?{placeholder:a}:{},...void 0!==e.defaultValue?{defaultValue:e.defaultValue}:{},...void 0!==e.initialValue?{initialValue:e.initialValue}:{},validate(a){const t=a??"";if(0!==t.trim().length||void 0===e.defaultValue)return e.required&&0===t.trim().length?"此项不能为空":e.validate?.(t)}}))},password:async e=>c(await o({message:e.message,output:p,validate(a){const t=a??"";return e.required&&0===t.trim().length?"此项不能为空":e.validate?.(t)}})),confirm:async e=>c(await a({message:e.message,output:p,...void 0!==e.initialValue?{initialValue:e.initialValue}:{}})),async multiselect(e){const a=e.options.map(e=>e.hint?{value:e.value,label:e.label,hint:e.hint}:{value:e.value,label:e.label});return c(await u({message:e.message,options:a,output:p,required:e.required??!1,...e.initialValues?{initialValues:[...e.initialValues]}:{}})).map(e=>e)}};function c(a){if(l(a))throw e("已取消",{output:p}),new ConfigSetupCancelledError;return a}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type ConfigPromptAdapter } from "./config-prompts.ts";
|
|
2
|
-
export type ConfigSetupModule = "llm" | "install" | "agent";
|
|
2
|
+
export type ConfigSetupModule = "llm" | "install" | "agent" | "shell";
|
|
3
3
|
export declare function runConfigSetup(moduleArg: string | undefined, valueArg: string | undefined, prompts?: ConfigPromptAdapter): Promise<void>;
|
|
4
|
+
export declare function setupLlm(prompts: ConfigPromptAdapter): Promise<string>;
|
|
5
|
+
export declare function setupInstall(prompts: ConfigPromptAdapter): Promise<string>;
|
|
6
|
+
export declare function setupShell(prompts: ConfigPromptAdapter, platform?: NodeJS.Platform): Promise<string>;
|
|
7
|
+
export declare function setupBash(prompts: ConfigPromptAdapter): Promise<string>;
|
|
8
|
+
export declare function setupAgentEnv(agentNameArg: string | undefined, prompts: ConfigPromptAdapter): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t,writeFileSync as r}from"node:fs";import{
|
|
1
|
+
import{existsSync as e,readFileSync as t,writeFileSync as r}from"node:fs";import{homedir as n}from"node:os";import{resolve as a}from"node:path";import{stringify as i}from"yaml";import{DEFAULT_CONFIG as o,DEFAULT_LLM_MODELS as s,DEFAULT_LLM_PROVIDER as l,LLM_PROVIDER_OPTIONS as c}from"../../config/defaults.js";import{inspectAgentEnvRequirements as u}from"../../config/helpers.js";import{loadAgentsConfig as m,parseConfigDocument as f,resolveConfigPath as d,validateConfigText as g}from"../../config/loader.js";import{encodePathToYaml as p}from"../../config/key-codec.js";import{detectKnownConfigMigrations as h,formatConfigMigrationError as v}from"../../config/migration.js";import{AgentStore as w}from"../../registry/store.js";import{flattenAgentEnvDeclarations as y,isSecretEnvName as k}from"./config-guidance.js";import{ConfigSetupCancelledError as $,clackPromptAdapter as b}from"./config-prompts.js";const A=/\$\{[^}]+\}/u;export async function runConfigSetup(e,t,r=b){try{r.intro("Roll 配置向导"),q();switch(await x(e,r)){case"llm":r.outro(await setupLlm(r));break;case"install":r.outro(await setupInstall(r));break;case"agent":r.outro(await setupAgentEnv(t,r));break;case"shell":r.outro(await setupShell(r))}}catch(e){if(e instanceof $)return void(process.exitCode=1);throw e}}async function x(e,t){if(void 0===e)return t.select({message:"要配置哪一类内容?",options:[{value:"llm",label:"LLM",hint:"默认模型和 provider key"},{value:"install",label:"Install network",hint:"npm registry、重试和超时"},{value:"agent",label:"Agent env",hint:"配置某个 Agent 需要的环境变量"},{value:"shell",label:"Chat shell 工具",hint:"roll chat 内建本机命令执行能力(默认关闭)"}]});if("llm"===e||"install"===e||"agent"===e||"shell"===e)return e;if("bash"===e)return"shell";throw new Error(`未知 setup 模块: ${e}。可用: llm, install, agent, shell`)}export async function setupLlm(e){q();const t=await e.select({message:"选择默认 LLM provider",options:c.map(e=>({value:e,label:e,hint:s[e]})),initialValue:l}),r=await e.text({message:"默认 model",defaultValue:s[t],required:!0}),n=await e.password({message:`${t} API key(可直接输入真实 key,也可输入 \${ENV_VAR})`,required:!0}),a=await e.text({message:"自定义 base-url(可选,留空则不写)",required:!1}),i=q();return N(i.document,["llm","defaultProvider"],t),N(i.document,["llm","defaultModel"],r),N(i.document,["llm","providers",t,"apiKey"],n),a.trim().length>0?N(i.document,["llm","providers",t,"baseUrl"],a.trim()):O(i.document,["llm","providers",t,"baseUrl"]),R(i),D(e,`${t} API key`,n),`已配置 LLM: ${t}/${r}(写入 ${i.configPath})`}export async function setupInstall(e){q();const t=await e.select({message:"当前安装/更新网络更接近哪种场景?",options:[{value:"default-network",label:"默认网络 / 海外网络",hint:"不设置 registry"},{value:"china-dev",label:"国内开发机",hint:"使用 https://registry.npmmirror.com"},{value:"private-registry",label:"企业内网 / 私有 registry"},{value:"advanced",label:"高级自定义"}]}),r=await S(t,e),n=q();switch(r.scenario){case"default-network":P(n.document),O(n.document,["install","registry"]);break;case"china-dev":P(n.document),N(n.document,["install","registry"],"https://registry.npmmirror.com");break;case"private-registry":P(n.document),N(n.document,["install","registry"],r.registry);break;case"advanced":{const{registry:e,fetchRetries:t,preferOffline:a,networkTimeoutMs:i}=r.values;N(n.document,["install","fetchRetries"],t),N(n.document,["install","preferOffline"],a),N(n.document,["install","networkTimeoutMs"],i),e.length>0?N(n.document,["install","registry"],e):O(n.document,["install","registry"]);break}}return R(n),`已配置 install 网络参数(写入 ${n.configPath})`}export async function setupShell(e,t=process.platform){q();const r=await e.confirm({message:"启用 roll chat 内建 shell 工具(允许模型在本机执行命令;默认确认策略受 runtime.approval 及显式 override 控制)?",initialValue:!1});let n=!0,a=!1;const i="win32"===t;r&&i&&e.warn("Windows 原生 shell 当前仅支持 PowerShell 7 one-shot;安全命令自动放行和 session exec 暂不生效。默认每条命令都需确认;显式 approval override(roll.powershell: auto)可覆盖此默认策略。"),r&&!i&&(n=await e.confirm({message:"安全只读命令(ls/grep 等)自动放行,无需逐条确认?",initialValue:!0}),a=await e.confirm({message:"启用长跑命令会话(session exec,供 dev server 等常驻进程使用)?",initialValue:!1}));const o=q();N(o.document,["runtime","shell","enabled"],r),r&&!i&&(N(o.document,["runtime","shell","autoApproveSafe"],n),N(o.document,["runtime","shell","session","enabled"],a)),R(o);return`已${r?"启用":"禁用"} chat shell 工具${r&&i?";Windows 当前仅启用 PowerShell one-shot,默认逐条确认(显式 approval override 可覆盖)":""}(写入 ${o.configPath})`}export async function setupBash(e){return setupShell(e)}async function S(e,t){switch(e){case"default-network":case"china-dev":return{scenario:e};case"private-registry":return{scenario:e,registry:(await t.text({message:"私有 registry URL",placeholder:"https://registry.example.com",required:!0})).trim()};case"advanced":return{scenario:e,values:await V(t)}}}async function V(e){const t=await e.text({message:"npm registry(留空则不写)",placeholder:"https://registry.npmmirror.com"}),r=await e.text({message:"fetch-retries",defaultValue:"3",required:!0,validate:T(0,10)}),n=await e.confirm({message:"是否启用 prefer-offline?",initialValue:!1}),a=await e.text({message:"network-timeout-ms",defaultValue:"120000",required:!0,validate:T(1e4,Number.MAX_SAFE_INTEGER)});return{registry:t.trim(),fetchRetries:Number.parseInt(r,10),preferOffline:n,networkTimeoutMs:Number.parseInt(a,10)}}export async function setupAgentEnv(e,t){q();const{agentsConfig:r}=m(),n=new w(r.dataDir),a=await E(n.list(),e,t),i=u(a.skill.name,a.skill.env,r.env);if(!i)throw new Error(`Agent "${a.skill.name}" 未声明环境变量需求。`);const o={...r.env?.[a.skill.name]??{}},s=y(a.skill.env),l=new Map(i.items.map(e=>[e.name,e]));for(const e of s.filter(e=>e.required)){const r=l.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}if(await t.confirm({message:"是否配置可选环境变量?",initialValue:!1}))for(const e of s.filter(e=>!e.required)){const r=l.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}const c=q();N(c.document,["agents","env",a.skill.name],o),R(c);for(const[e,r]of Object.entries(o))k(e)&&D(t,e,r);return Y(t,a),`已配置 Agent 环境变量: ${a.skill.name}(写入 ${c.configPath})`}async function E(e,t,r){if(void 0!==t){const r=e.find(e=>e.skill.name===t);if(!r)throw new Error(`Agent "${t}" 未找到`);if(!M(r))throw new Error(`Agent "${t}" 未声明环境变量需求。`);return r}const n=e.filter(M);if(0===n.length)throw new Error("没有已注册且声明环境变量需求的 Agent。");const a=await r.select({message:"选择要配置的 Agent",options:n.map(e=>({value:e.skill.name,label:e.skill.name,hint:e.skill.description}))}),i=n.find(e=>e.skill.name===a);if(!i)throw new Error(`Agent "${a}" 未找到`);return i}function M(e){return y(e.skill.env).length>0}async function j(e,t){const{declaration:r,processEnvValue:n,source:a,yamlValue:i}=e,o="agents.env"===a&&C(i),s=[r.required?"必填":"可选",o?"回车保留当前值":void 0].filter(e=>void 0!==e).join(","),l=`${r.name}(${s})`,c=[r.purpose?`用途: ${r.purpose}`:void 0,r.example?`示例: ${r.example}`:void 0,r.default?`默认: ${r.default}`:void 0,o?"当前 YAML 已配置,回车保留当前值。":void 0,void 0===i||o?void 0:"当前 YAML 值为空或占位符未解析,会被视为缺失。","process.env"===a?"当前来源: 当前 shell 临时环境变量,尚未持久写入 YAML。":void 0].filter(e=>void 0!==e).join("\n");if(c.length>0&&t.info(c),"process.env"===a&&"string"==typeof n&&n.length>0){if(await t.confirm({message:`是否将 ${r.name} 的当前 shell 值写入 roll.config.yaml?`,initialValue:!0}))return n}const u=r.required&&!o&&void 0===r.default&&"process.env"!==a,m={message:l,...o&&void 0!==i?{defaultValue:i}:{},...void 0!==r.example?{placeholder:r.example}:{},required:u},f=k(r.name)?await t.password({message:l,required:u}):await t.text(m);return 0===f.trim().length?o?i:void 0:f}function P(e){N(e,["install","fetchRetries"],o.install.fetchRetries),N(e,["install","preferOffline"],o.install.preferOffline),N(e,["install","networkTimeoutMs"],o.install.networkTimeoutMs)}function q(){const r=d(),i=r??a(n(),"roll.config.yaml");if(void 0===r||!e(i))return{configPath:i,existed:!1,document:L()};const o=t(i,"utf-8"),s=f(o,i),l=h(s);if(l.needsMigration)throw new Error(v(i,l));return{configPath:i,existed:!0,raw:o,document:s}}function L(){return{llm:{"default-provider":o.llm.defaultProvider,"default-model":o.llm.defaultModel,providers:{}},ask:{},agents:{"data-dir":o.agents.dataDir}}}function R(e){const t=i(e.document,{lineWidth:0});g(t,e.configPath),e.existed&&void 0!==e.raw&&r(I(e.configPath),e.raw,"utf-8"),r(e.configPath,t,"utf-8")}function I(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 N(e,t,r){const n=p(t),a=n[n.length-1];if(void 0===a)throw new Error("配置路径不能为空");let i=e;for(const e of n.slice(0,-1)){const t=i[e];("object"!=typeof t||null===t||Array.isArray(t))&&(i[e]={}),i=i[e]}i[a]=r}function O(e,t){const r=p(t),n=r[r.length-1];if(void 0===n)return;let a=e;for(const e of r.slice(0,-1)){if("object"!=typeof a||null===a||Array.isArray(a))return;a=a[e]}"object"!=typeof a||null===a||Array.isArray(a)||delete a[n]}function T(e,t){return r=>{if(!/^\d+$/u.test(r))return"请输入整数";const n=Number.parseInt(r,10);return n<e||n>t?`请输入 ${String(e)} 到 ${String(t)} 之间的整数`:void 0}}function C(e){return"string"==typeof e&&e.length>0&&!A.test(e)}function D(e,t,r){0===r.trim().length||A.test(r)||e.warn(`${t} 将以明文写入 roll.config.yaml,建议改用 \${ENV_VAR} 引用并避免把配置文件提交到版本库。`)}function Y(e,t){const r=t.skill.name;switch(t.runtime.ownership){case"core-managed":e.info(`需重启后生效:roll agent stop ${r} && roll agent start ${r}`);break;case"external-managed":e.info(`请重启你自行管理的 "${r}" 进程以加载新环境变量。`);break;case"on-demand":e.info(`下次 roll run / roll ask 调用 "${r}" 时自动生效。`)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineCommand as o}from"citty";import{readFileSync as
|
|
1
|
+
import{defineCommand as o}from"citty";import{readFileSync as t,writeFileSync as e,existsSync as r}from"node:fs";import{homedir as n}from"node:os";import{resolve as i}from"node:path";import{createInterface as s}from"node:readline/promises";import{stringify as c}from"yaml";import{DEFAULT_CONFIG as l,DEFAULT_LLM_MODELS as a,LLM_PROVIDER_OPTIONS as f}from"../../config/defaults.js";import{inspectConfigFile as d,loadConfig as u,parseConfigDocument as p,resolveConfigPath as g,validateConfigText as m}from"../../config/loader.js";import{encodePathToYaml as v,normalizeUserPath as y}from"../../config/key-codec.js";import{applyKnownConfigMigrations as h}from"../../config/migration.js";import{explainConfig as $}from"./config-explain.js";import{runConfigSetup as P}from"./config-setup.js";export default o({meta:{description:"管理全局配置"},args:{action:{type:"positional",description:"操作(init/get/set/migrate/setup/explain)",required:!0},key:{type:"positional",description:"配置键(get/set 时使用,用英文句点 `.` 分隔,如 ask.confirm-threshold)",required:!1},value:{type:"positional",description:"配置值(set 时使用)",required:!1}},async run({args:o}){try{if("init"===o.action)return void await A();if("get"===o.action)return void q(o.key);if("set"===o.action)return void Y(o.key,o.value);if("migrate"===o.action)return void migrateConfig();if("setup"===o.action)return process.stdin.isTTY?void await P(o.key,o.value):(console.error("✗ `roll config setup` 需要交互式终端。"),console.error(" 非交互环境请改用 `roll config set <key> <value>` 或直接编辑 roll.config.yaml。"),console.error(" 查看配置说明:`roll config explain <key>`。"),void(process.exitCode=1));if("explain"===o.action)return void $(o.key);console.error(`✗ 未知操作: ${o.action}。可用: init, get, set, migrate, setup, explain`),process.exitCode=1}catch(o){const t=o instanceof Error?o.message:String(o);console.error(`✗ ${t}`),process.exitCode=1}}});const S={anthropic:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY",qwen:"DASHSCOPE_API_KEY",deepseek:"DEEPSEEK_API_KEY"};function w(o,t){const e=o?.trim();return e||t}function k(o){return f.some(t=>t===o)}function E(o){return k(o)?a[o]:l.llm.defaultModel}function x(o){return k(o)?S[o]:S.anthropic}function C({provider:o,model:t,apiKeyEnv:e}){return`llm:\n default-provider: ${o}\n default-model: ${t}\n providers:\n ${o}:\n api-key: \${${e}}\n\nask:\n confirm-threshold: 0.5\n\nagents:\n data-dir: ~/.roll-agent/agents\n`}async function j(){if(!process.stdin.isTTY){const[o,e,r]=t(0,"utf-8").split(/\r?\n/u),n=w(o,l.llm.defaultProvider);return{provider:n,model:w(e,E(n)),apiKeyEnv:w(r,x(n))}}const o=s({input:process.stdin,output:process.stderr}),e=w(await o.question(`默认 LLM provider (${f.join("/")}) [${l.llm.defaultProvider}]: `),l.llm.defaultProvider),r=E(e),n=w(await o.question(`默认 model [${r}]: `),r),i=x(e),c=w(await o.question(`API Key 环境变量名 [${i}]: `),i);return o.close(),{provider:e,model:n,apiKeyEnv:c}}async function A(){const o=g()??i(n(),"roll.config.yaml");if(r(o)){const t=d({configPath:o});switch(t.status){case"needs-migration":console.error(`⚠ 现有配置文件需要迁移: ${o}`),console.error(" 建议先运行 `roll config migrate`,再决定是否重新初始化。");break;case"invalid":console.error(`⚠ 现有配置文件存在问题:\n${t.error.message}`)}if(console.error(`⚠ 配置文件已存在: ${o}`),!process.stdin.isTTY)throw new Error("非交互模式下不会覆盖现有配置文件,请手动删除后重试。");const e=s({input:process.stdin,output:process.stderr}),r=await e.question("是否覆盖?(y/N) ");if(e.close(),"y"!==r.toLowerCase())return void console.error("已取消。")}const t=C(await j());m(t,o),e(o,t,"utf-8"),console.log(`✓ 配置文件已创建: ${o}`)}function K(o){const t=new Date;return`${o}.bak.${[t.getFullYear().toString().padStart(4,"0"),(t.getMonth()+1).toString().padStart(2,"0"),t.getDate().toString().padStart(2,"0"),"-",t.getHours().toString().padStart(2,"0"),t.getMinutes().toString().padStart(2,"0"),t.getSeconds().toString().padStart(2,"0")].join("")}`}export function migrateConfig(){const o=d();if("not-found"===o.status)throw new Error("未找到配置文件。请先运行 roll config init");if("valid"===o.status)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);if("invalid"===o.status)throw o.error;const t=p(o.raw,o.configPath),r=h(t);if(!r.ok){const o=r.issues.map(o=>` - ${o.message}`).join("\n");throw new Error(`配置无法自动迁移:\n${o}`)}if(!r.changed)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);const n=c(r.document,{lineWidth:0});m(n,o.configPath);const i=K(o.configPath);e(i,o.raw,"utf-8"),e(o.configPath,n,"utf-8"),console.log(`✓ 配置文件已迁移: ${o.configPath}`),console.log(`✓ 已备份原文件: ${i}`);for(const o of r.summary)console.log(` - ${o}`)}function q(o){const{config:t,configPath:e}=u();if(!o)return console.log(JSON.stringify(t,null,2)),void(e&&console.error(`(来源: ${e})`));const r=y(o.split("."));let n=t;for(const t of r){if("object"!=typeof n||null===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);n=n[t]}if(void 0===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);console.log("object"==typeof n?JSON.stringify(n,null,2):String(n))}function Y(o,r){if(!o||void 0===r)return console.error("✗ 用法: roll config set <key> <value>"),console.error(" 示例: roll config set ask.confirm-threshold 0.5"),void(process.exitCode=1);const{configPath:n}=u();if(!n)return console.error("✗ 未找到配置文件。请先运行 roll config init"),void(process.exitCode=1);const i=t(n,"utf-8"),s=p(i,n),l=v(o.split(".")),a=l[l.length-1];if(void 0===a)return console.error("✗ 配置键不能为空"),void(process.exitCode=1);let f=s;for(let o=0;o<l.length-1;o++){const t=l[o],e=f[t];("object"!=typeof e||null===e||Array.isArray(e))&&(f[t]={}),f=f[t]}let d=r;"true"===r?d=!0:"false"===r?d=!1:/^\d+(\.\d+)?$/.test(r)&&(d=Number(r)),f[a]=d;const g=c(s,{lineWidth:0});m(g,n),e(n,g,"utf-8"),console.log(`✓ ${o} = ${String(d)}`),console.error(` (已写入: ${n})`)}
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as t}from"node:fs";import{defineCommand as o}from"citty";import{loadConfig as n}from"../../config/loader.js";import{getAgentEnv as r,getMissingAgentEnvRuntimeIssues as e,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{AgentStore as s}from"../../registry/store.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{
|
|
1
|
+
import{readFileSync as t}from"node:fs";import{defineCommand as o}from"citty";import{loadConfig as n}from"../../config/loader.js";import{getAgentEnv as r,getMissingAgentEnvRuntimeIssues as e,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{AgentStore as s}from"../../registry/store.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{resolveLLMCall as c,toSamplingConnectOptions as u}from"../../llm/providers.js";import{formatValidationIssuesMessage as f}from"../../tool-runtime/messages.js";import{preflightToolCall as p}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as g,normalizeListedTools as h}from"../utils/agent-tools.js";import{extractTextContent as m,formatToolResultForJsonOutput as d,isToolErrorResult as b}from"../utils/tool-results.js";import{log as w,redactToolArgsForLog as y}from"../utils/output.js";import{shouldSkipRuntimeReadinessForTool as j}from"../../config/runtime-env.js";export default o({meta:{description:"直接调用已注册 Agent 的指定 MCP tool"},args:{agent:{type:"positional",description:"Agent 名称",required:!1},tool:{type:"positional",description:"MCP tool 名称",required:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1},bail:{type:"boolean",description:"batch 模式下遇到第一条失败即停止",default:!1},"input-json":{type:"string",description:"以 JSON 字符串提供完整 tool 输入对象;不能与 --input-file 同用"},"input-file":{type:"string",description:"从 JSON 文件读取完整 tool 输入对象;不能与 --input-json 同用"},"batch-json":{type:"string",description:"以 JSON array 提供多条调用;每项包含 agent/tool/input"},"batch-file":{type:"string",description:"从 JSON 文件读取 batch array;不能与 --batch-json 同用"},"batch-stdin":{type:"boolean",description:"从 stdin 读取 batch array;不能与 --batch-json 或 --batch-file 同用",default:!1}},async run({args:t,rawArgs:o}){const{config:r}=n(),e=new s(r.agents.dataDir),i=new l,a=new Map;try{const n=P(r),s={store:e,config:r,clientManager:i,agentConnections:a,...n?{samplingCall:n}:{}};if(x(o)&&I(o)>0)return w.error("batch 模式不接受 agent/tool 位置参数;请在每个 batch item 中声明 agent 和 tool"),void(process.exitCode=1);const l=parseBatchInput(o,{readStdin:T});if(l){const o=await D({items:l,...s,bail:!0===t.bail});return t.json?console.log(JSON.stringify(o.map(formatRunToolResultForJsonOutput),null,2)):L(o),void(o.some(t=>!t.ok)&&(process.exitCode=1))}const c=M(t.agent,"agent"),u=M(t.tool,"tool");if(!c||!u)return w.error("roll run 需要提供 agent/tool 位置参数,或使用 --batch-json / --batch-file / --batch-stdin 进入 batch 模式"),void(process.exitCode=1);let f;try{f=resolveToolArgs(o)}catch(t){return w.error(t instanceof Error?t.message:String(t)),void(process.exitCode=1)}const p=await F({item:{agent:c,tool:u,input:f},index:0,...s});p.ok?(t.json?console.log(JSON.stringify(d(p.result),null,2)):K(p.result),w.success("调用完成")):(t.json?console.log(JSON.stringify(formatRunToolResultForJsonOutput(p),null,2)):(w.error(p.error),void 0!==p.result&&K(p.result)),process.exitCode=1)}catch(t){const o=t instanceof Error?t.message:String(t),n=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";w.error(`${o}${n}`),process.exitCode=1}finally{await i.disconnectAll()}}});const S=new Set(["json","verbose","v","bail","help","h","version"]),$=new Set(["batch-stdin"]),v=new Set(["config","input-json","input-file","batch-json","batch-file"]);function E(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function N(t,o){let n;try{n=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!E(n))throw new Error(`${o} 必须是 JSON object`);return n}function J(t,o){let n;try{n=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!Array.isArray(n))throw new Error(`${o} 必须是 JSON array`);return n}function O(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]!==`--${o}`){n++;continue}const r=t[n+1];if(!r||r.startsWith("--"))throw new Error(`选项 --${o} 需要提供值`);return r}}function k(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]===`--${o}`)return!0;n++}return!1}function x(t){return A(t,"batch-json")||A(t,"batch-file")||A(t,"batch-stdin")}function A(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]===`--${o}`)return!0;n++}return!1}export function parseExplicitToolInput(o){const n=O(o,"input-json"),r=O(o,"input-file"),e=R(o);if([n,r,e].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 positional JSON、--input-json 和 --input-file");if(n)return N(n,"--input-json");if(r){return N(t(r,"utf-8"),`输入文件 ${r}`)}return e?N(e,"positional JSON input"):void 0}export function parseBatchInput(o,n={}){const r=O(o,"batch-json"),e=O(o,"batch-file"),i=k(o,"batch-stdin"),s=O(o,"input-json"),l=O(o,"input-file");if([r,e,i?"--batch-stdin":void 0].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 --batch-json、--batch-file 和 --batch-stdin");if(!r&&!e&&!i)return;if(s||l)throw new Error("batch 模式不能同时使用 --input-json 或 --input-file");const a=r?"--batch-json":e?`batch 文件 ${e}`:"--batch-stdin",c=J(r??(e?t(e,"utf-8"):C(n)),a);if(0===c.length)throw new Error(`${a} 至少需要包含一条调用`);return c.map((t,o)=>W(t,o))}function C(t){if(!t.readStdin)throw new Error("--batch-stdin 需要可用的 stdin 读取器");return t.readStdin()}function T(){if(process.stdin.isTTY)throw new Error("--batch-stdin 需要从 stdin 管道或文件重定向读取 JSON array");return t(0,"utf-8")}function W(t,o){const n=`batch[${String(o)}]`;if(!E(t))throw new Error(`${n} 必须是 JSON object`);const r=t.agent,e=t.tool,i="input"in t?t.input:{},s=t.label;if("string"!=typeof r||0===r.trim().length)throw new Error(`${n}.agent 必须是非空字符串`);if("string"!=typeof e||0===e.trim().length)throw new Error(`${n}.tool 必须是非空字符串`);if(void 0!==i&&!E(i))throw new Error(`${n}.input 必须是 JSON object`);if(void 0!==s&&"string"!=typeof s)throw new Error(`${n}.label 必须是字符串`);return{agent:r,tool:e,input:i??{},...void 0!==s?{label:s}:{}}}function R(t){const o=[];for(const n of t){if(n.startsWith("--"))break;o.push(n)}const n=o.slice(2);if(0!==n.length){if(1===n.length&&n[0]?.trim().startsWith("{"))return n[0];throw new Error("roll run 只接受 agent/tool 两个位置参数;tool 输入请使用 --key value、--input-json、--input-file,或第三个位置参数 JSON object")}}function I(t){let o=0;for(const n of t){if(n.startsWith("--"))return o;o+=1}return o}export function resolveToolArgs(t){return{...parseExplicitToolInput(t)??{},...parseToolArgs(t)}}export function parseToolArgs(t){const o={};let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){const r=t[n];if(!r?.startsWith("--")){n++;continue}const e=r.slice(2),i=t[n+1];if(S.has(e)||$.has(e))n++;else if(v.has(e))n+=i&&!i.startsWith("--")?2:1;else if(!i||i.startsWith("--"))o[e]=!0,n++;else{const t=Number(i);o[e]=Number.isNaN(t)?i:t,n+=2}}return o}function M(t,o){if("string"==typeof t&&0!==t.trim().length)return t;w.error(`缺少必填位置参数:${o}`)}function P(t){const o=t.llm.defaultProvider,n=t.llm.providers[o];return n?c(o,t.llm.defaultModel,n.apiKey,"sampling",n.baseUrl,t.runtime.thinkingLevel):void 0}async function B(t){const o=t.agentConnections.get(t.item.agent);if(o)return o;const n=t.store.findByName(t.item.agent);if(!n)return`Agent "${t.item.agent}" 未注册。使用 \`roll agent list\` 查看已注册 Agent。`;w.info(`连接 Agent "${n.skill.name}"...`);const e=r(t.config,n.skill.name),s=a(n),l=await t.clientManager.connect(n.skill.name,s,n.installPath,{...u(t.samplingCall),...e?{env:e}:{}}),c=h((await l.listTools()).tools),f={agent:n,client:l,tools:c,envReport:i(n.skill.name,n.skill.env,t.config.agents.env)};return t.agentConnections.set(t.item.agent,f),f}async function F(t){const o=q(t.item,t.index);try{const n=await B(t);if("string"==typeof n)return{...o,ok:!1,error:n};const r=n.tools.find(o=>o.name===t.item.tool);if(!r)return{...o,ok:!1,error:g(n.agent.skill.name,t.item.tool,n.tools)};const i=j(r.name)?[]:e(n.envReport),s=p(r,t.item.input,{runtimeIssues:i});if(!s.ok)return{...o,ok:!1,error:f(n.agent.skill.name,t.item.tool,s.issues,s.runtimeIssues)};w.info(`调用 ${n.agent.skill.name}.${t.item.tool}`),w.debug(`调用参数: ${JSON.stringify(y(t.item.input))}`);const l=await n.client.callTool({name:t.item.tool,arguments:t.item.input});return b(l)?{...o,ok:!1,error:"tool 返回 isError=true",result:l}:{...o,ok:!0,result:l}}catch(t){const n=t instanceof Error?t.message:String(t),r=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";return{...o,ok:!1,error:`${n}${r}`}}}function q(t,o){return{index:o,agent:t.agent,tool:t.tool,...t.label?{label:t.label}:{}}}async function D(t){const o=[];for(const[n,r]of t.items.entries()){const e=await F({...t,item:r,index:n});if(o.push(e),!e.ok&&t.bail)break}return o}function K(t){if("object"==typeof t&&null!==t&&"content"in t)for(const o of m(t.content))console.log(o);else"string"!=typeof t?console.log(JSON.stringify(t,null,2)):console.log(t)}export function formatRunToolResultForJsonOutput(t){return t.ok||"result"in t?{...t,result:d(t.result)}:t}function L(t){for(const o of t){const t=o.label?`[${String(o.index+1)}] ${o.label} (${o.agent}.${o.tool})`:`[${String(o.index+1)}] ${o.agent}.${o.tool}`;console.log(t),o.ok?K(o.result):console.log(o.error)}}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveAgentCatalog } from "../../registry/catalog-discovery.ts";
|
|
2
|
+
import { installAgent } from "../../registry/install.ts";
|
|
3
|
+
import { inspectCatalogAvailability } from "../utils/catalog-status.ts";
|
|
4
|
+
import { setupAgentEnv, setupInstall, setupLlm, setupShell } from "./config-setup.ts";
|
|
5
|
+
import type { ConfigPromptAdapter } from "./config-prompts.ts";
|
|
6
|
+
import type { RegisteredAgent } from "../../types/agent.ts";
|
|
7
|
+
import type { RollConfig } from "../../config/schema.ts";
|
|
8
|
+
export interface LlmConfigStatus {
|
|
9
|
+
readonly configured: boolean;
|
|
10
|
+
readonly summary: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RunChatOnboardingOptions {
|
|
13
|
+
readonly provider?: string;
|
|
14
|
+
readonly model?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SetupAgentContext {
|
|
17
|
+
readonly agentsConfig: RollConfig["agents"];
|
|
18
|
+
readonly installConfig?: RollConfig["install"];
|
|
19
|
+
readonly installConfigError?: string;
|
|
20
|
+
readonly agents: readonly RegisteredAgent[];
|
|
21
|
+
}
|
|
22
|
+
export interface RunSetupDeps {
|
|
23
|
+
readonly prompts?: ConfigPromptAdapter;
|
|
24
|
+
readonly detectLlm?: () => LlmConfigStatus;
|
|
25
|
+
readonly setupLlmFn?: typeof setupLlm;
|
|
26
|
+
readonly setupInstallFn?: typeof setupInstall;
|
|
27
|
+
readonly setupShellFn?: typeof setupShell;
|
|
28
|
+
readonly setupAgentEnvFn?: typeof setupAgentEnv;
|
|
29
|
+
readonly loadAgentContext?: () => SetupAgentContext;
|
|
30
|
+
readonly resolveCatalog?: typeof resolveAgentCatalog;
|
|
31
|
+
readonly inspectAvailability?: typeof inspectCatalogAvailability;
|
|
32
|
+
readonly install?: typeof installAgent;
|
|
33
|
+
readonly runDoctor?: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function runSetup(deps?: RunSetupDeps): Promise<void>;
|
|
36
|
+
export declare function runChatOnboarding(deps?: RunSetupDeps, options?: RunChatOnboardingOptions): Promise<boolean>;
|
|
37
|
+
declare const _default: import("citty").CommandDef<{}>;
|
|
38
|
+
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,n,r,i,o){return n?e?".jsx":".js":!r||i&&o?r+i+"."+o.toLowerCase()+"js":t}):t};import{defineCommand as e}from"citty";import{getAgentEnv as n,inspectLlmConfigReadiness as r}from"../../config/helpers.js";import{loadAgentsConfig as i,loadConfig as o,loadInstallConfig as a}from"../../config/loader.js";import{catalogPackageSpec as s}from"../../registry/catalog.js";import{resolveAgentCatalog as l}from"../../registry/catalog-discovery.js";import{installAgent as c}from"../../registry/install.js";import{AgentStore as g}from"../../registry/store.js";import{inspectCatalogAvailability as m}from"../utils/catalog-status.js";import{log as u}from"../utils/output.js";import{setupAgentEnv as f,setupInstall as p,setupLlm as d,setupShell as y}from"./config-setup.js";import{ConfigSetupCancelledError as w,clackPromptAdapter as h}from"./config-prompts.js";const v=import.meta.url.endsWith(".ts")?"ts":"js";export async function runSetup(t={}){const e=t.prompts??h,n=t.detectLlm??$,r=t.setupLlmFn??d,i=t.setupInstallFn??p,o=t.setupShellFn??y,a=t.setupAgentEnvFn??f,s=t.runDoctor??x;try{e.intro("Roll 一键初始化");const l=n();if(l.configured){await e.confirm({message:`已检测到 LLM 配置(${l.summary}),是否重新配置?`,initialValue:!1})&&e.info(await r(e))}else e.info(await r(e));await e.confirm({message:"是否配置安装网络(npm registry / 重试 / 超时)?",initialValue:!1})&&e.info(await i(e));await e.confirm({message:"是否配置 chat 内建 shell 工具(默认关闭)?",initialValue:!1})&&e.info(await o(e));const c=await C(e,t);for(const t of c){if(!t.envReport||0===t.envReport.missingRequired.length)continue;await e.confirm({message:`Agent "${t.agent.skill.name}" 缺少必填环境变量,现在配置?`,initialValue:!0})&&e.info(await a(t.agent.skill.name,e)),t.started||"core-managed"!==t.agent.runtime.ownership||e.info(`运行 roll agent start ${t.agent.skill.name} 启动该 Agent。`)}e.info("运行 roll doctor 检查..."),await s(),e.outro("初始化完成。下一步:`roll chat` 开始对话,或 `roll agent list` 查看已注册 Agent。")}catch(t){if(t instanceof w)return void(process.exitCode=1);throw t}}export async function runChatOnboarding(t={},e={}){const n=t.prompts??h,r=t.detectLlm??(()=>$(e)),i=t.setupLlmFn??d;try{n.intro("Roll chat 初始化");if(!await n.confirm({message:"LLM 尚未配置完成(缺少 provider 或 apiKey),现在进入初始化向导吗?",initialValue:!0}))return n.outro("已跳过初始化。"),!1;r().configured||n.info(await i(n));const e=await C(n,t);for(const t of e)t.envReport&&0!==t.envReport.missingRequired.length&&n.warn(`Agent "${t.agent.skill.name}" 缺少必填环境变量,可运行 roll config setup agent ${t.agent.skill.name} 配置。`);return n.outro("初始化完成,继续进入对话。完整初始化可运行 roll setup,环境检查可运行 roll doctor。"),!0}catch(t){if(t instanceof w)return!1;throw t}}async function C(t,e){const r=e.loadAgentContext??A,i=e.resolveCatalog??l,a=e.inspectAvailability??m,g=e.install??c,u=r();if(!u.installConfig)return t.warn(`install 配置无效,跳过官方 Agent 安装:${u.installConfigError??"未知原因"}`),[];const f=u.installConfig,p=await i(void 0,{...f.registry?{registry:f.registry}:{}}),d=await a(p,u.agents,{...f.registry?{registry:f.registry}:{}});if(0===d.length)return[];const y=await t.multiselect({message:"选择要安装的官方 Agent(空格勾选,回车确认,可全部跳过)",options:d.map(t=>({value:t.entry.shortName,label:t.entry.shortName,hint:j(t)})),initialValues:d.filter(t=>"not-installed"===t.state).map(t=>t.entry.shortName),required:!1}),w=[];for(const e of y){const r=d.find(t=>t.entry.shortName===e);if(!r)continue;const i=await g({packageSpec:s(r.entry),expectedSkillName:r.entry.skillName,replaceExisting:"installed-other-source"===r.state},{agentsConfig:u.agentsConfig,installConfig:f,getStartEnv:t=>n(o().config,t),report:e=>L(t,e)});i.ok?(t.info(`Agent "${i.agent.skill.name}" 安装并注册成功`),w.push(i)):k(t,i)}return w}function $(t={}){try{const{config:e}=o(),n=r(e,t);return{configured:n.configured,summary:n.summary}}catch{return{configured:!1,summary:""}}}function A(){const{agentsConfig:t}=i(),e=new g(t.dataDir);try{return{agentsConfig:t,installConfig:a().installConfig,agents:e.list()}}catch(n){return{agentsConfig:t,installConfigError:n instanceof Error?n.message:String(n),agents:e.list()}}}function j(t){const e="installed-package"===t.installedAgent?.source?.type?t.installedAgent.source.installedVersion:void 0,n={"not-installed":()=>t.latestVersion?`最新 v${t.latestVersion}`:"未安装",installed:()=>"update-available"===t.update?.status&&t.latestVersion?`已安装${e?` v${e}`:""},可更新到 v${t.latestVersion}`:"已安装"+(e?` v${e}`:""),"installed-other-source":()=>"已通过其他来源注册,选择将替换为 npm 安装"};return`${t.entry.description}(${n[t.state]()})`}function L(t,e){"retry"!==e.type?"warn"!==e.type?t.info(e.message):t.warn(e.message):t.warn(`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`)}function k(t,e){if("setup"===e.step)return t.warn(`Agent setup 失败:${e.message}`),void(e.retryCommand&&t.info(`重试命令: ${e.retryCommand}`));t.warn(e.message)}async function x(){const e=new URL(`./doctor.${v}`,import.meta.url).href,n=(await import(t(e))).default,{runCommand:r}=await import("citty");await r(n,{rawArgs:[]})}export default e({meta:{description:"一键初始化:配置 LLM、安装官方 Agent、检查环境(新设备 onboarding)"},args:{},async run(){if(!process.stdin.isTTY||!process.stderr.isTTY)return u.error("roll setup 需要交互式终端。非交互环境请使用 roll config set 与 roll agent install。"),void(process.exitCode=1);await runSetup()}});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare const _default: import("citty").CommandDef<{
|
|
2
|
+
source: {
|
|
3
|
+
type: "positional";
|
|
4
|
+
description: string;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
target: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
project: {
|
|
12
|
+
type: "boolean";
|
|
13
|
+
description: string;
|
|
14
|
+
default: false;
|
|
15
|
+
};
|
|
16
|
+
dir: {
|
|
17
|
+
type: "string";
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
skill: {
|
|
21
|
+
type: "string";
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
json: {
|
|
25
|
+
type: "boolean";
|
|
26
|
+
description: string;
|
|
27
|
+
default: false;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineCommand as t}from"citty";import{resolve as r}from"node:path";import{expandTilde as i,loadAgentsConfig as e}from"../../config/loader.js";import{collectInstallableSkills as o,findExistingSkillInstalls as s,installSkillsToDir as l}from"../../skills/install.js";import{ORCHESTRATOR_SKILL_TARGETS as n,detectOrchestratorTargets as a,isOrchestratorTargetId as c}from"../../skills/orchestrators.js";import{cloneOrPullRepo as p,isGitUrl as d,repoNameFromUrl as f}from"../utils/git-source.js";import{log as u}from"../utils/output.js";import{ConfigSetupCancelledError as g,clackPromptAdapter as m}from"./config-prompts.js";export default t({meta:{description:"安装 skill 到 orchestrator 的 skills 目录(Claude Code / Codex / 通用 .agents)"},args:{source:{type:"positional",description:"skill 来源:包含 SKILL.md 的本地目录(或多 skill 子目录集合)、Git 仓库 URL",required:!0},target:{type:"string",description:`目标 orchestrator,逗号分隔或 all(${n.map(t=>`${t.id}=${t.userDir}`).join("、")};agents 目录同时被 roll chat 读取)`},project:{type:"boolean",description:"安装到当前项目目录(如 .claude/skills)而非用户级目录",default:!1},dir:{type:"string",description:"自定义目标目录(与 --target 互斥)"},skill:{type:"string",description:"只安装指定名称的 skill,逗号分隔(默认安装来源中的全部 skill)"},json:{type:"boolean",description:"JSON 格式输出安装结果",default:!1}},async run({args:t}){if(t.dir&&(t.target||t.project))return u.error("--dir 与 --target / --project 互斥,请只指定一种目标方式"),void(process.exitCode=1);let n;if(d(t.source)){const{agentsConfig:i}=e(),o=r(i.dataDir,"skill-repos",f(t.source));try{const{action:r}=await p(t.source,o);u.info("cloned"===r?`已克隆到 ${o}`:`已更新 ${o}`)}catch(t){return u.error(`拉取 skill 仓库失败: ${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}n=o}else n=r(i(t.source));const a=o(n);for(const t of a.issues)u.warn(t);const c=k(a.skills,t.skill);if(!c)return void(process.exitCode=1);if(0===c.length)return u.error(`未在 ${n} 找到可安装的 skill`),void(process.exitCode=1);let m;try{const r=await h(t);if(!r)return void(process.exitCode=1);m=r}catch(t){if(t instanceof g)return void(process.exitCode=1);throw t}const j=[];for(const{id:t,dir:r}of m){const i=s(c,r);i.length>0&&u.warn(`将覆盖 ${r} 中已有的 skill(手工修改会丢失): ${i.join(", ")}`);const e=l(c,r);j.push({target:t,dir:r,skills:e});for(const t of e)u.success(`${t.overwritten?"已更新":"已安装"} ${t.name} → ${t.targetPath}`)}t.json&&console.log(JSON.stringify(j,null,2))}});function k(t,r){if(!r)return t;const i=r.split(",").map(t=>t.trim()).filter(t=>t.length>0),e=new Map(t.map(t=>[t.name,t])),o=i.filter(t=>!e.has(t));if(!(o.length>0))return i.map(t=>e.get(t)).filter(t=>void 0!==t);u.error(`来源中不存在这些 skill: ${o.join(", ")}(可用: ${t.map(t=>t.name).join(", ")})`)}async function h(t){if(t.dir)return[{id:null,dir:r(i(t.dir))}];const e=a({project:t.project}),o=new Map(e.map(t=>[t.target.id,t]));if(t.target){const r="all"===t.target?n.map(t=>t.id):t.target.split(",").map(t=>t.trim()).filter(t=>t.length>0),i=r.filter(t=>!c(t));return i.length>0?void u.error(`未知的 --target: ${i.join(", ")}(可选: ${n.map(t=>t.id).join("、")}、all)`):r.filter(c).flatMap(t=>{const r=o.get(t);return r?[{id:t,dir:r.skillsDir}]:[]})}if(!process.stdin.isTTY||!process.stderr.isTTY)return void u.error("非交互模式需要指定 --target 或 --dir");const s=e.flatMap(t=>c(t.target.id)?[{item:t,id:t.target.id}]:[]);return(await m.multiselect({message:"选择要安装到的 orchestrator(空格勾选,回车确认)",options:s.map(({item:t,id:r})=>({value:r,label:`${t.target.label}(${t.skillsDir})`,...t.present?{}:{hint:"未检测到使用痕迹"}})),initialValues:s.filter(({item:t})=>t.present).map(({id:t})=>t),required:!0})).flatMap(t=>{const r=o.get(t);return r?[{id:t,dir:r.skillsDir}]:[]})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var t=this&&this.__rewriteRelativeImportExtension||function(t,
|
|
1
|
+
var t=this&&this.__rewriteRelativeImportExtension||function(t,s){return"string"==typeof t&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(t,e,i,r,l){return e?s?".jsx":".js":!i||r&&l?i+r+"."+l.toLowerCase()+"js":t}):t};import{defineCommand as s}from"citty";const e=import.meta.url.endsWith(".ts")?"ts":"js";function i(s){const i=new URL(`./${s}.${e}`,import.meta.url).href;return import(t(i)).then(t=>t.default)}export default s({meta:{description:"读取已注册 Agent 的 SKILL.md(list/get/path),或安装 skill 到 orchestrator 目录(install)"},subCommands:{list:()=>i("skills-list"),get:()=>i("skills-get"),path:()=>i("skills-path"),install:()=>i("skills-install")}});
|
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
|
|
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 p=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"),setup:()=>u("setup"),skills:()=>u("skills"),browser:()=>u("browser"),doctor:()=>u("doctor"),update:()=>u("update")}}),m=s({allowNetwork:!1}).catch(()=>{});i(a(process.argv.slice(2))),r(p).then(()=>{m.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,20 @@
|
|
|
1
|
+
import { checkPublishedPackageUpdate, fetchLatestPublishedVersion } from "./update-checker.ts";
|
|
2
|
+
import type { PublishedPackageUpdateInfo } from "./update-checker.ts";
|
|
3
|
+
import type { AgentCatalogEntry } from "../../registry/catalog.ts";
|
|
4
|
+
import type { RegisteredAgent } from "../../types/agent.ts";
|
|
5
|
+
export declare const CATALOG_INSTALL_STATES: readonly ["not-installed", "installed", "installed-other-source"];
|
|
6
|
+
export type CatalogInstallState = (typeof CATALOG_INSTALL_STATES)[number];
|
|
7
|
+
export interface CatalogAvailabilityItem {
|
|
8
|
+
readonly entry: AgentCatalogEntry;
|
|
9
|
+
readonly state: CatalogInstallState;
|
|
10
|
+
readonly installedAgent?: RegisteredAgent;
|
|
11
|
+
readonly latestVersion?: string;
|
|
12
|
+
readonly update?: PublishedPackageUpdateInfo;
|
|
13
|
+
}
|
|
14
|
+
export interface InspectCatalogAvailabilityOptions {
|
|
15
|
+
readonly allowNetwork?: boolean;
|
|
16
|
+
readonly registry?: string;
|
|
17
|
+
readonly fetchLatest?: typeof fetchLatestPublishedVersion;
|
|
18
|
+
readonly checkUpdate?: typeof checkPublishedPackageUpdate;
|
|
19
|
+
}
|
|
20
|
+
export declare function inspectCatalogAvailability(catalog: readonly AgentCatalogEntry[], agents: readonly RegisteredAgent[], options?: InspectCatalogAvailabilityOptions): Promise<readonly CatalogAvailabilityItem[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{checkPublishedPackageUpdate as e,fetchLatestPublishedVersion as t}from"./update-checker.js";export const CATALOG_INSTALL_STATES=["not-installed","installed","installed-other-source"];function a(e,t){for(const a of t)if("installed-package"===a.source?.type&&a.source.packageName===e.packageName)return{...a,source:a.source}}export async function inspectCatalogAvailability(s,n,o={}){const r=o.fetchLatest??t,c=o.checkUpdate??e,l={allowNetwork:o.allowNetwork??!0,...o.registry?{registry:o.registry}:{}},i=[];for(const e of s){const t=a(e,n);if(t){const a=await c({packageName:t.source.packageName,packageSpec:t.source.packageSpec,...t.source.installedVersion?{currentVersion:t.source.installedVersion}:{}},l);i.push({entry:e,state:"installed",installedAgent:t,...a.latestVersion?{latestVersion:a.latestVersion}:{},update:a});continue}const s=await r(e.packageName,l),o=n.find(t=>t.skill.name===e.skillName);o?i.push({entry:e,state:"installed-other-source",installedAgent:o,...s?{latestVersion:s}:{}}):i.push({entry:e,state:"not-installed",...s?{latestVersion:s}:{}})}return i}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import t from"chalk";import{isCancel as s,select as e}from"@clack/prompts";import{createSpinner as n,log as o}from"./output.js";import{GLYPHS as a}from"./glyphs.js";import{computeUsageParts as i,formatUsageLine as r}from"./token-format.js";import{formatToolInput as p,formatApprovalDetails as l}from"./tool-format.js";export const clackConfirm=async t=>{const n=await e({message:t,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!s(n)&&"yes"===n};function c(t){const s=[`chat.${t.stage}`,t.message];return void 0!==t.elapsedMs&&s.push(`${String(t.elapsedMs)}ms`),void 0!==t.data&&s.push(JSON.stringify(t.data)),s.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;toolLabels=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(t,s){this.confirm=t,this.contextWindow=s}async handle(s,e){switch(s.type){case"debug":o.debug(c(s));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(s.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const e=`${s.agentName}.${s.toolName}`,o=n(`${t.cyan(e)} ${t.gray(p(s.input))}`);o.start(),this.spinners.set(s.toolCallId,o),this.toolLabels.set(s.toolCallId,e);break}case"tool-output-delta":{const e=this.spinners.get(s.toolCallId),n=this.toolLabels.get(s.toolCallId);if(e&&n){const o=s.delta.split("\n").map(t=>t.trim()).filter(t=>t.length>0).at(-1);if(o){const s=o.length>80?`${o.slice(0,79)}…`:o;e.text=`${t.cyan(n)} ${t.gray(s)}`}}break}case"tool-result":{const t=this.spinners.get(s.toolCallId);t&&(s.isError?t.fail():t.succeed(),this.spinners.delete(s.toolCallId)),this.toolLabels.delete(s.toolCallId);break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const t=s.reason?`(${s.reason})`:"",n=l(s.input),o=`执行 ${s.agentName}.${s.toolName}${t}?`;await this.confirm(n?`${o}\n${n}`:o)?e.approve(s.approvalId):e.reject(s.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=n(t.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const e="auto"===s.reason?"自动压缩":"手动压缩",n=s.truncatedTools?`,精简 ${String(s.truncatedTools)} 个工具结果`:"",o=0!==s.removed||s.truncatedTools?`${a.compact} ${e}(${s.strategy}):移除 ${String(s.removed)} 条 → 保留 ${String(s.kept)} 条${n}`:`${a.compact} ${e}:无需压缩`;process.stderr.write(`${t.gray(o)}\n`);break}case"turn-cancelled":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine();for(const s of this.spinners.values())s.warn(`${s.text} ${t.yellow("已中断")}`);this.spinners.clear(),this.toolLabels.clear(),o.warn(s.message);break;case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),o.error(s.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===s.text.length&&(s.totalUsage?.outputTokens??0)>0&&o.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),s.stoppedAtStepLimit&&o.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const e=r(i(s.totalUsage,s.sessionUsage,this.contextWindow,s.contextInputTokens));e&&process.stderr.write(`${t.gray(e)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=n(t.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function isGitUrl(input: string): boolean;
|
|
2
|
+
export declare function repoNameFromUrl(url: string): string;
|
|
3
|
+
export declare const CLONE_OR_PULL_ACTIONS: readonly ["cloned", "pulled"];
|
|
4
|
+
export type CloneOrPullAction = (typeof CLONE_OR_PULL_ACTIONS)[number];
|
|
5
|
+
export declare function cloneOrPullRepo(url: string, cloneTarget: string): Promise<{
|
|
6
|
+
readonly action: CloneOrPullAction;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{execFile as t}from"node:child_process";import{existsSync as o,mkdirSync as r}from"node:fs";import{dirname as e}from"node:path";import{promisify as i}from"node:util";const n=i(t);export function isGitUrl(t){return t.startsWith("https://")||t.startsWith("http://")||t.startsWith("git@")||t.endsWith(".git")}export function repoNameFromUrl(t){return(t.split("/").pop()??t).replace(/\.git$/,"")}export const CLONE_OR_PULL_ACTIONS=["cloned","pulled"];export async function cloneOrPullRepo(t,i){if(o(i))return await n("git",["pull"],{cwd:i}),{action:"pulled"};const p=e(i);return o(p)||r(p,{recursive:!0}),await n("git",["clone",t,i]),{action:"cloned"}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{redactToolArgsForLog as t}from"./output.js";export function formatToolInput(
|
|
1
|
+
import{redactToolArgsForLog as t}from"./output.js";export function formatToolInput(n){const r=JSON.stringify(t(n))??"";return r.length>80?`${r.slice(0,79)}…`:r}function n(t){return 9===t||10===t||!(t<=8||t>=11&&t<=31)&&(t<127||t>159)}function r(t){let r="";for(const o of t)n(o.codePointAt(0)??0)&&(r+=o);return r}export function formatApprovalDetails(n){const o=t(n);if("object"!=typeof o||null===o||Array.isArray(o))return r(JSON.stringify(o)??"");return Object.entries(o).map(([t,n])=>`${t}: ${r("string"==typeof n?n:JSON.stringify(n))}`).join("\n")}
|
package/dist/config/defaults.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LLM_PROVIDER_OPTIONS=["anthropic","openai","qwen","deepseek"];export const DEFAULT_LLM_PROVIDER="anthropic";export const DEFAULT_LLM_MODELS={anthropic:"claude-sonnet-4-6",openai:"gpt-5.5",qwen:"qwen3.6-plus",deepseek:"deepseek-v4-flash"};export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:DEFAULT_LLM_MODELS.anthropic,providers:{}},ask:{},runtime:{maxSteps:80,turnTimeoutMs:3e5,threadsDir:"~/.roll-agent/threads",thinkingLevel:"medium",approval:{default:"guarded",overrides:{}},compaction:{enabled:!0,strategy:"summarize",threshold:.75,keepRecentTurns:4,keepRecentTokens:32e3}},skills:{dirs:[]},agents:{dataDir:"~/.roll-agent/agents"},install:{fetchRetries:3,preferOffline:!1,networkTimeoutMs:12e4},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
|
|
1
|
+
export const LLM_PROVIDER_OPTIONS=["anthropic","openai","qwen","deepseek"];export const DEFAULT_LLM_PROVIDER="anthropic";export const DEFAULT_LLM_MODELS={anthropic:"claude-sonnet-4-6",openai:"gpt-5.5",qwen:"qwen3.6-plus",deepseek:"deepseek-v4-flash"};export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:DEFAULT_LLM_MODELS.anthropic,providers:{}},ask:{},runtime:{maxSteps:80,turnTimeoutMs:3e5,threadsDir:"~/.roll-agent/threads",thinkingLevel:"medium",approval:{default:"guarded",overrides:{}},compaction:{enabled:!0,strategy:"summarize",threshold:.75,keepRecentTurns:4,keepRecentTokens:32e3},shell:{enabled:!1,autoApproveSafe:!0,defaultTimeoutMs:1e4,maxTimeoutMs:6e5,maxCaptureBytes:1048576,maxModelOutputChars:16e3,session:{enabled:!1,maxSessions:8,defaultYieldMs:1e4,maxOutputTokens:1e4}}},skills:{dirs:[]},agents:{dataDir:"~/.roll-agent/agents"},install:{fetchRetries:3,preferOffline:!1,networkTimeoutMs:12e4},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
|
package/dist/config/helpers.d.ts
CHANGED
|
@@ -2,6 +2,18 @@ import type { AgentSkillEnvDeclarations } from "../types/agent.ts";
|
|
|
2
2
|
import type { AskRuntimeIssue } from "../types/ask.ts";
|
|
3
3
|
import type { RollConfig } from "./schema.ts";
|
|
4
4
|
type AgentEnvMap = RollConfig["agents"]["env"];
|
|
5
|
+
type LlmProviderConfig = RollConfig["llm"]["providers"][string];
|
|
6
|
+
export declare const LLM_CONFIG_STATUS_CODES: readonly ["ready", "missing-provider", "missing-api-key", "unresolved-api-key"];
|
|
7
|
+
export type LlmConfigStatusCode = (typeof LLM_CONFIG_STATUS_CODES)[number];
|
|
8
|
+
export interface LlmConfigReadiness {
|
|
9
|
+
readonly configured: boolean;
|
|
10
|
+
readonly status: LlmConfigStatusCode;
|
|
11
|
+
readonly provider: string;
|
|
12
|
+
readonly model: string;
|
|
13
|
+
readonly summary: string;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly providerConfig?: LlmProviderConfig;
|
|
16
|
+
}
|
|
5
17
|
export interface AgentEnvCheckItem {
|
|
6
18
|
readonly name: string;
|
|
7
19
|
readonly required: boolean;
|
|
@@ -15,6 +27,11 @@ export interface AgentEnvCheckReport {
|
|
|
15
27
|
readonly missingRequired: readonly AgentEnvCheckItem[];
|
|
16
28
|
readonly processEnvOnlyRequired: readonly AgentEnvCheckItem[];
|
|
17
29
|
}
|
|
30
|
+
export declare function hasUnresolvedEnvPlaceholder(value: string): boolean;
|
|
31
|
+
export declare function inspectLlmConfigReadiness(config: RollConfig, options?: {
|
|
32
|
+
readonly provider?: string;
|
|
33
|
+
readonly model?: string;
|
|
34
|
+
}): LlmConfigReadiness;
|
|
18
35
|
export declare function getAgentEnvFromAgentsConfig(agentsConfig: RollConfig["agents"], agentName: string): Readonly<Record<string, string>> | undefined;
|
|
19
36
|
/** 获取指定 Agent 的环境变量配置,没有则返回 undefined */
|
|
20
37
|
export declare function getAgentEnv(config: RollConfig, agentName: string): Readonly<Record<string, string>> | undefined;
|
package/dist/config/helpers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=/\$\{[^}]+\}/;function
|
|
1
|
+
const e=/\$\{[^}]+\}/;export const LLM_CONFIG_STATUS_CODES=["ready","missing-provider","missing-api-key","unresolved-api-key"];export function hasUnresolvedEnvPlaceholder(r){return e.test(r)}export function inspectLlmConfigReadiness(e,r={}){const n=r.provider??e.llm.defaultProvider,s=r.model??e.llm.defaultModel,t=e.llm.providers[n],i=`${n}/${s}`;if(!t)return{configured:!1,status:"missing-provider",provider:n,model:s,summary:i,message:`LLM provider "${n}" 未配置。请运行 roll setup 或 roll config setup llm`};const o=t.apiKey.trim();return 0===o.length?{configured:!1,status:"missing-api-key",provider:n,model:s,summary:i,message:`LLM provider "${n}" 的 apiKey 未配置。请运行 roll setup 或 roll config setup llm`,providerConfig:t}:hasUnresolvedEnvPlaceholder(o)?{configured:!1,status:"unresolved-api-key",provider:n,model:s,summary:i,message:`LLM provider "${n}" 的 apiKey 仍是未解析的环境变量占位符。请设置对应环境变量,或运行 roll setup / roll config setup llm`,providerConfig:t}:{configured:!0,status:"ready",provider:n,model:s,summary:i,message:`LLM provider "${n}" 已配置`,providerConfig:t}}function r(e,r){return e?.[r]}export function getAgentEnvFromAgentsConfig(e,n){return s(r(e.env,n))}export function getAgentEnv(e,r){const n=getAgentEnvFromAgentsConfig(e.agents,r);return"browser-use-agent"!==r||0===Object.keys(e.browser.instances).length?n:{...n??{},BROWSER_INSTANCES_JSON:JSON.stringify(e.browser)}}export function inspectAgentEnvRequirements(e,t,i,o=process.env){if(!t)return;const u=s(r(i,e)),l=[...n(t.required,!0,u,o),...n(t.optional,!1,u,o)];return 0!==l.length?{items:l,missingRequired:l.filter(e=>e.required&&"missing"===e.source),processEnvOnlyRequired:l.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 n(e,r,n,s){return e?e.map(e=>{const t=n?.[e.name];if(void 0!==t&&t.length>0)return{...e,required:r,source:"agents.env"};const i=s[e.name];return"string"==typeof i&&i.length>0?{...e,required:r,source:"process.env"}:e.default?{...e,required:r,source:"default"}:{...e,required:r,source:"missing"}}):[]}function s(e){if(!e)return;const r=Object.entries(e).filter(([,e])=>t(e));return 0!==r.length?Object.fromEntries(r):void 0}function t(r){return"string"==typeof r&&r.length>0&&!e.test(r)}
|
package/dist/config/key-codec.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={kind:"leaf"},
|
|
1
|
+
const e={kind:"leaf"},o={kind:"object",fields:{apiKey:e,baseUrl:e}},t={kind:"object",fields:{platform:e,mode:e,headless:e,cdpUrl:e,cdpHost:e,cdpPort:e,channel:e,executablePath:e,userDataDir:e,sessionsDir:e,args:e,profileName:e,profileColor:e,windowBounds:{kind:"object",fields:{x:e,y:e,width:e,height:e}},trackingAgentId:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:o}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},runtime:{kind:"object",fields:{provider:e,model:e,maxSteps:e,turnTimeoutMs:e,threadsDir:e,contextWindow:e,thinkingLevel:e,approval:{kind:"object",fields:{default:e,overrides:{kind:"record",value:e}}},compaction:{kind:"object",fields:{enabled:e,strategy:e,threshold:e,keepRecentTurns:e,keepRecentTokens:e}},shell:{kind:"object",fields:{enabled:e,autoApproveSafe:e,defaultTimeoutMs:e,maxTimeoutMs:e,maxCaptureBytes:e,maxModelOutputChars:e,session:{kind:"object",fields:{enabled:e,maxSessions:e,defaultYieldMs:e,maxOutputTokens:e}}}}}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}},install:{kind:"object",fields:{registry:e,fetchRetries:e,preferOffline:e,networkTimeoutMs:e}},browser:{kind:"object",fields:{defaultInstance:e,instances:{kind:"record",value:t}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,o)=>o.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function r(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(o,t=CONFIG_KEY_CODEC){if(Array.isArray(o))return o.map(e=>decodeFromYaml(e,t));if(!r(o))return o;if("object"===t.kind){const r={};for(const[n,i]of Object.entries(o)){const o=kebabToCamel(n),d=t.fields[o]??e;r[o]=decodeFromYaml(i,d)}return r}if("record"===t.kind){const e={};for(const[r,n]of Object.entries(o))e[r]=decodeFromYaml(n,t.value);return e}return o}function n(o,t){const r=[];let n=CONFIG_KEY_CODEC;for(const i of o)if("object"===n.kind){const o=kebabToCamel(i),d=n.fields[o];d?(r.push("camel"===t?o:camelToKebab(o)),n=d):(r.push(i),n=e)}else"record"===n.kind?(r.push(i),n=n.value):r.push(i);return r}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { RollConfig } from "./schema.ts";
|
|
2
2
|
import { type ConfigMigrationReport } from "./migration.ts";
|
|
3
|
+
/** 展开 `~` 为用户 home 目录 */
|
|
4
|
+
export declare function expandTilde(filePath: string): string;
|
|
3
5
|
export interface LoadConfigOptions {
|
|
4
6
|
/** 指定配置文件路径(跳过自动查找) */
|
|
5
7
|
readonly configPath?: string;
|
package/dist/config/loader.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as n,existsSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as o}from"node:path";import{parse as e}from"yaml";import{agentsConfigSchema as i,installConfigSchema as s,rollConfigSchema as a}from"./schema.js";import{DEFAULT_CONFIG as f,CONFIG_FILE_NAMES as c}from"./defaults.js";import{decodeFromYaml as
|
|
1
|
+
import{readFileSync as n,existsSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as o}from"node:path";import{parse as e}from"yaml";import{agentsConfigSchema as i,installConfigSchema as s,rollConfigSchema as a}from"./schema.js";import{DEFAULT_CONFIG as f,CONFIG_FILE_NAMES as c}from"./defaults.js";import{decodeFromYaml as l}from"./key-codec.js";import{detectKnownConfigMigrations as u,formatConfigMigrationError as d}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(p(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function p(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function h(n){return p(n)&&"number"==typeof n.line&&"number"==typeof n.col}function m(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(h)}function w(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(m(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 v(n){let e=o(n);const i=o("/");for(;e!==i;){for(const n of c){const t=o(e,n);if(r(t))return t}const n=o(e,"..");if(n===e)break;e=n}for(const n of c){const e=o(t(),n);if(r(e))return e}}export function expandTilde(n){return"~"===n?t():n.startsWith("~/")||n.startsWith("~\\")?o(t(),n.slice(2)):n}function $(n){return{...n,agents:{...n.agents,dataDir:expandTilde(n.agents.dataDir)},runtime:{...n.runtime,threadsDir:expandTilde(n.runtime.threadsDir)},skills:{...n.skills,dirs:n.skills.dirs.map(expandTilde)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:expandTilde(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:expandTilde(r.sessionsDir)}:{}}]))}}}const C={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=e(n)}catch(n){throw new Error(w(r,n),{cause:n instanceof Error?n:void 0})}if(!p(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function P(n,r,t={}){const o=parseConfigDocument(n,r),e=u(o,t);if(e.needsMigration)throw new Error(d(r,e));return o}export function validateConfigText(n,r){const t=P(n,r),o=g(l(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=j(f,o),i=a.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return $(i.data)}function x(n,r){const t=P(n,r,{scope:"agents"}),o=g(l(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.agents)?o.agents:{},s=j(f.agents,e),a=i.safeParse(s);if(!a.success){const n=a.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...a.data,dataDir:expandTilde(a.data.dataDir)}}function y(n,r){const t=parseConfigDocument(n,r),o=g(l(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.install)?o.install:{},i=j(f.install,e),a=s.safeParse(i);if(!a.success){const n=a.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return a.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:f.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:y(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:$(f),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:{...f.agents,dataDir:expandTilde(f.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:x(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??v(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:C.notFound,configPath:void 0};if(!r(o))return{status:C.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=u(i);if(s.needsMigration)return{status:C.needsMigration,configPath:o,raw:e,report:s};try{return{status:C.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function j(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=j(r,e)}return t}
|