@roll-agent/core 0.15.0 → 0.16.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.
Files changed (60) hide show
  1. package/dist/cli/chat/ink/live-region.js +1 -1
  2. package/dist/cli/chat/ink/state.d.ts +1 -0
  3. package/dist/cli/chat/ink/state.js +1 -1
  4. package/dist/cli/commands/agent-add.js +1 -1
  5. package/dist/cli/commands/agent-env-guidance.d.ts +2 -0
  6. package/dist/cli/commands/agent-env-guidance.js +1 -0
  7. package/dist/cli/commands/agent-install.d.ts +5 -0
  8. package/dist/cli/commands/agent-install.js +1 -1
  9. package/dist/cli/commands/agent-list.d.ts +5 -0
  10. package/dist/cli/commands/agent-list.js +1 -1
  11. package/dist/cli/commands/chat.d.ts +3 -0
  12. package/dist/cli/commands/chat.js +1 -1
  13. package/dist/cli/commands/config-guidance.d.ts +21 -0
  14. package/dist/cli/commands/config-guidance.js +1 -1
  15. package/dist/cli/commands/config-prompts.d.ts +6 -0
  16. package/dist/cli/commands/config-prompts.js +1 -1
  17. package/dist/cli/commands/config-setup.d.ts +6 -1
  18. package/dist/cli/commands/config-setup.js +1 -1
  19. package/dist/cli/commands/config.d.ts +1 -0
  20. package/dist/cli/commands/config.js +1 -1
  21. package/dist/cli/commands/setup.d.ts +38 -0
  22. package/dist/cli/commands/setup.js +1 -0
  23. package/dist/cli/commands/skills-install.d.ts +30 -0
  24. package/dist/cli/commands/skills-install.js +1 -0
  25. package/dist/cli/commands/skills.js +1 -1
  26. package/dist/cli/index.js +1 -1
  27. package/dist/cli/utils/catalog-status.d.ts +20 -0
  28. package/dist/cli/utils/catalog-status.js +1 -0
  29. package/dist/cli/utils/chat-renderer.d.ts +1 -0
  30. package/dist/cli/utils/chat-renderer.js +1 -1
  31. package/dist/cli/utils/git-source.d.ts +7 -0
  32. package/dist/cli/utils/git-source.js +1 -0
  33. package/dist/cli/utils/tool-format.d.ts +1 -0
  34. package/dist/cli/utils/tool-format.js +1 -1
  35. package/dist/config/defaults.js +1 -1
  36. package/dist/config/helpers.d.ts +17 -0
  37. package/dist/config/helpers.js +1 -1
  38. package/dist/config/key-codec.js +1 -1
  39. package/dist/config/loader.d.ts +2 -0
  40. package/dist/config/loader.js +1 -1
  41. package/dist/config/migration.d.ts +5 -1
  42. package/dist/config/migration.js +1 -1
  43. package/dist/config/runtime-env.d.ts +30 -30
  44. package/dist/config/schema.d.ts +250 -0
  45. package/dist/config/schema.js +1 -1
  46. package/dist/mcp/client-manager.js +1 -1
  47. package/dist/registry/catalog-discovery.d.ts +30 -0
  48. package/dist/registry/catalog-discovery.js +1 -0
  49. package/dist/registry/catalog.d.ts +42 -0
  50. package/dist/registry/catalog.js +1 -0
  51. package/dist/registry/discovery.js +1 -1
  52. package/dist/registry/index.d.ts +4 -0
  53. package/dist/registry/index.js +1 -1
  54. package/dist/registry/install.d.ts +69 -0
  55. package/dist/registry/install.js +1 -0
  56. package/dist/skills/install.d.ts +17 -0
  57. package/dist/skills/install.js +1 -0
  58. package/dist/skills/orchestrators.d.ts +39 -0
  59. package/dist/skills/orchestrators.js +1 -0
  60. package/package.json +2 -2
