@roll-agent/core 0.10.0 → 0.11.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/bin/roll.js CHANGED
@@ -1,2 +1,40 @@
1
1
  #!/usr/bin/env node
2
- import "../dist/cli/index.js";
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ async function sqliteAvailable() {
8
+ try {
9
+ await import("node:sqlite");
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function sourceTreeAvailable() {
17
+ return existsSync(resolve(dirname(fileURLToPath(import.meta.url)), "../src/cli/index.ts"));
18
+ }
19
+
20
+ function hasExecFlag(flag) {
21
+ return process.execArgv.includes(flag);
22
+ }
23
+
24
+ const needsSqliteFlag = !(await sqliteAvailable());
25
+ const needsTypeStripFlag = sourceTreeAvailable() && !hasExecFlag("--experimental-strip-types");
26
+
27
+ if (!process.env.ROLL_SQLITE_RESPAWNED && (needsSqliteFlag || needsTypeStripFlag)) {
28
+ const nodeFlags = [
29
+ ...(needsSqliteFlag ? ["--experimental-sqlite"] : []),
30
+ ...(needsTypeStripFlag ? ["--experimental-strip-types"] : []),
31
+ ];
32
+ const result = spawnSync(
33
+ process.execPath,
34
+ [...nodeFlags, process.argv[1], ...process.argv.slice(2)],
35
+ { stdio: "inherit", env: { ...process.env, ROLL_SQLITE_RESPAWNED: "1" } },
36
+ );
37
+ process.exit(result.status ?? 0);
38
+ }
39
+
40
+ await import("../dist/cli/index.js");
@@ -1,3 +1,6 @@
1
+ import type { AgentSession } from "@roll-agent/runtime";
2
+ import type { ChatCommandResult } from "../../types/chat.ts";
3
+ export declare function runJsonTurn(session: AgentSession, message: string): Promise<ChatCommandResult>;
1
4
  declare const _default: import("citty").CommandDef<{
2
5
  message: {
3
6
  type: "positional";
@@ -8,10 +11,25 @@ declare const _default: import("citty").CommandDef<{
8
11
  type: "string";
9
12
  description: string;
10
13
  };
14
+ last: {
15
+ type: "boolean";
16
+ description: string;
17
+ default: false;
18
+ };
19
+ list: {
20
+ type: "boolean";
21
+ description: string;
22
+ default: false;
23
+ };
11
24
  json: {
12
25
  type: "boolean";
13
26
  description: string;
14
27
  default: false;
15
28
  };
29
+ server: {
30
+ type: "boolean";
31
+ description: string;
32
+ default: false;
33
+ };
16
34
  }>;
17
35
  export default _default;
@@ -1 +1 @@
1
- import{defineCommand as s}from"citty";import{log as e}from"../utils/output.js";const t="roll chat 仍处于 experimental 阶段,当前只提供命令骨架,不会执行会话编排、不会恢复 session,也不会隐式降级到 roll ask。";function o(){return{status:"unavailable",message:t}}function n(s){console.log(JSON.stringify(s,null,2))}export default s({meta:{description:"Experimental: 会话式入口骨架(当前不可用)"},args:{message:{type:"positional",description:"起始消息",required:!1},session:{type:"string",description:"继续已有会话的 session ID"},json:{type:"boolean",description:"JSON 格式输出",default:!1}},async run({args:s}){const t=o();s.json?n(t):e.warn(t.message),process.exitCode=1}});
1
+ import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{createProviderModel as t}from"../../llm/providers.js";import{createInterface as o}from"node:readline/promises";import r from"chalk";import i from"cli-table3";import{log as n}from"../utils/output.js";import{ChatRenderer as a,clackConfirm as l}from"../utils/chat-renderer.js";function c(e,s){return new e.ConfigurableToolPolicy({defaultMode:s.runtime.approval.default,overrides:s.runtime.approval.overrides})}function d(e){const s=e.trim().replace(/\s+/g," ");return s.length>40?`${s.slice(0,39)}…`:s}function u(e){console.log(JSON.stringify(e,null,2))}function m(e){n.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}async function p(){return import("@roll-agent/runtime")}async function f(e){const s=e.runtime.provider??e.llm.defaultProvider,o=e.runtime.model??e.llm.defaultModel,r=e.llm.providers[s];if(!r)return n.error(`LLM provider "${s}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const i=await p(),{ConversationEngine:a,ThreadStore:l,RuntimeServer:d,createStdioConnection:u}=i,f=t(s,o,r.apiKey,r.baseUrl),g=new l(e.runtime.threadsDir),y=new a({config:e,model:f,store:g,policy:c(i,e),maxSteps:e.runtime.maxSteps,onAgentBootstrapIssue:m}),h=u(process.stdin,process.stdout),w=new d(y,h);h.onClose(()=>{w.abortAll(),y.dispose().catch(()=>{}).finally(()=>{g.close(),process.exit(0)})}),n.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function g(e,s){const{ThreadStore:t}=await p(),o=new t(e.runtime.threadsDir);try{const e=o.listThreads();if(s){const s=e.map(e=>({...e,messageCount:o.countMessages(e.id)}));return void console.log(JSON.stringify(s,null,2))}if(0===e.length)return void console.log('暂无会话。用 `roll chat "<message>"` 开始一个。');const t=new i({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const s of e)t.push([s.id,s.title??"-",String(o.countMessages(s.id)),s.updatedAt]);console.log(t.toString())}finally{o.close()}}export async function runJsonTurn(e,s){const t=[],o=[],r=[];let i,n,a="";for await(const l of e.send(s))switch(l.type){case"text-delta":a+=l.delta;break;case"tool-call":t.push({summary:`${l.agentName}.${l.toolName}`,agentName:l.agentName,toolName:l.toolName});break;case"confirmation-required":r.push({summary:`${l.agentName}.${l.toolName}`,agentName:l.agentName,toolName:l.toolName}),e.reject(l.approvalId,"json 模式不支持交互确认");break;case"step-finish":o.push({finishReason:l.finishReason,...l.usage?{usage:l.usage}:{}});break;case"message-finish":n=l.totalUsage;break;case"error":i=l.message}return void 0!==i?{status:"failed",stage:"execute",message:i,sessionId:e.id}:r.length>0?{status:"needs_confirmation",sessionId:e.id,message:"存在需要确认的工具调用,请在交互模式下执行或显式批准",pendingActions:r}:{status:"completed",sessionId:e.id,output:a,steps:t,...o.length>0?{stepUsages:o}:{},...n?{totalUsage:n}:{}}}async function y(e,s,t){const i=o({input:process.stdin,output:process.stdout});i.on("SIGINT",()=>i.close());const l=new a(async e=>{const s=(await i.question(`${e} (y/N) `)).trim().toLowerCase();return"y"===s||"yes"===s});n.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出)");let c=!t,u=!1;try{for(;;){let t;try{t=(await i.question(r.green("› "))).trim()}catch{break}if(0!==t.length){if("exit"===t||"quit"===t)break;c||(s.updateTitle(e.id,d(t)),c=!0),u=!0;for await(const s of e.send(t))await l.handle(s,e)}}}finally{i.close(),t&&!u&&0===s.countMessages(e.id)&&s.deleteThread(e.id)}}export default e({meta:{description:"会话式 AI 助手(多轮对话 + 自动工具调用)"},args:{message:{type:"positional",description:"起始消息",required:!1},session:{type:"string",description:"继续已有会话的 session ID"},last:{type:"boolean",description:"继续最近一个会话",default:!1},list:{type:"boolean",description:"列出已有会话",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},server:{type:"boolean",description:"以 JSON-RPC daemon 模式运行(stdio,供 GUI/前端接入)",default:!1}},async run({args:e}){const{config:o}=s();if(e.server)return void await f(o);if(e.list)return void await g(o,e.json);const r=o.runtime.provider??o.llm.defaultProvider,i=o.runtime.model??o.llm.defaultModel,h=o.llm.providers[r];if(!h)return n.error(`LLM provider "${r}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);if(e.json&&!e.message)return n.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const w=await p(),{ConversationEngine:v,ThreadStore:S}=w,N=t(r,i,h.apiKey,h.baseUrl),x=new S(o.runtime.threadsDir),C=new v({config:o,model:N,store:x,policy:c(w,o),maxSteps:o.runtime.maxSteps,onAgentBootstrapIssue:m});try{let s;if(e.session)s=await C.resumeSession(e.session);else if(e.last){const e=x.listThreads()[0];if(!e)return n.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);s=await C.resumeSession(e.id)}else s=await C.createSession(e.message?{title:d(e.message)}:{});if(e.json&&e.message){const t=await runJsonTurn(s,e.message);return u(t),void("completed"!==t.status&&(process.exitCode=1))}if(n.info(`会话 ${s.id}`),e.message){const t=new a(l);for await(const o of s.send(e.message))await t.handle(o,s)}else await y(s,x,!e.session&&!e.last)}catch(e){n.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await C.dispose(),x.close()}}});
@@ -88,6 +88,18 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
88
88
  readonly purpose: "`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。";
89
89
  readonly defaultBehavior: "未配置时使用内置路由默认行为。";
90
90
  readonly example: "ask:\n confirm-threshold: 0.5";
91
+ }, {
92
+ readonly path: "runtime.approval.default";
93
+ readonly title: "Chat 工具确认默认策略";
94
+ readonly purpose: "`roll chat` 调用工具时的默认确认策略。`guarded` 使用内置读写启发式,`auto` 默认放行非破坏性工具,`deny` 默认拒绝。";
95
+ readonly defaultBehavior: "默认值为 `guarded`。此配置不影响 `roll ask`。" | "默认值为 `auto`。此配置不影响 `roll ask`。" | "默认值为 `deny`。此配置不影响 `roll ask`。";
96
+ readonly example: "runtime:\n approval:\n default: guarded" | "runtime:\n approval:\n default: auto" | "runtime:\n approval:\n default: deny";
97
+ }, {
98
+ readonly path: "runtime.approval.overrides";
99
+ readonly title: "Chat 工具确认精确覆盖";
100
+ readonly purpose: "`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。";
101
+ readonly defaultBehavior: "未配置时回退到 `runtime.approval.default`。";
102
+ readonly example: "runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto";
91
103
  }, {
92
104
  readonly path: "browser.default-instance";
93
105
  readonly title: "默认浏览器实例";
@@ -1 +1 @@
1
- import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as t}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=t(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>l(e.path,r))}function l(e,l){const r=t(e.split(".")).join(".");if(r===l)return!0;const a=r.split("."),n=l.split(".");return a.length===n.length&&a.every((e,t)=>e.startsWith("<")||e===n[t])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
1
+ import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as t}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"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:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=t(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>l(e.path,r))}function l(e,l){const r=t(e.split(".")).join(".");if(r===l)return!0;const a=r.split("."),n=l.split(".");return a.length===n.length&&a.every((e,t)=>e.startsWith("<")||e===n[t])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r}from"node:fs";import{execFile as a}from"node:child_process";import{promisify as s}from"node:util";import{inspectConfigFile as n,loadAgentsConfig as i,loadConfig as o,loadInstallConfig as l}from"../../config/loader.js";import{DEFAULT_CONFIG as c}from"../../config/defaults.js";import{getAgentEnv as u}from"../../config/helpers.js";import{getAgentPid as m,startAgent as p,stopAgentGracefully as f,waitForAgentReady as g}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as k}from"../../registry/dev-spawn.js";import{runAgentSetup as d}from"../../registry/runtime-setup.js";import{AgentStore as y}from"../../registry/store.js";import{discoverAgent as h}from"../../registry/discovery.js";import{inferAgentSourceType as $,readInstalledPackageManifest as w,resolveInstalledPackageRoot as v}from"../../registry/source.js";import{McpClientManager as S}from"../../mcp/client-manager.js";import{log as b,createSpinner as V}from"../utils/output.js";import{checkForUpdate as j,checkPublishedPackageUpdate as P,getCurrentVersion as A}from"../utils/update-checker.js";import{buildNpmRetryPolicy as M,detectInstallCommand as B,formatPackageManagerError as T,npmInstallNetworkArgs as D,runPackageManager as R,runPackageManagerWithRetry as E}from"../utils/package-manager.js";function N(e){return D({...e.registry?{registry:e.registry}:{},fetchRetries:e.fetchRetries,preferOffline:e.preferOffline})}function x(e){return{forceRefresh:!0,fetchRetries:e.fetchRetries,...e.registry?{registry:e.registry}:{}}}function C(e){return"invalid"===e.status&&(e.error.message.startsWith("Invalid YAML syntax")||e.error.message.includes("(expected YAML object)"))}const I=s(a);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function L(e,t){switch(e.status){case"needs-migration":{const r="post-update"===t?"升级后需要迁移本地配置":"检测到本地配置需要迁移";b.warn(`${r}: ${e.configPath}`);for(const t of e.report.issues)b.warn(` - ${t.message}`);e.report.canAutoMigrate&&b.info("建议命令: roll config migrate");break}case"invalid":b.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),b.warn(` - ${e.error.message}`),"post-update"===t&&b.info("请修复配置文件后再继续使用相关命令。")}}function O(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function U(e,t){switch(e.status){case"up-to-date":return e.currentVersion?`已是最新版本 (v${e.currentVersion})`:"已是最新版本";case"update-available":return e.currentVersion&&e.latestVersion?`可更新 v${e.currentVersion} → v${e.latestVersion}`:"检测到可用更新";case"pinned-behind":return e.currentVersion&&e.latestVersion?`固定版本 v${e.currentVersion};latest=v${e.latestVersion}`:"固定版本,需手动调整 package spec";case"unsupported-spec":return`不支持检查此 package spec: ${t}`;case"unknown":return e.currentVersion?`无法检查最新版本 (current=v${e.currentVersion})`:"无法检查最新版本"}}function Y(e,t){if("installed-package"!==e.source?.type)return e;const r=v(e.source.installDir,e.source.packageName),a=w(r);if(!a)return e;const s={...e.source,...a.name?{packageName:a.name}:{},...a.version?{installedVersion:a.version}:{}},n=s.packageName!==e.source.packageName||s.installedVersion!==e.source.installedVersion,i=r!==e.installPath;if(!n&&!i)return e;const o={...e,installPath:r,source:s};return t&&t.replace(e.skill.name,o),o}async function K(e,t,r){const a=A();if(a===e)return b.info(`roll 已是最新版本 (v${a})`),!1;if(b.info(`roll v${a} → v${e}`),t)return b.info("[dry-run] 跳过实际更新"),!0;const s=V("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`,...N(r)]};try{return await E(n,{timeout:r.networkTimeoutMs},{...M(r.fetchRetries),onRetry:({attempt:e,delayMs:t})=>{s.text=`roll 更新遇到网络问题,${Math.round(t/1e3)}s 后重试(第 ${e+1} 次)...`}}),s.succeed(`roll 已更新到 v${e}`),!0}catch(e){return s.fail("更新失败"),b.error(T(n,e)),!1}}async function W(e){const a=V(`更新 ${e.skill.name} (git pull)...`).start();try{await I("git",["pull"],{cwd:e.installPath,timeout:3e4}),a.succeed(`${e.skill.name} 代码已更新`);const s=t(e.installPath,"package.json");if(r(s)){const t=B(e.installPath);if(t){const r=V(`安装 ${e.skill.name} 依赖...`).start();try{await R(t,{cwd:e.installPath,timeout:6e4}),r.succeed(`${e.skill.name} 依赖已更新 (${t.command} ${t.args.join(" ")})`)}catch(a){return r.fail(`${e.skill.name} 依赖安装失败`),b.error(T(t,a)),!1}}else b.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(t){return a.fail(`${e.skill.name} 更新失败`),b.error(t instanceof Error?t.message:String(t)),!1}}async function _(e,t){if("installed-package"!==e.source?.type)return;const a=V(`更新 ${e.skill.name} (npm install)...`).start(),s={command:"npm",args:["install","--prefix",e.source.installDir,e.source.packageSpec,...N(t)]};try{await E(s,{timeout:t.networkTimeoutMs},{...M(t.fetchRetries),onRetry:({attempt:t,delayMs:r})=>{a.text=`更新 ${e.skill.name} 遇到网络问题,${Math.round(r/1e3)}s 后重试(第 ${t+1} 次)...`}});const n=v(e.source.installDir,e.source.packageName);if(!r(n))throw new Error(`Installed package root not found: ${n}`);const i=w(n);return a.succeed(`${e.skill.name} 已重新安装`),{packageRoot:n,packageName:i?.name??e.source.packageName,...i?.version?{installedVersion:i.version}:{}}}catch(t){return a.fail(`${e.skill.name} 更新失败`),void b.error(T(s,t))}}async function q(e){const t=V(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),r=new S;try{const a=k(e),s=await r.connect(e.skill.name,a,e.installPath),{tools:n}=await s.listTools();return t.succeed(`${e.skill.name} 元数据已刷新(${n.length} 个 tool)`),!0}catch(r){return t.fail(`${e.skill.name} 刷新失败`),b.error(r instanceof Error?r.message:String(r)),!1}finally{await r.disconnectAll()}}export default e({meta:{description:"检查并更新 roll 及已注册 Agent"},args:{check:{type:"boolean",description:"仅检查 roll/Agent 可用更新,不执行安装或刷新",default:!1},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1}},async run({args:e}){const t=e.check,r=n();let a;try{a=l().installConfig}catch(e){if(!C(r))return b.error(`install 配置无效,已停止更新:${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1);a=c.install,b.warn(`无法读取 install 配置,使用默认值:${e instanceof Error?e.message:String(e)}`)}a.registry&&b.info(`使用 npm registry: ${a.registry}(roll.config.yaml install.registry)`);const s=x(a);b.info("检查 roll 更新...");const o=await j(s);let u,p;o.hasUpdate?b.success(`roll 有新版本: v${o.current} → v${o.latest}`):b.info(`roll 已是最新版本 (v${o.current})`);let g=[];try{u=i().agentsConfig,p=new y(u.dataDir),g=p.list()}catch(e){b.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const k=[];for(const e of g){let t=e;"installed-package"===$(t)&&(t=Y(t,p));const r=$(t);switch(r){case"git":k.push({name:t.skill.name,sourceType:r,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==t.source?.type){k.push({name:t.skill.name,sourceType:r,icon:"?",action:"无法检查已安装包版本"});break}const e=await P({packageName:t.source.packageName,packageSpec:t.source.packageSpec,...t.source.installedVersion?{currentVersion:t.source.installedVersion}:{}},s);k.push({name:t.skill.name,sourceType:r,icon:O(e.status),action:U(e,t.source.packageSpec)});break}case"remote-manifest":k.push({name:t.skill.name,sourceType:r,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":k.push({name:t.skill.name,sourceType:r,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(g.length>0){b.info(`\n已注册 Agent (${g.length}):`);for(const e of k)b.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else u&&b.info("无已注册 Agent");if("needs-migration"!==r.status&&"invalid"!==r.status||(b.info(""),L(r,t?"check":"pre-update")),t)return;b.info("");let w=!1,v=!1;o.hasUpdate&&(w=await K(o.latest,!1,a),w||(v=!0)),!w||"needs-migration"!==r.status&&"invalid"!==r.status||(b.info(""),L(r,"post-update"));let S=0,V=0;for(const t of g){if(!p||!u)break;switch($(t)){case"git":{const e="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);if(await W(t))try{const r=h(t.installPath),a={...t,skill:r.skill,transport:r.transport,runtime:r.runtime,...r.skillBody.length>0?{skillBody:r.skillBody}:{}};p.replace(t.skill.name,a)?(await z(a,e,u.dataDir),S++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}else V++;break}case"installed-package":{const r="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);if(r)try{await f(u.dataDir,t.skill.name)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),V++;break}const s=await _(t,a);if(s)try{const a=h(s.packageRoot),n="installed-package"===t.source?.type?{...t.source,packageName:s.packageName,...s.installedVersion?{installedVersion:s.installedVersion}:{}}:void 0,i={...t,skill:a.skill,transport:a.transport,runtime:a.runtime,installPath:s.packageRoot,...n?{source:n}:{},...a.skillBody.length>0?{skillBody:a.skillBody}:{}},o=await d(i,{skipBrowserSetup:e["skip-browser-setup"]});o.ok||(b.warn(`${i.skill.name} setup 失败:${o.message}`),o.retryCommand&&b.info(`重试命令: ${o.retryCommand}`));p.replace(t.skill.name,i)?o.ok?(r&&await F(i,u.dataDir),S++):(p.updateStatus(i.skill.name,"error"),V++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}else{if(r)try{await F(t,u.dataDir)}catch{p.updateStatus(t.skill.name,"error")}V++}break}case"remote-manifest":try{const e=h(t.installPath),r={...t,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!p.replace(t.skill.name,r)){b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++;break}await q(r)?S++:V++}catch(e){b.warn(`${t.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),V++}break;case"local-path":{const e="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);try{const r=h(t.installPath),a={...t,skill:r.skill,transport:r.transport,runtime:r.runtime,...r.skillBody.length>0?{skillBody:r.skillBody}:{}};p.replace(t.skill.name,a)?(await z(a,e,u.dataDir),S++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}break}}}b.info("");if(V+(v?1:0)>0){process.exitCode=1;const e=v?"roll 更新失败":w?"roll ✓":"roll 无更新";return void b.warn(`更新完成但有失败:${e}${V>0?`,${V} 个 Agent 更新失败`:""}${S>0?`,${S} 个 Agent 已更新`:""}`)}w||S>0?b.success(`更新完成:${w?"roll ✓":"roll 无更新"}${S>0?`,${S} 个 Agent 已更新`:""}`):b.success("一切都已是最新版本")}});async function z(e,t,r){t&&"core-managed"===e.runtime.ownership&&(await f(r,e.skill.name),await F(e,r))}async function F(e,t){let r=!1;try{p(e,t,u(o().config,e.skill.name)),r=!0,await g(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(a){throw r&&await f(t,e.skill.name).catch(()=>{}),a}}
1
+ import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r}from"node:fs";import{execFile as a}from"node:child_process";import{promisify as s}from"node:util";import{inspectConfigFile as n,loadAgentsConfig as i,loadConfig as o,loadInstallConfig as l}from"../../config/loader.js";import{DEFAULT_CONFIG as c}from"../../config/defaults.js";import{getAgentEnv as u}from"../../config/helpers.js";import{getAgentPid as m,startAgent as p,stopAgentGracefully as f,waitForAgentReady as g}from"../../registry/process-manager.js";import{resolveTransportWithDevSpawnSpec as k}from"../../registry/dev-spawn.js";import{runAgentSetup as d}from"../../registry/runtime-setup.js";import{AgentStore as $}from"../../registry/store.js";import{discoverAgent as y}from"../../registry/discovery.js";import{inferAgentSourceType as h,readInstalledPackageManifest as w,resolveInstalledPackageRoot as v}from"../../registry/source.js";import{McpClientManager as S}from"../../mcp/client-manager.js";import{log as b,createSpinner as V}from"../utils/output.js";import{checkForUpdate as j,checkPublishedPackageUpdate as P,getCurrentVersion as A,isPinnedPublishedPackageSpec as M}from"../utils/update-checker.js";import{buildNpmRetryPolicy as B,detectInstallCommand as T,formatPackageManagerError as D,npmInstallNetworkArgs as N,runPackageManager as R,runPackageManagerWithRetry as E}from"../utils/package-manager.js";function x(e){return N({...e.registry?{registry:e.registry}:{},fetchRetries:e.fetchRetries,preferOffline:e.preferOffline})}function C(e){return{forceRefresh:!0,fetchRetries:e.fetchRetries,...e.registry?{registry:e.registry}:{}}}function I(e){return"invalid"===e.status&&(e.error.message.startsWith("Invalid YAML syntax")||e.error.message.includes("(expected YAML object)"))}const L=s(a);export{inferAgentSourceType as inferSourceType}from"../../registry/source.js";export{detectInstallCommand}from"../utils/package-manager.js";function O(e,t){switch(e.status){case"needs-migration":{const r="post-update"===t?"升级后需要迁移本地配置":"检测到本地配置需要迁移";b.warn(`${r}: ${e.configPath}`);for(const t of e.report.issues)b.warn(` - ${t.message}`);e.report.canAutoMigrate&&b.info("建议命令: roll config migrate");break}case"invalid":b.warn(`本地配置存在问题: ${e.configPath??"(unknown path)"}`),b.warn(` - ${e.error.message}`),"post-update"===t&&b.info("请修复配置文件后再继续使用相关命令。")}}function U(e){switch(e){case"up-to-date":return"✅";case"update-available":return"⬆";case"pinned-behind":return"📌";case"unsupported-spec":case"unknown":return"?"}}function W(e,t){switch(e.status){case"up-to-date":return e.currentVersion?`已是最新版本 (v${e.currentVersion})`:"已是最新版本";case"update-available":return e.currentVersion&&e.latestVersion?`可更新 v${e.currentVersion} → v${e.latestVersion}`:"检测到可用更新";case"pinned-behind":return e.currentVersion&&e.latestVersion?`固定版本 v${e.currentVersion};latest=v${e.latestVersion}`:"固定版本,需手动调整 package spec";case"unsupported-spec":return`不支持检查此 package spec: ${t}`;case"unknown":return e.currentVersion?`无法检查最新版本 (current=v${e.currentVersion})`:"无法检查最新版本"}}function Y(e,t){if("installed-package"!==e.source?.type)return e;const r=v(e.source.installDir,e.source.packageName),a=w(r);if(!a)return e;const s={...e.source,...a.name?{packageName:a.name}:{},...a.version?{installedVersion:a.version}:{}},n=s.packageName!==e.source.packageName||s.installedVersion!==e.source.installedVersion,i=r!==e.installPath;if(!n&&!i)return e;const o={...e,installPath:r,source:s};return t&&t.replace(e.skill.name,o),o}function K(e){if("installed-package"!==e.source?.type)return;const{packageName:t,packageSpec:r}=e.source;return r===t||r.startsWith(`${t}@`)&&!M(t,r)?`${t}@latest`:r}async function _(e,t,r){const a=A();if(a===e)return b.info(`roll 已是最新版本 (v${a})`),!1;if(b.info(`roll v${a} → v${e}`),t)return b.info("[dry-run] 跳过实际更新"),!0;const s=V("正在更新 @roll-agent/core...").start(),n={command:"npm",args:["install","-g",`@roll-agent/core@${e}`,...x(r)]};try{return await E(n,{timeout:r.networkTimeoutMs},{...B(r.fetchRetries),onRetry:({attempt:e,delayMs:t})=>{s.text=`roll 更新遇到网络问题,${Math.round(t/1e3)}s 后重试(第 ${e+1} 次)...`}}),s.succeed(`roll 已更新到 v${e}`),!0}catch(e){return s.fail("更新失败"),b.error(D(n,e)),!1}}async function q(e){const a=V(`更新 ${e.skill.name} (git pull)...`).start();try{await L("git",["pull"],{cwd:e.installPath,timeout:3e4}),a.succeed(`${e.skill.name} 代码已更新`);const s=t(e.installPath,"package.json");if(r(s)){const t=T(e.installPath);if(t){const r=V(`安装 ${e.skill.name} 依赖...`).start();try{await R(t,{cwd:e.installPath,timeout:6e4}),r.succeed(`${e.skill.name} 依赖已更新 (${t.command} ${t.args.join(" ")})`)}catch(a){return r.fail(`${e.skill.name} 依赖安装失败`),b.error(D(t,a)),!1}}else b.warn(`${e.skill.name} 未检测到 packageManager 或 lockfile,跳过依赖安装。`)}return!0}catch(t){return a.fail(`${e.skill.name} 更新失败`),b.error(t instanceof Error?t.message:String(t)),!1}}async function z(e,t){if("installed-package"!==e.source?.type)return;const a=V(`更新 ${e.skill.name} (npm install)...`).start(),s=K(e);if(!s)return void a.fail(`${e.skill.name} 更新失败`);const n={command:"npm",args:["install","--prefix",e.source.installDir,s,...x(t)]};try{await E(n,{timeout:t.networkTimeoutMs},{...B(t.fetchRetries),onRetry:({attempt:t,delayMs:r})=>{a.text=`更新 ${e.skill.name} 遇到网络问题,${Math.round(r/1e3)}s 后重试(第 ${t+1} 次)...`}});const s=v(e.source.installDir,e.source.packageName);if(!r(s))throw new Error(`Installed package root not found: ${s}`);const i=w(s);return a.succeed(`${e.skill.name} 已重新安装`),{packageRoot:s,packageName:i?.name??e.source.packageName,...i?.version?{installedVersion:i.version}:{}}}catch(t){return a.fail(`${e.skill.name} 更新失败`),void b.error(D(n,t))}}async function F(e){const t=V(`刷新 ${e.skill.name} (MCP tools/list)...`).start(),r=new S;try{const a=k(e),s=await r.connect(e.skill.name,a,e.installPath),{tools:n}=await s.listTools();return t.succeed(`${e.skill.name} 元数据已刷新(${n.length} 个 tool)`),!0}catch(r){return t.fail(`${e.skill.name} 刷新失败`),b.error(r instanceof Error?r.message:String(r)),!1}finally{await r.disconnectAll()}}export default e({meta:{description:"检查并更新 roll 及已注册 Agent"},args:{check:{type:"boolean",description:"仅检查 roll/Agent 可用更新,不执行安装或刷新",default:!1},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1}},async run({args:e}){const t=e.check,r=n();let a;try{a=l().installConfig}catch(e){if(!I(r))return b.error(`install 配置无效,已停止更新:${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1);a=c.install,b.warn(`无法读取 install 配置,使用默认值:${e instanceof Error?e.message:String(e)}`)}a.registry&&b.info(`使用 npm registry: ${a.registry}(roll.config.yaml install.registry)`);const s=C(a);b.info("检查 roll 更新...");const o=await j(s);let u,p;o.hasUpdate?b.success(`roll 有新版本: v${o.current} → v${o.latest}`):b.info(`roll 已是最新版本 (v${o.current})`);let g=[];try{u=i().agentsConfig,p=new $(u.dataDir),g=p.list()}catch(e){b.warn(`无法读取 Agent 配置,跳过已注册 Agent 检查:${e instanceof Error?e.message:String(e)}`)}const k=[];for(const e of g){let t=e;"installed-package"===h(t)&&(t=Y(t,p));const r=h(t);switch(r){case"git":k.push({name:t.skill.name,sourceType:r,icon:"~",action:"可执行 git pull + 重新安装依赖"});break;case"installed-package":{if("installed-package"!==t.source?.type){k.push({name:t.skill.name,sourceType:r,icon:"?",action:"无法检查已安装包版本"});break}const e=await P({packageName:t.source.packageName,packageSpec:t.source.packageSpec,...t.source.installedVersion?{currentVersion:t.source.installedVersion}:{}},s);k.push({name:t.skill.name,sourceType:r,icon:U(e.status),action:W(e,t.source.packageSpec)});break}case"remote-manifest":k.push({name:t.skill.name,sourceType:r,icon:"~",action:"可刷新本地 manifest + MCP 元数据"});break;case"local-path":k.push({name:t.skill.name,sourceType:r,icon:"⏭",action:"刷新本地 SKILL/manifest"})}}if(g.length>0){b.info(`\n已注册 Agent (${g.length}):`);for(const e of k)b.info(` ${e.icon} ${e.name} [${e.sourceType}] — ${e.action}`)}else u&&b.info("无已注册 Agent");if("needs-migration"!==r.status&&"invalid"!==r.status||(b.info(""),O(r,t?"check":"pre-update")),t)return;b.info("");let w=!1,v=!1;o.hasUpdate&&(w=await _(o.latest,!1,a),w||(v=!0)),!w||"needs-migration"!==r.status&&"invalid"!==r.status||(b.info(""),O(r,"post-update"));let S=0,V=0;for(const t of g){if(!p||!u)break;switch(h(t)){case"git":{const e="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);if(await q(t))try{const r=y(t.installPath),a={...t,skill:r.skill,transport:r.transport,runtime:r.runtime,...r.skillBody.length>0?{skillBody:r.skillBody}:{}};p.replace(t.skill.name,a)?(await G(a,e,u.dataDir),S++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}else V++;break}case"installed-package":{const r="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);if(r)try{await f(u.dataDir,t.skill.name)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} 停止失败,无法继续升级: ${e instanceof Error?e.message:String(e)}`),V++;break}const s=await z(t,a);if(s)try{const a=y(s.packageRoot),n="installed-package"===t.source?.type?{...t.source,packageName:s.packageName,...s.installedVersion?{installedVersion:s.installedVersion}:{}}:void 0,i={...t,skill:a.skill,transport:a.transport,runtime:a.runtime,installPath:s.packageRoot,...n?{source:n}:{},...a.skillBody.length>0?{skillBody:a.skillBody}:{}},o=await d(i,{skipBrowserSetup:e["skip-browser-setup"]});o.ok||(b.warn(`${i.skill.name} setup 失败:${o.message}`),o.retryCommand&&b.info(`重试命令: ${o.retryCommand}`));p.replace(t.skill.name,i)?o.ok?(r&&await H(i,u.dataDir),S++):(p.updateStatus(i.skill.name,"error"),V++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} metadata 刷新、setup 或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}else{if(r)try{await H(t,u.dataDir)}catch{p.updateStatus(t.skill.name,"error")}V++}break}case"remote-manifest":try{const e=y(t.installPath),r={...t,skill:e.skill,transport:e.transport,runtime:e.runtime,...e.skillBody.length>0?{skillBody:e.skillBody}:{}};if(!p.replace(t.skill.name,r)){b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++;break}await F(r)?S++:V++}catch(e){b.warn(`${t.skill.name} manifest 刷新失败: ${e instanceof Error?e.message:String(e)}`),V++}break;case"local-path":{const e="core-managed"===t.runtime.ownership&&void 0!==m(u.dataDir,t.skill.name);try{const r=y(t.installPath),a={...t,skill:r.skill,transport:r.transport,runtime:r.runtime,...r.skillBody.length>0?{skillBody:r.skillBody}:{}};p.replace(t.skill.name,a)?(await G(a,e,u.dataDir),S++):(b.warn(`${t.skill.name} 已从注册表中移除,跳过元数据刷新`),V++)}catch(e){p.updateStatus(t.skill.name,"error"),b.warn(`${t.skill.name} 本地 metadata 刷新或重启失败: ${e instanceof Error?e.message:String(e)}`),V++}break}}}b.info("");if(V+(v?1:0)>0){process.exitCode=1;const e=v?"roll 更新失败":w?"roll ✓":"roll 无更新";return void b.warn(`更新完成但有失败:${e}${V>0?`,${V} 个 Agent 更新失败`:""}${S>0?`,${S} 个 Agent 已更新`:""}`)}w||S>0?b.success(`更新完成:${w?"roll ✓":"roll 无更新"}${S>0?`,${S} 个 Agent 已更新`:""}`):b.success("一切都已是最新版本")}});async function G(e,t,r){t&&"core-managed"===e.runtime.ownership&&(await f(r,e.skill.name),await H(e,r))}async function H(e,t){let r=!1;try{p(e,t,u(o().config,e.skill.name)),r=!0,await g(e,{startupTimeoutMs:15e3,probeTimeoutMs:2e3})}catch(a){throw r&&await f(t,e.skill.name).catch(()=>{}),a}}
@@ -0,0 +1,15 @@
1
+ import type { SessionEvent } from "@roll-agent/runtime";
2
+ export interface ChatApprover {
3
+ approve(approvalId: string): void;
4
+ reject(approvalId: string, reason?: string): void;
5
+ }
6
+ export type ChatConfirm = (message: string) => Promise<boolean>;
7
+ export declare const clackConfirm: ChatConfirm;
8
+ export declare class ChatRenderer {
9
+ private readonly confirm;
10
+ private readonly spinners;
11
+ private streaming;
12
+ constructor(confirm: ChatConfirm);
13
+ handle(event: SessionEvent, approver: ChatApprover): Promise<void>;
14
+ private flushLine;
15
+ }
@@ -0,0 +1 @@
1
+ import t from"chalk";import{confirm as s,isCancel as e}from"@clack/prompts";import{createSpinner as o,log as n,redactToolArgsForLog as r}from"./output.js";export const clackConfirm=async t=>{const o=await s({message:t});return!e(o)&&!0===o};function a(t){const s=JSON.stringify(r(t));return s.length>80?`${s.slice(0,79)}…`:s}function i(t){const s=[];return void 0!==t.inputTokens&&s.push(`in ${String(t.inputTokens)}`),void 0!==t.outputTokens&&s.push(`out ${String(t.outputTokens)}`),void 0!==t.totalTokens&&s.push(`total ${String(t.totalTokens)}`),s.join(" · ")}export class ChatRenderer{confirm;spinners=new Map;streaming=!1;constructor(t){this.confirm=t}async handle(s,e){switch(s.type){case"text-delta":process.stdout.write(s.delta),this.streaming=!0;break;case"tool-call":{this.flushLine();const e=o(`${t.cyan(`${s.agentName}.${s.toolName}`)} ${t.gray(a(s.input))}`);e.start(),this.spinners.set(s.toolCallId,e);break}case"tool-result":{const t=this.spinners.get(s.toolCallId);t&&(s.isError?t.fail():t.succeed(),this.spinners.delete(s.toolCallId));break}case"confirmation-required":{this.flushLine();const t=s.reason?`(${s.reason})`:"";await this.confirm(`执行 ${s.agentName}.${s.toolName}${t}?`)?e.approve(s.approvalId):e.reject(s.approvalId,"用户取消");break}case"error":this.flushLine(),n.error(s.message);break;case"message-finish":this.flushLine(),s.totalUsage&&n.debug(`tokens: ${i(s.totalUsage)}`)}}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
@@ -24,6 +24,7 @@ export interface UpdateInfo {
24
24
  export declare function fetchLatestPublishedVersion(packageName: string, options?: PackageVersionQueryOptions): Promise<string | undefined>;
25
25
  export declare function getCurrentVersion(): string;
26
26
  export declare function isNewerVersion(latest: string, current: string): boolean;
27
+ export declare function isPinnedPublishedPackageSpec(packageName: string, packageSpec: string): boolean;
27
28
  export declare function checkPublishedPackageUpdate(input: {
28
29
  packageName: string;
29
30
  packageSpec: string;
@@ -1 +1 @@
1
- import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as r}from"node:fs";import{resolve as s,dirname as a}from"node:path";import{homedir as o}from"node:os";import{npmViewNetworkArgs as i,runPackageManager as c}from"./package-manager.js";const u=864e5,p="@roll-agent/core",f=1e4;export const PUBLISHED_PACKAGE_UPDATE_STATUSES=["up-to-date","update-available","pinned-behind","unsupported-spec","unknown"];function l(){return s(o(),".roll-agent","update-check.json")}function h(t){return"object"==typeof t&&null!==t&&("latestVersion"in t&&"checkedAt"in t&&("string"==typeof t.latestVersion&&t.latestVersion.length>0&&"number"==typeof t.checkedAt))}function g(t){return"object"==typeof t&&null!==t&&"packages"in t&&("object"==typeof t.packages&&null!==t.packages&&Object.values(t.packages).every(t=>h(t)))}function d(t){return h(t)}function k(){const e=l();if(r(e))try{const n=t(e,"utf-8"),r=JSON.parse(n);if(g(r))return r;if(d(r))return{packages:{[p]:{latestVersion:r.latestVersion,checkedAt:r.checkedAt}}}}catch{}}function m(t){try{const s=l(),o=a(s);r(o)||n(o,{recursive:!0}),e(s,JSON.stringify(t),"utf-8")}catch{}}function V(t,e){const n=k();m({packages:{...n?.packages??{},[t]:{latestVersion:e,checkedAt:Date.now()}}})}function y(t){return k()?.packages[t]}async function w(t,e={}){try{const n=i({...e.registry?{registry:e.registry}:{},...void 0!==e.fetchRetries?{fetchRetries:e.fetchRetries}:{}}),{stdout:r}=await c({command:"npm",args:["view",t,"version","--json",...n]},{timeout:e.timeoutMs??f}),s=r.trim();if(0===s.length)return;const a=JSON.parse(s);return"string"==typeof a&&a.length>0?a:void 0}catch{return}}export async function fetchLatestPublishedVersion(t,e={}){const n=e.forceRefresh??!1,r=e.allowNetwork??!0,s=y(t);if(!n&&s&&Date.now()-s.checkedAt<u)return s.latestVersion;if(!r)return s?.latestVersion;const a=await w(t,e);return a?(V(t,a),a):s?.latestVersion}export function getCurrentVersion(){try{const e=s(import.meta.dirname,"../../../package.json"),n=t(e,"utf-8"),r=JSON.parse(n);if("object"==typeof r&&null!==r&&"version"in r){const t=r.version;if("string"==typeof t)return t}}catch{}return"0.0.0"}export function isNewerVersion(t,e){const n=t=>t.replace(/^v/,"").split(".").map(Number),r=n(t),s=n(e);for(let t=0;t<3;t+=1){const e=r[t]??0,n=s[t]??0;if(e>n)return!0;if(e<n)return!1}return!1}function N(t,e){return!["file:","git+","http://","https://","link:","workspace:","npm:"].some(t=>e.startsWith(t))&&(!(e.startsWith(".")||e.startsWith("/")||e.startsWith("~")||e.endsWith(".tgz")||e.endsWith(".tar.gz"))&&(e===t||e.startsWith(`${t}@`)))}function b(t,e){if(e.startsWith(`${t}@`))return e.slice(t.length+1)}function v(t,e){const n=b(t,e);if(!n)return!1;const r=n.replace(/^v/,"");return/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r)}export async function checkPublishedPackageUpdate(t,e={}){const{packageName:n,packageSpec:r,currentVersion:s}=t;if(!N(n,r))return{packageName:n,...s?{currentVersion:s}:{},status:"unsupported-spec"};if(!s)return{packageName:n,status:"unknown"};const a=await fetchLatestPublishedVersion(n,e);return a?isNewerVersion(a,s)?v(n,r)?{packageName:n,currentVersion:s,latestVersion:a,status:"pinned-behind"}:{packageName:n,currentVersion:s,latestVersion:a,status:"update-available"}:{packageName:n,currentVersion:s,latestVersion:a,status:"up-to-date"}:{packageName:n,currentVersion:s,status:"unknown"}}export async function checkForUpdate(t={}){const e=getCurrentVersion(),n=await fetchLatestPublishedVersion(p,t)??e;return{current:e,latest:n,hasUpdate:isNewerVersion(n,e)}}
1
+ import{readFileSync as e,writeFileSync as t,mkdirSync as n,existsSync as r}from"node:fs";import{resolve as s,dirname as a}from"node:path";import{homedir as o}from"node:os";import{npmViewNetworkArgs as i,runPackageManager as c}from"./package-manager.js";const u=864e5,p="@roll-agent/core",f=1e4;export const PUBLISHED_PACKAGE_UPDATE_STATUSES=["up-to-date","update-available","pinned-behind","unsupported-spec","unknown"];function l(){return s(o(),".roll-agent","update-check.json")}function h(e){return"object"==typeof e&&null!==e&&("latestVersion"in e&&"checkedAt"in e&&("string"==typeof e.latestVersion&&e.latestVersion.length>0&&"number"==typeof e.checkedAt))}function d(e){return"object"==typeof e&&null!==e&&"packages"in e&&("object"==typeof e.packages&&null!==e.packages&&Object.values(e.packages).every(e=>h(e)))}function g(e){return h(e)}function k(){const t=l();if(r(t))try{const n=e(t,"utf-8"),r=JSON.parse(n);if(d(r))return r;if(g(r))return{packages:{[p]:{latestVersion:r.latestVersion,checkedAt:r.checkedAt}}}}catch{}}function m(e){try{const s=l(),o=a(s);r(o)||n(o,{recursive:!0}),t(s,JSON.stringify(e),"utf-8")}catch{}}function V(e,t){const n=k();m({packages:{...n?.packages??{},[e]:{latestVersion:t,checkedAt:Date.now()}}})}function y(e){return k()?.packages[e]}async function b(e,t={}){try{const n=i({...t.registry?{registry:t.registry}:{},...void 0!==t.fetchRetries?{fetchRetries:t.fetchRetries}:{}}),{stdout:r}=await c({command:"npm",args:["view",e,"version","--json",...n]},{timeout:t.timeoutMs??f}),s=r.trim();if(0===s.length)return;const a=JSON.parse(s);return"string"==typeof a&&a.length>0?a:void 0}catch{return}}export async function fetchLatestPublishedVersion(e,t={}){const n=t.forceRefresh??!1,r=t.allowNetwork??!0,s=y(e);if(!n&&s&&Date.now()-s.checkedAt<u)return s.latestVersion;if(!r)return s?.latestVersion;const a=await b(e,t);return a?(V(e,a),a):s?.latestVersion}export function getCurrentVersion(){try{const t=s(import.meta.dirname,"../../../package.json"),n=e(t,"utf-8"),r=JSON.parse(n);if("object"==typeof r&&null!==r&&"version"in r){const e=r.version;if("string"==typeof e)return e}}catch{}return"0.0.0"}export function isNewerVersion(e,t){const n=e=>e.replace(/^v/,"").split(".").map(Number),r=n(e),s=n(t);for(let e=0;e<3;e+=1){const t=r[e]??0,n=s[e]??0;if(t>n)return!0;if(t<n)return!1}return!1}function w(e,t){return!["file:","git+","http://","https://","link:","workspace:","npm:"].some(e=>t.startsWith(e))&&(!(t.startsWith(".")||t.startsWith("/")||t.startsWith("~")||t.endsWith(".tgz")||t.endsWith(".tar.gz"))&&(t===e||t.startsWith(`${e}@`)))}function N(e,t){if(t.startsWith(`${e}@`))return t.slice(e.length+1)}export function isPinnedPublishedPackageSpec(e,t){const n=N(e,t);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(e,t={}){const{packageName:n,packageSpec:r,currentVersion:s}=e;if(!w(n,r))return{packageName:n,...s?{currentVersion:s}:{},status:"unsupported-spec"};if(!s)return{packageName:n,status:"unknown"};const a=await fetchLatestPublishedVersion(n,t);return a?isNewerVersion(a,s)?isPinnedPublishedPackageSpec(n,r)?{packageName:n,currentVersion:s,latestVersion:a,status:"pinned-behind"}:{packageName:n,currentVersion:s,latestVersion:a,status:"update-available"}:{packageName:n,currentVersion:s,latestVersion:a,status:"up-to-date"}:{packageName:n,currentVersion:s,status:"unknown"}}export async function checkForUpdate(e={}){const t=getCurrentVersion(),n=await fetchLatestPublishedVersion(p,e)??t;return{current:t,latest:n,hasUpdate:isNewerVersion(n,t)}}
@@ -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:{},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:16,threadsDir:"~/.roll-agent/threads",approval:{default:"guarded",overrides:{}}},agents:{dataDir:"~/.roll-agent/agents"},install:{fetchRetries:3,preferOffline:!1,networkTimeoutMs:12e4},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
@@ -1 +1 @@
1
- const e={kind:"leaf"},r={kind:"object",fields:{apiKey:e,baseUrl:e}},o={kind:"object",fields:{platform:e,mode:e,headless:e,cdpUrl:e,cdpHost:e,cdpPort:e,channel:e,executablePath:e,userDataDir:e,sessionsDir:e,args:e,profileName:e,profileColor:e,windowBounds:{kind:"object",fields:{x:e,y:e,width:e,height:e}},trackingAgentId:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:r}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}},install:{kind:"object",fields:{registry:e,fetchRetries:e,preferOffline:e,networkTimeoutMs:e}},browser:{kind:"object",fields:{defaultInstance:e,instances:{kind:"record",value:o}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,r)=>r.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function t(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(r,o=CONFIG_KEY_CODEC){if(Array.isArray(r))return r.map(e=>decodeFromYaml(e,o));if(!t(r))return r;if("object"===o.kind){const t={};for(const[n,i]of Object.entries(r)){const r=kebabToCamel(n),d=o.fields[r]??e;t[r]=decodeFromYaml(i,d)}return t}if("record"===o.kind){const e={};for(const[t,n]of Object.entries(r))e[t]=decodeFromYaml(n,o.value);return e}return r}function n(r,o){const t=[];let n=CONFIG_KEY_CODEC;for(const i of r)if("object"===n.kind){const r=kebabToCamel(i),d=n.fields[r];d?(t.push("camel"===o?r:camelToKebab(r)),n=d):(t.push(i),n=e)}else"record"===n.kind?(t.push(i),n=n.value):t.push(i);return t}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
1
+ const e={kind:"leaf"},r={kind:"object",fields:{apiKey:e,baseUrl:e}},o={kind:"object",fields:{platform:e,mode:e,headless:e,cdpUrl:e,cdpHost:e,cdpPort:e,channel:e,executablePath:e,userDataDir:e,sessionsDir:e,args:e,profileName:e,profileColor:e,windowBounds:{kind:"object",fields:{x:e,y:e,width:e,height:e}},trackingAgentId:e}};export const CONFIG_KEY_CODEC={kind:"object",fields:{llm:{kind:"object",fields:{defaultProvider:e,defaultModel:e,providers:{kind:"record",value:r}}},ask:{kind:"object",fields:{llmModel:e,confirmThreshold:e}},runtime:{kind:"object",fields:{provider:e,model:e,maxSteps:e,threadsDir:e,approval:{kind:"object",fields:{default:e,overrides:{kind:"record",value:e}}}}},agents:{kind:"object",fields:{dataDir:e,env:{kind:"record",value:{kind:"record",value:e}}}},install:{kind:"object",fields:{registry:e,fetchRetries:e,preferOffline:e,networkTimeoutMs:e}},browser:{kind:"object",fields:{defaultInstance:e,instances:{kind:"record",value:o}}}}};export function kebabToCamel(e){return e.replace(/-([a-z])/g,(e,r)=>r.toUpperCase())}export function camelToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function t(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}export function decodeFromYaml(r,o=CONFIG_KEY_CODEC){if(Array.isArray(r))return r.map(e=>decodeFromYaml(e,o));if(!t(r))return r;if("object"===o.kind){const t={};for(const[n,i]of Object.entries(r)){const r=kebabToCamel(n),d=o.fields[r]??e;t[r]=decodeFromYaml(i,d)}return t}if("record"===o.kind){const e={};for(const[t,n]of Object.entries(r))e[t]=decodeFromYaml(n,o.value);return e}return r}function n(r,o){const t=[];let n=CONFIG_KEY_CODEC;for(const i of r)if("object"===n.kind){const r=kebabToCamel(i),d=n.fields[r];d?(t.push("camel"===o?r:camelToKebab(r)),n=d):(t.push(i),n=e)}else"record"===n.kind?(t.push(i),n=n.value):t.push(i);return t}export function encodePathToYaml(e){return n(e,"kebab")}export function normalizeUserPath(e){return n(e,"camel")}
@@ -1 +1 @@
1
- import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,installConfigSchema as i,rollConfigSchema as s}from"./schema.js";import{DEFAULT_CONFIG as a,CONFIG_FILE_NAMES as f}from"./defaults.js";import{decodeFromYaml as c}from"./key-codec.js";import{detectKnownConfigMigrations as u,formatConfigMigrationError as l}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(d(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function d(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function p(n){return d(n)&&"number"==typeof n.line&&"number"==typeof n.col}function h(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(p)}function m(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(h(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function w(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of f){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function $(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:v(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:v(r.sessionsDir)}:{}}]))}}}const C={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(m(r,n),{cause:n instanceof Error?n:void 0})}if(!d(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function P(n,r,t={}){const o=parseConfigDocument(n,r),e=u(o,t);if(e.needsMigration)throw new Error(l(r,e));return o}export function validateConfigText(n,r){const t=P(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=j(a,o),i=s.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return $(i.data)}function E(n,r){const t=P(n,r,{scope:"agents"}),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=d(o.agents)?o.agents:{},s=j(a.agents,i),f=e.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...f.data,dataDir:v(f.data.dataDir)}}function y(n,r){const t=parseConfigDocument(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=d(o.install)?o.install:{},s=j(a.install,e),f=i.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return f.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:a.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:y(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:$(a),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...a.agents,dataDir:v(a.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:E(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??w(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:C.notFound,configPath:void 0};if(!r(o))return{status:C.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=u(i);if(s.needsMigration)return{status:C.needsMigration,configPath:o,raw:e,report:s};try{return{status:C.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function j(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=j(r,e)}return t}
1
+ import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,installConfigSchema as i,rollConfigSchema as s}from"./schema.js";import{DEFAULT_CONFIG as a,CONFIG_FILE_NAMES as f}from"./defaults.js";import{decodeFromYaml as c}from"./key-codec.js";import{detectKnownConfigMigrations as u,formatConfigMigrationError as l}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(d(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function d(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function p(n){return d(n)&&"number"==typeof n.line&&"number"==typeof n.col}function h(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(p)}function m(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(h(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function w(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of f){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function $(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)},runtime:{...n.runtime,threadsDir:v(n.runtime.threadsDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:v(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:v(r.sessionsDir)}:{}}]))}}}const C={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(m(r,n),{cause:n instanceof Error?n:void 0})}if(!d(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function P(n,r,t={}){const o=parseConfigDocument(n,r),e=u(o,t);if(e.needsMigration)throw new Error(l(r,e));return o}export function validateConfigText(n,r){const t=P(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=j(a,o),i=s.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return $(i.data)}function E(n,r){const t=P(n,r,{scope:"agents"}),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=d(o.agents)?o.agents:{},s=j(a.agents,i),f=e.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...f.data,dataDir:v(f.data.dataDir)}}function y(n,r){const t=parseConfigDocument(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=d(o.install)?o.install:{},s=j(a.install,e),f=i.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return f.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:a.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:y(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:$(a),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...a.agents,dataDir:v(a.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:E(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??w(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:C.notFound,configPath:void 0};if(!r(o))return{status:C.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=u(i);if(s.needsMigration)return{status:C.needsMigration,configPath:o,raw:e,report:s};try{return{status:C.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function j(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=j(r,e)}return t}
@@ -22,13 +22,13 @@ export declare const BrowserSecurityDiagnosticSchema: z.ZodObject<{
22
22
  domainAllowlist: string[];
23
23
  maxPageContentBytes: number;
24
24
  maxSnapshotNodes: number;
25
- actionPolicy: "log" | "deny" | "confirm";
25
+ actionPolicy: "deny" | "confirm" | "log";
26
26
  foregroundPolicy: "never" | "when-minimized" | "always";
27
27
  }, {
28
28
  domainAllowlist: string[];
29
29
  maxPageContentBytes: number;
30
30
  maxSnapshotNodes: number;
31
- actionPolicy: "log" | "deny" | "confirm";
31
+ actionPolicy: "deny" | "confirm" | "log";
32
32
  foregroundPolicy?: "never" | "when-minimized" | "always" | undefined;
33
33
  }>;
34
34
  export declare const BrowserUseToolPolicyDiagnosticSchema: z.ZodObject<{
@@ -36,18 +36,18 @@ export declare const BrowserUseToolPolicyDiagnosticSchema: z.ZodObject<{
36
36
  tools: z.ZodRecord<z.ZodString, z.ZodObject<{
37
37
  policy: z.ZodEnum<["log", "deny", "confirm"]>;
38
38
  }, "strip", z.ZodTypeAny, {
39
- policy: "log" | "deny" | "confirm";
39
+ policy: "deny" | "confirm" | "log";
40
40
  }, {
41
- policy: "log" | "deny" | "confirm";
41
+ policy: "deny" | "confirm" | "log";
42
42
  }>>;
43
43
  }, "strip", z.ZodTypeAny, {
44
44
  tools: Record<string, {
45
- policy: "log" | "deny" | "confirm";
45
+ policy: "deny" | "confirm" | "log";
46
46
  }>;
47
47
  approvalTtlMs: number;
48
48
  }, {
49
49
  tools: Record<string, {
50
- policy: "log" | "deny" | "confirm";
50
+ policy: "deny" | "confirm" | "log";
51
51
  }>;
52
52
  approvalTtlMs: number;
53
53
  }>;
@@ -164,13 +164,13 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
164
164
  domainAllowlist: string[];
165
165
  maxPageContentBytes: number;
166
166
  maxSnapshotNodes: number;
167
- actionPolicy: "log" | "deny" | "confirm";
167
+ actionPolicy: "deny" | "confirm" | "log";
168
168
  foregroundPolicy: "never" | "when-minimized" | "always";
169
169
  }, {
170
170
  domainAllowlist: string[];
171
171
  maxPageContentBytes: number;
172
172
  maxSnapshotNodes: number;
173
- actionPolicy: "log" | "deny" | "confirm";
173
+ actionPolicy: "deny" | "confirm" | "log";
174
174
  foregroundPolicy?: "never" | "when-minimized" | "always" | undefined;
175
175
  }>>;
176
176
  toolPolicy: z.ZodOptional<z.ZodObject<{
@@ -178,18 +178,18 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
178
178
  tools: z.ZodRecord<z.ZodString, z.ZodObject<{
179
179
  policy: z.ZodEnum<["log", "deny", "confirm"]>;
180
180
  }, "strip", z.ZodTypeAny, {
181
- policy: "log" | "deny" | "confirm";
181
+ policy: "deny" | "confirm" | "log";
182
182
  }, {
183
- policy: "log" | "deny" | "confirm";
183
+ policy: "deny" | "confirm" | "log";
184
184
  }>>;
185
185
  }, "strip", z.ZodTypeAny, {
186
186
  tools: Record<string, {
187
- policy: "log" | "deny" | "confirm";
187
+ policy: "deny" | "confirm" | "log";
188
188
  }>;
189
189
  approvalTtlMs: number;
190
190
  }, {
191
191
  tools: Record<string, {
192
- policy: "log" | "deny" | "confirm";
192
+ policy: "deny" | "confirm" | "log";
193
193
  }>;
194
194
  approvalTtlMs: number;
195
195
  }>>;
@@ -316,12 +316,12 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
316
316
  domainAllowlist: string[];
317
317
  maxPageContentBytes: number;
318
318
  maxSnapshotNodes: number;
319
- actionPolicy: "log" | "deny" | "confirm";
319
+ actionPolicy: "deny" | "confirm" | "log";
320
320
  foregroundPolicy: "never" | "when-minimized" | "always";
321
321
  } | undefined;
322
322
  toolPolicy?: {
323
323
  tools: Record<string, {
324
- policy: "log" | "deny" | "confirm";
324
+ policy: "deny" | "confirm" | "log";
325
325
  }>;
326
326
  approvalTtlMs: number;
327
327
  } | undefined;
@@ -360,12 +360,12 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
360
360
  domainAllowlist: string[];
361
361
  maxPageContentBytes: number;
362
362
  maxSnapshotNodes: number;
363
- actionPolicy: "log" | "deny" | "confirm";
363
+ actionPolicy: "deny" | "confirm" | "log";
364
364
  foregroundPolicy?: "never" | "when-minimized" | "always" | undefined;
365
365
  } | undefined;
366
366
  toolPolicy?: {
367
367
  tools: Record<string, {
368
- policy: "log" | "deny" | "confirm";
368
+ policy: "deny" | "confirm" | "log";
369
369
  }>;
370
370
  approvalTtlMs: number;
371
371
  } | undefined;
@@ -63,6 +63,50 @@ export declare const askConfigSchema: z.ZodObject<{
63
63
  llmModel?: string | undefined;
64
64
  confirmThreshold?: number | undefined;
65
65
  }>;
66
+ export declare const runtimeApprovalConfigSchema: z.ZodObject<{
67
+ default: z.ZodDefault<z.ZodEnum<["guarded", "auto", "deny"]>>;
68
+ overrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["auto", "confirm", "deny"]>>>;
69
+ }, "strip", z.ZodTypeAny, {
70
+ default: "guarded" | "auto" | "deny";
71
+ overrides: Record<string, "auto" | "deny" | "confirm">;
72
+ }, {
73
+ default?: "guarded" | "auto" | "deny" | undefined;
74
+ overrides?: Record<string, "auto" | "deny" | "confirm"> | undefined;
75
+ }>;
76
+ export declare const runtimeConfigSchema: z.ZodObject<{
77
+ provider: z.ZodOptional<z.ZodString>;
78
+ model: z.ZodOptional<z.ZodString>;
79
+ maxSteps: z.ZodDefault<z.ZodNumber>;
80
+ threadsDir: z.ZodDefault<z.ZodString>;
81
+ approval: z.ZodDefault<z.ZodObject<{
82
+ default: z.ZodDefault<z.ZodEnum<["guarded", "auto", "deny"]>>;
83
+ overrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["auto", "confirm", "deny"]>>>;
84
+ }, "strip", z.ZodTypeAny, {
85
+ default: "guarded" | "auto" | "deny";
86
+ overrides: Record<string, "auto" | "deny" | "confirm">;
87
+ }, {
88
+ default?: "guarded" | "auto" | "deny" | undefined;
89
+ overrides?: Record<string, "auto" | "deny" | "confirm"> | undefined;
90
+ }>>;
91
+ }, "strip", z.ZodTypeAny, {
92
+ maxSteps: number;
93
+ threadsDir: string;
94
+ approval: {
95
+ default: "guarded" | "auto" | "deny";
96
+ overrides: Record<string, "auto" | "deny" | "confirm">;
97
+ };
98
+ provider?: string | undefined;
99
+ model?: string | undefined;
100
+ }, {
101
+ provider?: string | undefined;
102
+ model?: string | undefined;
103
+ maxSteps?: number | undefined;
104
+ threadsDir?: string | undefined;
105
+ approval?: {
106
+ default?: "guarded" | "auto" | "deny" | undefined;
107
+ overrides?: Record<string, "auto" | "deny" | "confirm"> | undefined;
108
+ } | undefined;
109
+ }>;
66
110
  export declare const agentsConfigSchema: z.ZodObject<{
67
111
  dataDir: z.ZodString;
68
112
  /** per-agent 环境变量:键为 agent name,值为 key-value 对 */
@@ -470,6 +514,40 @@ export declare const rollConfigSchema: z.ZodObject<{
470
514
  llmModel?: string | undefined;
471
515
  confirmThreshold?: number | undefined;
472
516
  }>;
517
+ runtime: z.ZodDefault<z.ZodObject<{
518
+ provider: z.ZodOptional<z.ZodString>;
519
+ model: z.ZodOptional<z.ZodString>;
520
+ maxSteps: z.ZodDefault<z.ZodNumber>;
521
+ threadsDir: z.ZodDefault<z.ZodString>;
522
+ approval: z.ZodDefault<z.ZodObject<{
523
+ default: z.ZodDefault<z.ZodEnum<["guarded", "auto", "deny"]>>;
524
+ overrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["auto", "confirm", "deny"]>>>;
525
+ }, "strip", z.ZodTypeAny, {
526
+ default: "guarded" | "auto" | "deny";
527
+ overrides: Record<string, "auto" | "deny" | "confirm">;
528
+ }, {
529
+ default?: "guarded" | "auto" | "deny" | undefined;
530
+ overrides?: Record<string, "auto" | "deny" | "confirm"> | undefined;
531
+ }>>;
532
+ }, "strip", z.ZodTypeAny, {
533
+ maxSteps: number;
534
+ threadsDir: string;
535
+ approval: {
536
+ default: "guarded" | "auto" | "deny";
537
+ overrides: Record<string, "auto" | "deny" | "confirm">;
538
+ };
539
+ provider?: string | undefined;
540
+ model?: string | undefined;
541
+ }, {
542
+ provider?: string | undefined;
543
+ model?: string | undefined;
544
+ maxSteps?: number | undefined;
545
+ threadsDir?: string | undefined;
546
+ approval?: {
547
+ default?: "guarded" | "auto" | "deny" | undefined;
548
+ overrides?: Record<string, "auto" | "deny" | "confirm"> | undefined;
549
+ } | undefined;
550
+ }>>;
473
551
  agents: z.ZodObject<{
474
552
  dataDir: z.ZodString;
475
553
  /** per-agent 环境变量:键为 agent name,值为 key-value 对 */
@@ -760,6 +838,16 @@ export declare const rollConfigSchema: z.ZodObject<{
760
838
  baseUrl?: string | undefined;
761
839
  }>;
762
840
  };
841
+ runtime: {
842
+ maxSteps: number;
843
+ threadsDir: string;
844
+ approval: {
845
+ default: "guarded" | "auto" | "deny";
846
+ overrides: Record<string, "auto" | "deny" | "confirm">;
847
+ };
848
+ provider?: string | undefined;
849
+ model?: string | undefined;
850
+ };
763
851
  agents: {
764
852
  dataDir: string;
765
853
  env?: Record<string, Record<string, string>> | undefined;
@@ -812,7 +900,19 @@ export declare const rollConfigSchema: z.ZodObject<{
812
900
  trackingAgentId?: string | undefined;
813
901
  }> | undefined;
814
902
  } | undefined;
903
+ runtime?: {
904
+ provider?: string | undefined;
905
+ model?: string | undefined;
906
+ maxSteps?: number | undefined;
907
+ threadsDir?: string | undefined;
908
+ approval?: {
909
+ default?: "guarded" | "auto" | "deny" | undefined;
910
+ overrides?: Record<string, "auto" | "deny" | "confirm"> | undefined;
911
+ } | undefined;
912
+ } | undefined;
815
913
  }>;
816
914
  export type RollConfig = z.infer<typeof rollConfigSchema>;
915
+ export type RuntimeConfig = z.infer<typeof runtimeConfigSchema>;
916
+ export type RuntimeApprovalConfig = z.infer<typeof runtimeApprovalConfigSchema>;
817
917
  export type BrowserConfig = z.infer<typeof browserConfigSchema>;
818
918
  export type InstallConfig = z.infer<typeof installConfigSchema>;
@@ -1 +1 @@
1
- import{z as e}from"zod";const o=["zhipin","yupao"],t=["managed-cdp","remote-cdp","existing-session"],n=["chrome","chromium","msedge"],s=e.string().trim().regex(/^#[\da-fA-F]{6}$/,"profileColor must be a hex RGB color such as #2563EB").transform(e=>e.toUpperCase());export const browserWindowBoundsSchema=e.object({x:e.number().int().optional(),y:e.number().int().optional(),width:e.number().int().positive().optional(),height:e.number().int().positive().optional()});export const providerConfigSchema=e.object({apiKey:e.string(),baseUrl:e.string().optional()});export const llmConfigSchema=e.object({defaultProvider:e.string(),defaultModel:e.string(),providers:e.record(e.string(),providerConfigSchema)});export const askConfigSchema=e.object({llmModel:e.string().optional(),confirmThreshold:e.number().optional()});export const agentsConfigSchema=e.object({dataDir:e.string(),env:e.record(e.string(),e.record(e.string(),e.string())).optional()});export const installConfigSchema=e.object({registry:e.string().trim().url().optional(),fetchRetries:e.number().int().min(0).max(10).default(3),preferOffline:e.boolean().default(!1),networkTimeoutMs:e.number().int().min(1e4).default(12e4)});export const browserInstanceConfigSchema=e.object({platform:e.enum(o).optional(),mode:e.enum(t).default("managed-cdp"),headless:e.boolean().optional(),cdpUrl:e.string().optional(),cdpHost:e.string().default("127.0.0.1"),cdpPort:e.number().int().min(1).max(65535).optional(),channel:e.enum(n).default("chrome"),executablePath:e.string().optional(),userDataDir:e.string().trim().min(1),sessionsDir:e.string().trim().min(1).optional(),args:e.array(e.string()).optional(),profileName:e.string().trim().min(1).optional(),profileColor:s.optional(),windowBounds:browserWindowBoundsSchema.optional(),trackingAgentId:e.string().trim().min(1).optional()}).superRefine((o,t)=>{"managed-cdp"===o.mode&&void 0===o.cdpPort&&t.addIssue({code:e.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==o.mode&&"existing-session"!==o.mode||void 0!==o.cdpUrl||t.addIssue({code:e.ZodIssueCode.custom,path:["cdpUrl"],message:`${o.mode} browser instance requires cdpUrl`})});export const browserConfigSchema=e.object({defaultInstance:e.string().trim().min(1).optional(),instances:e.record(e.string(),browserInstanceConfigSchema).default({})}).superRefine((o,t)=>{const n=Object.entries(o.instances);void 0!==o.defaultInstance&&void 0===o.instances[o.defaultInstance]&&t.addIssue({code:e.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${o.defaultInstance}" is not declared in browser.instances`});const s=new Map,r=new Map;for(const[o,i]of n){if(void 0!==i.cdpPort){const n=s.get(i.cdpPort);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"cdpPort"],message:`cdpPort ${String(i.cdpPort)} is already used by browser instance "${n}"`}):s.set(i.cdpPort,o)}const n=r.get(i.userDataDir);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"userDataDir"],message:`userDataDir is already used by browser instance "${n}"`}):r.set(i.userDataDir,o)}});export const rollConfigSchema=e.object({llm:llmConfigSchema,ask:askConfigSchema,agents:agentsConfigSchema,install:installConfigSchema.default({}),browser:browserConfigSchema.default({})});
1
+ import{z as e}from"zod";const o=["zhipin","yupao"],t=["managed-cdp","remote-cdp","existing-session"],n=["chrome","chromium","msedge"],r=["guarded","auto","deny"],s=["auto","confirm","deny"],i=e.string().trim().regex(/^#[\da-fA-F]{6}$/,"profileColor must be a hex RGB color such as #2563EB").transform(e=>e.toUpperCase());export const browserWindowBoundsSchema=e.object({x:e.number().int().optional(),y:e.number().int().optional(),width:e.number().int().positive().optional(),height:e.number().int().positive().optional()});export const providerConfigSchema=e.object({apiKey:e.string(),baseUrl:e.string().optional()});export const llmConfigSchema=e.object({defaultProvider:e.string(),defaultModel:e.string(),providers:e.record(e.string(),providerConfigSchema)});export const askConfigSchema=e.object({llmModel:e.string().optional(),confirmThreshold:e.number().optional()});export const runtimeApprovalConfigSchema=e.object({default:e.enum(r).default("guarded"),overrides:e.record(e.string(),e.enum(s)).default({})});export const runtimeConfigSchema=e.object({provider:e.string().optional(),model:e.string().optional(),maxSteps:e.number().int().min(1).default(16),threadsDir:e.string().default("~/.roll-agent/threads"),approval:runtimeApprovalConfigSchema.default({})});export const agentsConfigSchema=e.object({dataDir:e.string(),env:e.record(e.string(),e.record(e.string(),e.string())).optional()});export const installConfigSchema=e.object({registry:e.string().trim().url().optional(),fetchRetries:e.number().int().min(0).max(10).default(3),preferOffline:e.boolean().default(!1),networkTimeoutMs:e.number().int().min(1e4).default(12e4)});export const browserInstanceConfigSchema=e.object({platform:e.enum(o).optional(),mode:e.enum(t).default("managed-cdp"),headless:e.boolean().optional(),cdpUrl:e.string().optional(),cdpHost:e.string().default("127.0.0.1"),cdpPort:e.number().int().min(1).max(65535).optional(),channel:e.enum(n).default("chrome"),executablePath:e.string().optional(),userDataDir:e.string().trim().min(1),sessionsDir:e.string().trim().min(1).optional(),args:e.array(e.string()).optional(),profileName:e.string().trim().min(1).optional(),profileColor:i.optional(),windowBounds:browserWindowBoundsSchema.optional(),trackingAgentId:e.string().trim().min(1).optional()}).superRefine((o,t)=>{"managed-cdp"===o.mode&&void 0===o.cdpPort&&t.addIssue({code:e.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==o.mode&&"existing-session"!==o.mode||void 0!==o.cdpUrl||t.addIssue({code:e.ZodIssueCode.custom,path:["cdpUrl"],message:`${o.mode} browser instance requires cdpUrl`})});export const browserConfigSchema=e.object({defaultInstance:e.string().trim().min(1).optional(),instances:e.record(e.string(),browserInstanceConfigSchema).default({})}).superRefine((o,t)=>{const n=Object.entries(o.instances);void 0!==o.defaultInstance&&void 0===o.instances[o.defaultInstance]&&t.addIssue({code:e.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${o.defaultInstance}" is not declared in browser.instances`});const r=new Map,s=new Map;for(const[o,i]of n){if(void 0!==i.cdpPort){const n=r.get(i.cdpPort);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"cdpPort"],message:`cdpPort ${String(i.cdpPort)} is already used by browser instance "${n}"`}):r.set(i.cdpPort,o)}const n=s.get(i.userDataDir);void 0!==n?t.addIssue({code:e.ZodIssueCode.custom,path:["instances",o,"userDataDir"],message:`userDataDir is already used by browser instance "${n}"`}):s.set(i.userDataDir,o)}});export const rollConfigSchema=e.object({llm:llmConfigSchema,ask:askConfigSchema,runtime:runtimeConfigSchema.default({}),agents:agentsConfigSchema,install:installConfigSchema.default({}),browser:browserConfigSchema.default({})});
@@ -7,6 +7,15 @@ export interface ChatStepSummary {
7
7
  readonly agentName?: string;
8
8
  readonly toolName?: string;
9
9
  }
10
+ export interface ChatTokenUsage {
11
+ readonly inputTokens?: number;
12
+ readonly outputTokens?: number;
13
+ readonly totalTokens?: number;
14
+ }
15
+ export interface ChatStepUsage {
16
+ readonly finishReason: string;
17
+ readonly usage?: ChatTokenUsage;
18
+ }
10
19
  export interface ChatInputRequirement {
11
20
  readonly name: string;
12
21
  readonly description: string;
@@ -25,6 +34,8 @@ export interface ChatCompletedResult {
25
34
  readonly sessionId: string;
26
35
  readonly output: string;
27
36
  readonly steps: ReadonlyArray<ChatStepSummary>;
37
+ readonly stepUsages?: ReadonlyArray<ChatStepUsage>;
38
+ readonly totalUsage?: ChatTokenUsage;
28
39
  }
29
40
  export interface ChatNeedsInputResult {
30
41
  readonly status: "needs_input";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/core",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,6 +14,34 @@
14
14
  ".": {
15
15
  "types": "./dist/cli/index.d.ts",
16
16
  "default": "./dist/cli/index.js"
17
+ },
18
+ "./mcp/*": {
19
+ "types": "./dist/mcp/*.d.ts",
20
+ "default": "./dist/mcp/*.js"
21
+ },
22
+ "./llm/*": {
23
+ "types": "./dist/llm/*.d.ts",
24
+ "default": "./dist/llm/*.js"
25
+ },
26
+ "./registry/*": {
27
+ "types": "./dist/registry/*.d.ts",
28
+ "default": "./dist/registry/*.js"
29
+ },
30
+ "./tool-runtime/*": {
31
+ "types": "./dist/tool-runtime/*.d.ts",
32
+ "default": "./dist/tool-runtime/*.js"
33
+ },
34
+ "./config/*": {
35
+ "types": "./dist/config/*.d.ts",
36
+ "default": "./dist/config/*.js"
37
+ },
38
+ "./types/*": {
39
+ "types": "./dist/types/*.d.ts",
40
+ "default": "./dist/types/*.js"
41
+ },
42
+ "./cli/utils/*": {
43
+ "types": "./dist/cli/utils/*.d.ts",
44
+ "default": "./dist/cli/utils/*.js"
17
45
  }
18
46
  },
19
47
  "bin": {
@@ -44,17 +72,18 @@
44
72
  "gray-matter": "^4.0.3",
45
73
  "ora": "^8.1.0",
46
74
  "yaml": "^2.7.0",
47
- "zod": "^3.25.76"
75
+ "zod": "^3.25.76",
76
+ "@roll-agent/runtime": "0.1.0"
48
77
  },
49
78
  "devDependencies": {
50
79
  "@types/node": "^22.0.0"
51
80
  },
52
81
  "scripts": {
53
- "dev": "node --experimental-strip-types src/cli/index.ts",
82
+ "dev": "node --experimental-strip-types --experimental-sqlite src/cli/index.ts",
54
83
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",
55
84
  "typecheck": "tsc --noEmit",
56
- "test": "node --experimental-strip-types --test \"src/**/*.test.ts\"",
57
- "test:e2e": "node --experimental-strip-types --test \"src/**/*.e2e.ts\"",
85
+ "test": "node --experimental-strip-types --experimental-sqlite --test \"src/**/*.test.ts\"",
86
+ "test:e2e": "node --experimental-strip-types --experimental-sqlite --test \"src/**/*.e2e.ts\"",
58
87
  "clean": "rm -rf dist"
59
88
  }
60
89
  }