@@ -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}
@@ -9,6 +9,7 @@ export declare class ChatRenderer {
9
9
  private readonly confirm;
10
10
  private readonly contextWindow;
11
11
  private readonly spinners;
12
+ private readonly toolLabels;
12
13
  private compactionSpinner;
13
14
  private messageSpinner;
14
15
  private streaming;
@@ -1 +1 @@
1
- import s from"chalk";import{isCancel as e,select as t}from"@clack/prompts";import{createSpinner as n,log as o}from"./output.js";import{GLYPHS as i}from"./glyphs.js";import{computeUsageParts as a,formatUsageLine as r}from"./token-format.js";import{formatToolInput as p}from"./tool-format.js";export const clackConfirm=async s=>{const n=await t({message:s,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!e(n)&&"yes"===n};function c(s){const e=[`chat.${s.stage}`,s.message];return void 0!==s.elapsedMs&&e.push(`${String(s.elapsedMs)}ms`),void 0!==s.data&&e.push(JSON.stringify(s.data)),e.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(s,e){this.confirm=s,this.contextWindow=e}async handle(e,t){switch(e.type){case"debug":o.debug(c(e));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(e.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const t=n(`${s.cyan(`${e.agentName}.${e.toolName}`)} ${s.gray(p(e.input))}`);t.start(),this.spinners.set(e.toolCallId,t);break}case"tool-result":{const s=this.spinners.get(e.toolCallId);s&&(e.isError?s.fail():s.succeed(),this.spinners.delete(e.toolCallId));break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const s=e.reason?`(${e.reason})`:"";await this.confirm(`执行 ${e.agentName}.${e.toolName}${s}?`)?t.approve(e.approvalId):t.reject(e.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=n(s.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const t="auto"===e.reason?"自动压缩":"手动压缩",n=e.truncatedTools?`,精简 ${String(e.truncatedTools)} 个工具结果`:"",o=0!==e.removed||e.truncatedTools?`${i.compact} ${t}(${e.strategy}):移除 ${String(e.removed)} 条 → 保留 ${String(e.kept)} 条${n}`:`${i.compact} ${t}:无需压缩`;process.stderr.write(`${s.gray(o)}\n`);break}case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),o.error(e.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===e.text.length&&(e.totalUsage?.outputTokens??0)>0&&o.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),e.stoppedAtStepLimit&&o.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const t=r(a(e.totalUsage,e.sessionUsage,this.contextWindow,e.contextInputTokens));t&&process.stderr.write(`${s.gray(t)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=n(s.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
1
+ import t from"chalk";import{isCancel as e,select as s}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 c}from"./tool-format.js";export const clackConfirm=async t=>{const n=await s({message:t,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!e(n)&&"yes"===n};function l(t){const e=[`chat.${t.stage}`,t.message];return void 0!==t.elapsedMs&&e.push(`${String(t.elapsedMs)}ms`),void 0!==t.data&&e.push(JSON.stringify(t.data)),e.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;toolLabels=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(t,e){this.confirm=t,this.contextWindow=e}async handle(e,s){switch(e.type){case"debug":o.debug(l(e));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(e.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const s=`${e.agentName}.${e.toolName}`,o=n(`${t.cyan(s)} ${t.gray(p(e.input))}`);o.start(),this.spinners.set(e.toolCallId,o),this.toolLabels.set(e.toolCallId,s);break}case"tool-output-delta":{const s=this.spinners.get(e.toolCallId),n=this.toolLabels.get(e.toolCallId);if(s&&n){const o=e.delta.split("\n").map(t=>t.trim()).filter(t=>t.length>0).at(-1);if(o){const e=o.length>80?`${o.slice(0,79)}…`:o;s.text=`${t.cyan(n)} ${t.gray(e)}`}}break}case"tool-result":{const t=this.spinners.get(e.toolCallId);t&&(e.isError?t.fail():t.succeed(),this.spinners.delete(e.toolCallId)),this.toolLabels.delete(e.toolCallId);break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const t=e.reason?`(${e.reason})`:"",n=c(e.input),o=`执行 ${e.agentName}.${e.toolName}${t}?`;await this.confirm(n?`${o}\n${n}`:o)?s.approve(e.approvalId):s.reject(e.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 s="auto"===e.reason?"自动压缩":"手动压缩",n=e.truncatedTools?`,精简 ${String(e.truncatedTools)} 个工具结果`:"",o=0!==e.removed||e.truncatedTools?`${a.compact} ${s}(${e.strategy}):移除 ${String(e.removed)} 条 → 保留 ${String(e.kept)} 条${n}`:`${a.compact} ${s}:无需压缩`;process.stderr.write(`${t.gray(o)}\n`);break}case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),o.error(e.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===e.text.length&&(e.totalUsage?.outputTokens??0)>0&&o.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),e.stoppedAtStepLimit&&o.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const s=r(i(e.totalUsage,e.sessionUsage,this.contextWindow,e.contextInputTokens));s&&process.stderr.write(`${t.gray(s)}\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,2 @@
1
1
  export declare function formatToolInput(input: unknown): string;
2
+ export declare function formatApprovalDetails(input: unknown): string;
@@ -1 +1 @@
1
- import{redactToolArgsForLog as t}from"./output.js";export function formatToolInput(o){const n=JSON.stringify(t(o))??"";return n.length>80?`${n.slice(0,79)}…`:n}
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")}
@@ -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"];
@@ -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;
@@ -1 +1 @@
1
- const e=/\$\{[^}]+\}/;function n(e,n){return e?.[n]}export function getAgentEnvFromAgentsConfig(e,r){return t(n(e.env,r))}export function getAgentEnv(e,n){const r=getAgentEnvFromAgentsConfig(e.agents,n);return"browser-use-agent"!==n||0===Object.keys(e.browser.instances).length?r:{...r??{},BROWSER_INSTANCES_JSON:JSON.stringify(e.browser)}}export function inspectAgentEnvRequirements(e,s,i,o=process.env){if(!s)return;const u=t(n(i,e)),g=[...r(s.required,!0,u,o),...r(s.optional,!1,u,o)];return 0!==g.length?{items:g,missingRequired:g.filter(e=>e.required&&"missing"===e.source),processEnvOnlyRequired:g.filter(e=>e.required&&"process.env"===e.source)}:void 0}export function getMissingAgentEnvRuntimeIssues(e){return e?e.missingRequired.map(e=>({category:"env",code:"missing_required_env",name:e.name,message:`必填环境变量 ${e.name} 未配置`,...e.purpose?{purpose:e.purpose}:{},...e.example?{example:e.example}:{}})):[]}function r(e,n,r,t){return e?e.map(e=>{const s=r?.[e.name];if(void 0!==s&&s.length>0)return{...e,required:n,source:"agents.env"};const i=t[e.name];return"string"==typeof i&&i.length>0?{...e,required:n,source:"process.env"}:e.default?{...e,required:n,source:"default"}:{...e,required:n,source:"missing"}}):[]}function t(e){if(!e)return;const n=Object.entries(e).filter(([,e])=>s(e));return 0!==n.length?Object.fromEntries(n):void 0}function s(n){return"string"==typeof n&&n.length>0&&!e.test(n)}
1
+ 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)}
@@ -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}},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}}}},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"},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")}
@@ -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;
@@ -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 u}from"./key-codec.js";import{detectKnownConfigMigrations as l,formatConfigMigrationError as g}from"./migration.js";function d(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(d);if(p(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=d(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 t=o(n);const e=o("/");for(;t!==e;){for(const n of c){const e=o(t,n);if(r(e))return e}const n=o(t,"..");if(n===t)break;t=n}}function $(n){return"~"===n?t():n.startsWith("~/")||n.startsWith("~\\")?o(t(),n.slice(2)):n}function C(n){return{...n,agents:{...n.agents,dataDir:$(n.agents.dataDir)},runtime:{...n.runtime,threadsDir:$(n.runtime.threadsDir)},skills:{...n.skills,dirs:n.skills.dirs.map($)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:$(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:$(r.sessionsDir)}:{}}]))}}}const P={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 y(n,r,t={}){const o=parseConfigDocument(n,r),e=l(o,t);if(e.needsMigration)throw new Error(g(r,e));return o}export function validateConfigText(n,r){const t=y(n,r),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=D(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 C(i.data)}function j(n,r){const t=y(n,r,{scope:"agents"}),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.agents)?o.agents:{},s=D(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:$(a.data.dataDir)}}function E(n,r){const t=parseConfigDocument(n,r),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.install)?o.install:{},i=D(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:E(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:C(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:$(f.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:j(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:P.notFound,configPath:void 0};if(!r(o))return{status:P.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:P.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=l(i);if(s.needsMigration)return{status:P.needsMigration,configPath:o,raw:e,report:s};try{return{status:P.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:P.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function D(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]=D(r,e)}return t}
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}
@@ -7,6 +7,10 @@ declare const CONFIG_MIGRATION_ISSUE_CODES: {
7
7
  readonly unknownRouterKeys: "unknown-router-keys";
8
8
  readonly legacyCamelCaseAgentEnvKey: "legacy-camelcase-agent-env-key";
9
9
  readonly legacyAgentEnvKeyConflict: "legacy-agent-env-key-conflict";
10
+ readonly deprecatedRuntimeBashSection: "deprecated-runtime-bash-section";
11
+ readonly invalidRuntimeBashSection: "invalid-runtime-bash-section";
12
+ readonly invalidRuntimeShellSection: "invalid-runtime-shell-section";
13
+ readonly runtimeShellConflict: "runtime-shell-conflict";
10
14
  };
11
15
  type ConfigMigrationIssueCode = (typeof CONFIG_MIGRATION_ISSUE_CODES)[keyof typeof CONFIG_MIGRATION_ISSUE_CODES];
12
16
  export interface ConfigMigrationIssue {
@@ -29,7 +33,7 @@ export type ApplyKnownConfigMigrationsResult = {
29
33
  readonly changed: false;
30
34
  readonly issues: readonly ConfigMigrationIssue[];
31
35
  };
32
- export type ConfigMigrationScope = "llm" | "ask" | "agents";
36
+ export type ConfigMigrationScope = "llm" | "ask" | "agents" | "runtime";
33
37
  export declare function detectKnownConfigMigrations(document: Record<string, unknown>, options?: {
34
38
  readonly scope?: ConfigMigrationScope;
35
39
  }): ConfigMigrationReport;
@@ -1 +1 @@
1
- import{camelToKebab as e}from"./key-codec.js";const t={deprecatedRouterSection:"deprecated-router-section",invalidAskSection:"invalid-ask-section",invalidRouterSection:"invalid-router-section",routerAskConflict:"router-ask-conflict",duplicateEquivalentKeys:"duplicate-equivalent-keys",unknownRouterKeys:"unknown-router-keys",legacyCamelCaseAgentEnvKey:"legacy-camelcase-agent-env-key",legacyAgentEnvKeyConflict:"legacy-agent-env-key-conflict"},s={llmModel:{routerKeys:["llm-model","llmModel"],askKeys:["llm-model","llmModel"],targetAskKey:"llm-model"},confirmThreshold:{routerKeys:["confirm-threshold","confirmThreshold"],askKeys:["confirm-threshold","confirmThreshold"],targetAskKey:"confirm-threshold"}},n=[...s.llmModel.routerKeys,...s.confirmThreshold.routerKeys,"mode"],o=new Set([t.invalidAskSection,t.invalidRouterSection,t.routerAskConflict,t.unknownRouterKeys,t.legacyAgentEnvKeyConflict]);function r(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function u(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function i(e,t){return{code:e,message:t}}function c(e,t){return t.filter(t=>u(e,t))}function a(e,s,n){const o=c(s,n);if(0===o.length)return{ok:!0,key:void 0,value:void 0};const[r]=o,u=r?s[r]:void 0;return o.some(e=>s[e]!==u)?{ok:!1,issue:i(t.duplicateEquivalentKeys,`\`${e}\` 同时包含等价键 ${o.join(", ")} 且值不一致,请手动处理。`)}:{ok:!0,key:r,value:u}}function l(e){if(!u(e,"router"))return{matches:!1,canAutoMigrate:!1,issues:[]};const c=[i(t.deprecatedRouterSection,"`router` 配置段已废弃,请改用 `ask`。"),i(t.deprecatedRouterSection,"将 `router.llm-model` 迁移为 `ask.llm-model`。"),i(t.deprecatedRouterSection,"将 `router.confirm-threshold` 迁移为 `ask.confirm-threshold`。"),i(t.deprecatedRouterSection,"删除 `router.mode`;命令本身已决定策略(`run` / `ask` / `chat`)。")],l=e.router;if(!r(l))return c.push(i(t.invalidRouterSection,"`router` 配置段不是对象,无法自动迁移,请手动修复。")),{matches:!0,canAutoMigrate:!1,issues:c};const d=Object.keys(l).filter(e=>!n.includes(e));d.length>0&&c.push(i(t.unknownRouterKeys,`\`router\` 包含无法自动迁移的未知键:${d.join(", ")}。`));const g=e.ask;if(void 0===g||r(g)||c.push(i(t.invalidAskSection,"`ask` 配置段不是对象,无法自动迁移,请手动修复。")),r(g))for(const e of Object.keys(s)){const n=s[e],o=a("router",l,n.routerKeys),r=a("ask",g,n.askKeys);o.ok?r.ok?o.key&&r.key&&r.value!==o.value&&c.push(i(t.routerAskConflict,`\`router.${n.targetAskKey}\` 与 \`ask.${n.targetAskKey}\` 同时存在且值冲突,请手动处理。`)):c.push(r.issue):c.push(o.issue)}return{matches:!0,canAutoMigrate:!c.some(e=>o.has(e.code)),issues:c}}function d(e){const t=l(e);if(!t.matches)return{ok:!0,changed:!1,document:structuredClone(e),issues:[],summary:[]};if(!t.canAutoMigrate)return{ok:!1,changed:!1,issues:t.issues};const n=structuredClone(e),o=n.router;if(!r(o))return{ok:!1,changed:!1,issues:t.issues};const i=n.ask,c=r(i)?i:{};let d=!1;const g=[];for(const e of Object.keys(s)){const t=s[e],n=a("router",o,t.routerKeys);if(!n.ok)return{ok:!1,changed:!1,issues:[n.issue]};if(!n.key)continue;const r=a("ask",c,t.askKeys);if(!r.ok)return{ok:!1,changed:!1,issues:[r.issue]};r.key?g.push(`移除已废弃的 \`router.${t.targetAskKey}\``):(c[t.targetAskKey]=n.value,d=!0,g.push(`将 \`router.${t.targetAskKey}\` 迁移到 \`ask.${t.targetAskKey}\``));for(const e of t.routerKeys)u(o,e)&&delete o[e]}return u(o,"mode")&&(delete o.mode,g.push("删除已废弃的 `router.mode`")),d&&(n.ask=c),0===Object.keys(o).length&&(delete n.router,g.push("删除空的 `router` 配置段")),{ok:!0,changed:g.length>0,document:n,issues:t.issues,summary:g}}const g=/^[a-z0-9]+(-[a-z0-9]+)*$/;function f(e){return!g.test(e)}function k(t){const s=e(t).toLowerCase();return g.test(s)?s:void 0}function y(e){const s=e.agents;if(!r(s))return{matches:!1,canAutoMigrate:!1,issues:[]};const n=s.env;if(!r(n))return{matches:!1,canAutoMigrate:!1,issues:[]};const o=Object.keys(n).filter(f);if(0===o.length)return{matches:!1,canAutoMigrate:!1,issues:[]};const c=[];let a=!1;for(const e of o){const s=k(e);void 0!==s?u(n,s)?(a=!0,c.push(i(t.legacyAgentEnvKeyConflict,`\`agents.env\` 下同时存在 \`${e}\` 与 \`${s}\`,无法自动合并,请手动处理。`))):c.push(i(t.legacyCamelCaseAgentEnvKey,`\`agents.env.${e}\` 应使用 kebab-case(\`${s}\`)。`)):(a=!0,c.push(i(t.legacyAgentEnvKeyConflict,`\`agents.env.${e}\` 命名不符合 kebab-case 规范,无法自动迁移,请手动重命名。`)))}return{matches:!0,canAutoMigrate:!a,issues:c}}function h(e){const t=y(e);if(!t.matches)return{ok:!0,changed:!1,document:structuredClone(e),issues:[],summary:[]};if(!t.canAutoMigrate)return{ok:!1,changed:!1,issues:t.issues};const s=structuredClone(e),n=s.agents;if(!r(n))return{ok:!1,changed:!1,issues:t.issues};const o=n.env;if(!r(o))return{ok:!1,changed:!1,issues:t.issues};const u=[];for(const e of Object.keys(o).filter(f)){const t=k(e);void 0!==t&&(o[t]=o[e],delete o[e],u.push(`将 \`agents.env.${e}\` 重命名为 \`agents.env.${t}\``))}return{ok:!0,changed:u.length>0,document:s,issues:t.issues,summary:u}}const m=[{id:"router-to-ask",scopes:new Set(["ask"]),inspect:l,apply:d},{id:"legacy-agent-env-keys",scopes:new Set(["agents"]),inspect:y,apply:h}];export function detectKnownConfigMigrations(e,t={}){const{scope:s}=t,n=(void 0!==s?m.filter(e=>e.scopes.has(s)):m).map(t=>t.inspect(e)).filter(e=>e.matches);return 0===n.length?{needsMigration:!1,canAutoMigrate:!1,issues:[]}:{needsMigration:!0,canAutoMigrate:n.every(e=>e.canAutoMigrate),issues:n.flatMap(e=>e.issues)}}export function applyKnownConfigMigrations(e){let t=structuredClone(e);const s=[],n=[];let o=!1;for(const e of m){const r=e.apply(t);if(!r.ok)return{ok:!1,changed:!1,issues:r.issues};t=r.document,r.changed&&(o=!0,s.push(...r.summary)),n.push(...r.issues)}return{ok:!0,changed:o,document:t,summary:s,issues:n}}export function formatConfigMigrationError(e,t){const s=[`Config validation failed (${e}):`];for(const e of t.issues)s.push(` - ${e.message}`);return t.canAutoMigrate?s.push(" - 可运行 `roll config migrate` 自动迁移当前配置。"):t.needsMigration&&s.push(" - 检测到配置迁移冲突,请手动处理后再重试。"),s.join("\n")}
1
+ import{camelToKebab as e,CONFIG_KEY_CODEC as t,kebabToCamel as s}from"./key-codec.js";const n={deprecatedRouterSection:"deprecated-router-section",invalidAskSection:"invalid-ask-section",invalidRouterSection:"invalid-router-section",routerAskConflict:"router-ask-conflict",duplicateEquivalentKeys:"duplicate-equivalent-keys",unknownRouterKeys:"unknown-router-keys",legacyCamelCaseAgentEnvKey:"legacy-camelcase-agent-env-key",legacyAgentEnvKeyConflict:"legacy-agent-env-key-conflict",deprecatedRuntimeBashSection:"deprecated-runtime-bash-section",invalidRuntimeBashSection:"invalid-runtime-bash-section",invalidRuntimeShellSection:"invalid-runtime-shell-section",runtimeShellConflict:"runtime-shell-conflict"},o={llmModel:{routerKeys:["llm-model","llmModel"],askKeys:["llm-model","llmModel"],targetAskKey:"llm-model"},confirmThreshold:{routerKeys:["confirm-threshold","confirmThreshold"],askKeys:["confirm-threshold","confirmThreshold"],targetAskKey:"confirm-threshold"}},r=[...o.llmModel.routerKeys,...o.confirmThreshold.routerKeys,"mode"],i=new Set([n.invalidAskSection,n.invalidRouterSection,n.routerAskConflict,n.unknownRouterKeys,n.legacyAgentEnvKeyConflict,n.invalidRuntimeBashSection,n.invalidRuntimeShellSection,n.runtimeShellConflict]);function u(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function c(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function a(e,t){return{code:e,message:t}}function l(e,t){if(Object.is(e,t))return!0;if(!u(e)||!u(t))return!1;const s=Object.keys(e).sort(),n=Object.keys(t).sort();return s.length===n.length&&s.every((s,o)=>s===n[o]&&l(e[s],t[s]))}function d(){if("object"!==t.kind)throw new Error("runtime shell key codec is unavailable");const e=t.fields.runtime;if("object"!==e?.kind)throw new Error("runtime shell key codec is unavailable");const s=e.fields.shell;if("object"!==s?.kind)throw new Error("runtime shell key codec is unavailable");return s}function h(e,t,n){if(!u(e)||"leaf"===t.kind)return{ok:!0,value:e};const o={};for(const[r,i]of Object.entries(e)){const e="object"===t.kind?s(r):r,u="object"===t.kind?t.fields[e]:t.value,a=void 0===u?{ok:!0,value:i}:h(i,u,[...n,e]);if(!a.ok)return a;if(c(o,e)){if(!l(o[e],a.value))return{ok:!1,conflictPath:[...n,e].join(".")}}else o[e]=a.value}return{ok:!0,value:o}}function f(e,t){const s=h(e,d(),[t]);return s.ok?u(s.value)?{ok:!0,value:s.value}:{ok:!0,value:e}:s}function m(e,t){if(!u(e)||"leaf"===t.kind)return e;const n={},o=new Set;for(const[r,i]of Object.entries(e)){const e="object"===t.kind?s(r):r;if(o.has(e))continue;o.add(e);const u="object"===t.kind?t.fields[e]:t.value;n[r]=void 0===u?i:m(i,u)}return n}function k(e,t){return t.filter(t=>c(e,t))}function g(e,t,s){const o=k(t,s);if(0===o.length)return{ok:!0,key:void 0,value:void 0};const[r]=o,i=r?t[r]:void 0;return o.some(e=>t[e]!==i)?{ok:!1,issue:a(n.duplicateEquivalentKeys,`\`${e}\` 同时包含等价键 ${o.join(", ")} 且值不一致,请手动处理。`)}:{ok:!0,key:r,value:i}}function y(e){if(!c(e,"router"))return{matches:!1,canAutoMigrate:!1,issues:[]};const t=[a(n.deprecatedRouterSection,"`router` 配置段已废弃,请改用 `ask`。"),a(n.deprecatedRouterSection,"将 `router.llm-model` 迁移为 `ask.llm-model`。"),a(n.deprecatedRouterSection,"将 `router.confirm-threshold` 迁移为 `ask.confirm-threshold`。"),a(n.deprecatedRouterSection,"删除 `router.mode`;命令本身已决定策略(`run` / `ask` / `chat`)。")],s=e.router;if(!u(s))return t.push(a(n.invalidRouterSection,"`router` 配置段不是对象,无法自动迁移,请手动修复。")),{matches:!0,canAutoMigrate:!1,issues:t};const l=Object.keys(s).filter(e=>!r.includes(e));l.length>0&&t.push(a(n.unknownRouterKeys,`\`router\` 包含无法自动迁移的未知键:${l.join(", ")}。`));const d=e.ask;if(void 0===d||u(d)||t.push(a(n.invalidAskSection,"`ask` 配置段不是对象,无法自动迁移,请手动修复。")),u(d))for(const e of Object.keys(o)){const r=o[e],i=g("router",s,r.routerKeys),u=g("ask",d,r.askKeys);i.ok?u.ok?i.key&&u.key&&u.value!==i.value&&t.push(a(n.routerAskConflict,`\`router.${r.targetAskKey}\` 与 \`ask.${r.targetAskKey}\` 同时存在且值冲突,请手动处理。`)):t.push(u.issue):t.push(i.issue)}return{matches:!0,canAutoMigrate:!t.some(e=>i.has(e.code)),issues:t}}function v(e){const t=y(e);if(!t.matches)return{ok:!0,changed:!1,document:structuredClone(e),issues:[],summary:[]};if(!t.canAutoMigrate)return{ok:!1,changed:!1,issues:t.issues};const s=structuredClone(e),n=s.router;if(!u(n))return{ok:!1,changed:!1,issues:t.issues};const r=s.ask,i=u(r)?r:{};let a=!1;const l=[];for(const e of Object.keys(o)){const t=o[e],s=g("router",n,t.routerKeys);if(!s.ok)return{ok:!1,changed:!1,issues:[s.issue]};if(!s.key)continue;const r=g("ask",i,t.askKeys);if(!r.ok)return{ok:!1,changed:!1,issues:[r.issue]};r.key?l.push(`移除已废弃的 \`router.${t.targetAskKey}\``):(i[t.targetAskKey]=s.value,a=!0,l.push(`将 \`router.${t.targetAskKey}\` 迁移到 \`ask.${t.targetAskKey}\``));for(const e of t.routerKeys)c(n,e)&&delete n[e]}return c(n,"mode")&&(delete n.mode,l.push("删除已废弃的 `router.mode`")),a&&(s.ask=i),0===Object.keys(n).length&&(delete s.router,l.push("删除空的 `router` 配置段")),{ok:!0,changed:l.length>0,document:s,issues:t.issues,summary:l}}const p=/^[a-z0-9]+(-[a-z0-9]+)*$/;function b(e){return!p.test(e)}function A(t){const s=e(t).toLowerCase();return p.test(s)?s:void 0}function K(e){const t=e.agents;if(!u(t))return{matches:!1,canAutoMigrate:!1,issues:[]};const s=t.env;if(!u(s))return{matches:!1,canAutoMigrate:!1,issues:[]};const o=Object.keys(s).filter(b);if(0===o.length)return{matches:!1,canAutoMigrate:!1,issues:[]};const r=[];let i=!1;for(const e of o){const t=A(e);void 0!==t?c(s,t)?(i=!0,r.push(a(n.legacyAgentEnvKeyConflict,`\`agents.env\` 下同时存在 \`${e}\` 与 \`${t}\`,无法自动合并,请手动处理。`))):r.push(a(n.legacyCamelCaseAgentEnvKey,`\`agents.env.${e}\` 应使用 kebab-case(\`${t}\`)。`)):(i=!0,r.push(a(n.legacyAgentEnvKeyConflict,`\`agents.env.${e}\` 命名不符合 kebab-case 规范,无法自动迁移,请手动重命名。`)))}return{matches:!0,canAutoMigrate:!i,issues:r}}function S(e){const t=K(e);if(!t.matches)return{ok:!0,changed:!1,document:structuredClone(e),issues:[],summary:[]};if(!t.canAutoMigrate)return{ok:!1,changed:!1,issues:t.issues};const s=structuredClone(e),n=s.agents;if(!u(n))return{ok:!1,changed:!1,issues:t.issues};const o=n.env;if(!u(o))return{ok:!1,changed:!1,issues:t.issues};const r=[];for(const e of Object.keys(o).filter(b)){const t=A(e);void 0!==t&&(o[t]=o[e],delete o[e],r.push(`将 \`agents.env.${e}\` 重命名为 \`agents.env.${t}\``))}return{ok:!0,changed:r.length>0,document:s,issues:t.issues,summary:r}}function M(e){const t=e.runtime;if(!u(t))return{matches:!1,canAutoMigrate:!1,issues:[]};if(!c(t,"bash"))return{matches:!1,canAutoMigrate:!1,issues:[]};const s=[a(n.deprecatedRuntimeBashSection,"`runtime.bash` 配置段已废弃,请改用 `runtime.shell`。")],o=t.bash;u(o)||s.push(a(n.invalidRuntimeBashSection,"`runtime.bash` 配置段不是对象,无法自动迁移,请手动修复。"));const r=t.shell;void 0===r||u(r)||s.push(a(n.invalidRuntimeShellSection,"`runtime.shell` 配置段不是对象,无法自动迁移,请手动修复。"));const d=u(o)?f(o,"runtime.bash"):void 0,h=u(r)?f(r,"runtime.shell"):void 0;for(const e of[d,h])void 0===e||e.ok||s.push(a(n.runtimeShellConflict,`\`${e.conflictPath}\` 同时包含等价键且值冲突,请手动处理。`));!0!==d?.ok||!0!==h?.ok||l(d.value,h.value)||s.push(a(n.runtimeShellConflict,"`runtime.bash` 与 `runtime.shell` 同时存在且值冲突,请手动处理。"));return{matches:!0,canAutoMigrate:!s.some(e=>i.has(e.code)),issues:s}}function C(e){const t=M(e);if(!t.matches)return{ok:!0,changed:!1,document:structuredClone(e),issues:[],summary:[]};if(!t.canAutoMigrate)return{ok:!1,changed:!1,issues:t.issues};const s=structuredClone(e),n=s.runtime;if(!u(n))return{ok:!1,changed:!1,issues:t.issues};const o=n.bash;if(!u(o))return{ok:!1,changed:!1,issues:t.issues};const r=[];return c(n,"shell")?r.push("删除已废弃的 `runtime.bash`"):(n.shell=m(o,d()),r.push("将 `runtime.bash` 迁移为 `runtime.shell`")),delete n.bash,{ok:!0,changed:!0,document:s,issues:t.issues,summary:r}}const j=[{id:"router-to-ask",scopes:new Set(["ask"]),inspect:y,apply:v},{id:"legacy-agent-env-keys",scopes:new Set(["agents"]),inspect:K,apply:S},{id:"runtime-bash-to-shell",scopes:new Set(["runtime"]),inspect:M,apply:C}];export function detectKnownConfigMigrations(e,t={}){const{scope:s}=t,n=(void 0!==s?j.filter(e=>e.scopes.has(s)):j).map(t=>t.inspect(e)).filter(e=>e.matches);return 0===n.length?{needsMigration:!1,canAutoMigrate:!1,issues:[]}:{needsMigration:!0,canAutoMigrate:n.every(e=>e.canAutoMigrate),issues:n.flatMap(e=>e.issues)}}export function applyKnownConfigMigrations(e){let t=structuredClone(e);const s=[],n=[];let o=!1;for(const e of j){const r=e.apply(t);if(!r.ok)return{ok:!1,changed:!1,issues:r.issues};t=r.document,r.changed&&(o=!0,s.push(...r.summary)),n.push(...r.issues)}return{ok:!0,changed:o,document:t,summary:s,issues:n}}export function formatConfigMigrationError(e,t){const s=[`Config validation failed (${e}):`];for(const e of t.issues)s.push(` - ${e.message}`);return t.canAutoMigrate?s.push(" - 可运行 `roll config migrate` 自动迁移当前配置。"):t.needsMigration&&s.push(" - 检测到配置迁移冲突,请手动处理后再重试。"),s.join("\n")}
@@ -107,17 +107,17 @@ export declare const BrowserInstanceStatusDiagnosticSchema: z.ZodObject<{
107
107
  }, "strip", z.ZodTypeAny, {
108
108
  mode: "managed-cdp" | "remote-cdp" | "existing-session";
109
109
  id: string;
110
+ profile: {
111
+ exists: boolean;
112
+ writable: boolean;
113
+ userDataDir?: string | undefined;
114
+ };
110
115
  cdp: {
111
116
  endpoint: string;
112
117
  versionReachable: boolean;
113
118
  listReachable: boolean;
114
119
  port?: number | undefined;
115
120
  };
116
- profile: {
117
- exists: boolean;
118
- writable: boolean;
119
- userDataDir?: string | undefined;
120
- };
121
121
  tracking: {
122
122
  source: "instance" | "missing" | "default-env";
123
123
  agentIdFingerprint?: string | undefined;
@@ -126,17 +126,17 @@ export declare const BrowserInstanceStatusDiagnosticSchema: z.ZodObject<{
126
126
  }, {
127
127
  mode: "managed-cdp" | "remote-cdp" | "existing-session";
128
128
  id: string;
129
+ profile: {
130
+ exists: boolean;
131
+ writable: boolean;
132
+ userDataDir?: string | undefined;
133
+ };
129
134
  cdp: {
130
135
  endpoint: string;
131
136
  versionReachable: boolean;
132
137
  listReachable: boolean;
133
138
  port?: number | undefined;
134
139
  };
135
- profile: {
136
- exists: boolean;
137
- writable: boolean;
138
- userDataDir?: string | undefined;
139
- };
140
140
  tracking: {
141
141
  source: "instance" | "missing" | "default-env";
142
142
  agentIdFingerprint?: string | undefined;
@@ -251,17 +251,17 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
251
251
  }, "strip", z.ZodTypeAny, {
252
252
  mode: "managed-cdp" | "remote-cdp" | "existing-session";
253
253
  id: string;
254
+ profile: {
255
+ exists: boolean;
256
+ writable: boolean;
257
+ userDataDir?: string | undefined;
258
+ };
254
259
  cdp: {
255
260
  endpoint: string;
256
261
  versionReachable: boolean;
257
262
  listReachable: boolean;
258
263
  port?: number | undefined;
259
264
  };
260
- profile: {
261
- exists: boolean;
262
- writable: boolean;
263
- userDataDir?: string | undefined;
264
- };
265
265
  tracking: {
266
266
  source: "instance" | "missing" | "default-env";
267
267
  agentIdFingerprint?: string | undefined;
@@ -270,17 +270,17 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
270
270
  }, {
271
271
  mode: "managed-cdp" | "remote-cdp" | "existing-session";
272
272
  id: string;
273
+ profile: {
274
+ exists: boolean;
275
+ writable: boolean;
276
+ userDataDir?: string | undefined;
277
+ };
273
278
  cdp: {
274
279
  endpoint: string;
275
280
  versionReachable: boolean;
276
281
  listReachable: boolean;
277
282
  port?: number | undefined;
278
283
  };
279
- profile: {
280
- exists: boolean;
281
- writable: boolean;
282
- userDataDir?: string | undefined;
283
- };
284
284
  tracking: {
285
285
  source: "instance" | "missing" | "default-env";
286
286
  agentIdFingerprint?: string | undefined;
@@ -295,17 +295,17 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
295
295
  instances?: {
296
296
  mode: "managed-cdp" | "remote-cdp" | "existing-session";
297
297
  id: string;
298
+ profile: {
299
+ exists: boolean;
300
+ writable: boolean;
301
+ userDataDir?: string | undefined;
302
+ };
298
303
  cdp: {
299
304
  endpoint: string;
300
305
  versionReachable: boolean;
301
306
  listReachable: boolean;
302
307
  port?: number | undefined;
303
308
  };
304
- profile: {
305
- exists: boolean;
306
- writable: boolean;
307
- userDataDir?: string | undefined;
308
- };
309
309
  tracking: {
310
310
  source: "instance" | "missing" | "default-env";
311
311
  agentIdFingerprint?: string | undefined;
@@ -339,17 +339,17 @@ export declare const AgentRuntimeEnvDiagnosticPayloadSchema: z.ZodObject<{
339
339
  instances?: {
340
340
  mode: "managed-cdp" | "remote-cdp" | "existing-session";
341
341
  id: string;
342
+ profile: {
343
+ exists: boolean;
344
+ writable: boolean;
345
+ userDataDir?: string | undefined;
346
+ };
342
347
  cdp: {
343
348
  endpoint: string;
344
349
  versionReachable: boolean;
345
350
  listReachable: boolean;
346
351
  port?: number | undefined;
347
352
  };
348
- profile: {
349
- exists: boolean;
350
- writable: boolean;
351
- userDataDir?: string | undefined;
352
- };
353
353
  tracking: {
354
354
  source: "instance" | "missing" | "default-env";
355
355
  agentIdFingerprint?: string | undefined;