@roll-agent/core 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/chat/banner.d.ts +21 -0
- package/dist/cli/chat/banner.js +1 -0
- package/dist/cli/chat/ink/app.d.ts +2 -0
- package/dist/cli/chat/ink/app.js +1 -1
- package/dist/cli/chat/ink/commands.d.ts +28 -0
- package/dist/cli/chat/ink/commands.js +1 -1
- package/dist/cli/chat/ink/confirm-select.js +1 -1
- package/dist/cli/chat/ink/history-from-messages.js +1 -1
- package/dist/cli/chat/ink/history-item.js +1 -1
- package/dist/cli/chat/ink/live-region.js +1 -1
- package/dist/cli/chat/ink/run-ink-repl.d.ts +3 -0
- package/dist/cli/chat/ink/run-ink-repl.js +1 -1
- package/dist/cli/chat/ink/slash-popup.d.ts +2 -2
- package/dist/cli/chat/ink/slash-popup.js +1 -1
- package/dist/cli/chat/ink/state.d.ts +5 -0
- package/dist/cli/chat/ink/state.js +1 -1
- package/dist/cli/chat/ink/status-line.js +1 -1
- package/dist/cli/chat/ink/text-prompt.d.ts +1 -0
- package/dist/cli/chat/ink/text-prompt.js +1 -1
- package/dist/cli/chat/ink/thinking.js +1 -1
- package/dist/cli/chat/ink/use-session.d.ts +1 -1
- package/dist/cli/chat/ink/use-session.js +1 -1
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/config-guidance.d.ts +6 -0
- package/dist/cli/commands/config-guidance.js +1 -1
- package/dist/cli/commands/skills-utils.d.ts +4 -7
- package/dist/cli/commands/skills-utils.js +1 -1
- package/dist/cli/utils/chat-renderer.js +1 -1
- package/dist/cli/utils/glyphs.d.ts +9 -0
- package/dist/cli/utils/glyphs.js +1 -0
- package/dist/config/defaults.js +1 -1
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +23 -0
- package/dist/config/schema.js +1 -1
- package/dist/skills/documents.d.ts +8 -0
- package/dist/skills/documents.js +1 -0
- package/dist/skills/library.d.ts +31 -0
- package/dist/skills/library.js +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface BannerInfo {
|
|
2
|
+
readonly version: string;
|
|
3
|
+
readonly model: string;
|
|
4
|
+
readonly agentCount: number;
|
|
5
|
+
readonly skillCount: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BannerSpan {
|
|
8
|
+
readonly text: string;
|
|
9
|
+
readonly color?: string;
|
|
10
|
+
readonly dim?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface BannerLine {
|
|
13
|
+
readonly spans: readonly BannerSpan[];
|
|
14
|
+
}
|
|
15
|
+
export interface BuildBannerOptions {
|
|
16
|
+
readonly hints?: string;
|
|
17
|
+
readonly unicode?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function bannerTextLine(text: string, style?: Omit<BannerSpan, "text">): BannerLine;
|
|
20
|
+
export declare function buildBannerLines(info: BannerInfo, width: number, options?: BuildBannerOptions): readonly BannerLine[];
|
|
21
|
+
export declare function renderBannerText(lines: readonly BannerLine[]): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import n from"chalk";import t from"is-unicode-supported";const e="Roll Agent",_="#e879f9",o=["██████╗ ██████╗ ██╗ ██╗","██╔══██╗██╔═══██╗██║ ██║","██████╔╝██║ ██║██║ ██║","██╔══██╗██║ ██║██║ ██║","██║ ██║╚██████╔╝███████╗███████╗","╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝"],r=[" ____ ____ __ __"," / __ \\/ __ \\/ / / /"," / /_/ / / / / / / /"," / _, _/ /_/ / /___/ /___","/_/ |_|\\____/_____/_____/"],i=["#22d3ee","#4ac1f0","#71aff2","#999df5","#c08bf7","#e879f9"],s=["#22d3ee","#54bdf1","#85a6f4","#b790f6","#e879f9"],u=36,f=28;export function bannerTextLine(n,t={}){return{spans:[{text:n,...t}]}}function a(n,t){return n.map((n,e)=>({spans:[{text:n,color:t[e]??t[t.length-1]??_}]}))}function c(n,t){return t&&n>=u?a(o,i):n>=f?a(r,s):[]}function l(n){const t=[`v${n.version}`,n.model];return n.agentCount>0&&t.push(`${String(n.agentCount)} agents`),n.skillCount>0&&t.push(`${String(n.skillCount)} skills`),{spans:[{text:e,color:_},{text:` ${t.join(" · ")}`,dim:!0}]}}export function buildBannerLines(n,e,_={}){const o=c(e,_.unicode??t()),r=[...o,...o.length>0?[bannerTextLine(" ")]:[],l(n)];return void 0!==_.hints&&r.push(bannerTextLine(_.hints,{dim:!0})),r}function p(t){let e=n;return void 0!==t.color&&(e=e.hex(t.color)),!0===t.dim&&(e=e.dim),e(t.text)}export function renderBannerText(n){return n.map(n=>n.spans.map(p).join("")).join("\n")}
|
|
@@ -2,12 +2,14 @@ import type { ReactElement } from "react";
|
|
|
2
2
|
import type { AgentSession } from "@roll-agent/runtime";
|
|
3
3
|
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
4
4
|
import type { HistoryItem } from "./state.ts";
|
|
5
|
+
import { type SlashSkillSummary } from "./commands.ts";
|
|
5
6
|
export interface ChatAppProps {
|
|
6
7
|
readonly session: AgentSession;
|
|
7
8
|
readonly model: string;
|
|
8
9
|
readonly contextWindow: number | undefined;
|
|
9
10
|
readonly initialHistory?: readonly HistoryItem[];
|
|
10
11
|
readonly initialThinkingLevel?: ThinkingLevel;
|
|
12
|
+
readonly availableSkills?: readonly SlashSkillSummary[];
|
|
11
13
|
readonly onThinkingChange?: (level: ThinkingLevel) => void;
|
|
12
14
|
readonly onUserSubmit: (text: string) => void;
|
|
13
15
|
readonly onExit: () => void;
|
package/dist/cli/chat/ink/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as i}from"node:crypto";import{createElement as t,useMemo as n,useState as o}from"react";import{Box as e,Static as s,useInput as r}from"ink";import{useSession as m}from"./use-session.js";import{HistoryItemView as a}from"./history-item.js";import{LiveRegion as l}from"./live-region.js";import{StatusLine as d}from"./status-line.js";import{TextPrompt as f}from"./text-prompt.js";import{ConfirmSelect as p}from"./confirm-select.js";import{SlashPopup as h}from"./slash-popup.js";import{buildSkillInvocationPrompt as u,buildSkillListLines as c,filterSlashEntries as g,parseSkillInvocation as v,SLASH_COMMANDS as k}from"./commands.js";import{bannerTextLine as j}from"../banner.js";import{cycleThinking as x}from"./thinking.js";function C(){return k.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(y){const{session:L,model:b,contextWindow:M,onUserSubmit:T,onExit:A}=y,$=y.availableSkills??[],{state:S,submit:w,compact:H,resolveConfirm:W,setDraft:D,setThinking:E,setAutoMode:P,toggleAutoMode:R,commitHistory:U}=m(L,{model:b,contextWindow:M,...y.initialHistory?{initialHistory:y.initialHistory}:{},...y.initialThinkingLevel?{initialThinkingLevel:y.initialThinkingLevel}:{},...y.onThinkingChange?{onThinkingChange:y.onThinkingChange}:{}}),q=n(()=>[...S.history],[S.history]),[z,B]=o(0),F="idle"===S.phase&&S.draft.startsWith("/"),G=F&&!0===S.draft.split(/\s+/).at(-1)?.startsWith("/"),I=G?g(S.draft,$):[],J=Math.max(I.length-1,0),K=Math.min(z,J);r((i,t)=>{t.tab&&t.shift?R():t.meta&&"."===i?E(x(S.status.thinkingLevel,1)):t.meta&&","===i&&E(x(S.status.thinkingLevel,-1))});const N=(i,t)=>{const n=i.trim();0!==n.length?(T(n),w(n,t)):D("")},O="confirm"===S.phase&&void 0!==S.pendingConfirm?t(p,{prompt:S.pendingConfirm.prompt,args:S.pendingConfirm.args,onDecide:W}):t(f,{value:S.draft,disabled:"idle"!==S.phase,slashActive:F,slashPopupActive:G,autoApprove:S.status.autoApprove,onChange:D,onSubmit:N,onSlashMove:i=>{B(t=>Math.min(Math.max(Math.min(t,J)+i,0),J))},onSlashComplete:()=>{const i=I[K];if(i){const t=S.draft.split(/\s+/);t[t.length-1]=i.name,D(`${t.join(" ")} `),B(0)}},onSlashRun:()=>{const t=S.draft.trim().split(/\s+/,1)[0]??"",n=k.some(i=>i.name===t),o=I[K];if(!n&&"skill"===o?.kind){const i=S.draft.split(/\s+/);return i[i.length-1]=o.name,D(`${i.join(" ")} `),void B(0)}(t=>{D("");const n=t.trim(),o=n.split(/\s+/),e=o[0]??"",s=(o[1]??"").toLowerCase(),r=S.status.thinkingLevel;if("/compact"===e)return void H();if("/think"===e)return void E("off"===s?"off":"on"===s?"off"===r?"medium":r:"off"===r?"medium":"off");if("/effort"===e)return void("low"===s||"medium"===s||"high"===s?E(s):U({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===s?P(!0):"off"===s?P(!1):R());if("/skills"===e){const t=(process.stdout.columns||80)-2,[n,...o]=c($,t);return void U({kind:"banner",id:i(),lines:[j(n??""),...o.map(i=>j(i,{dim:!0}))]})}if("/help"===e)return void U({kind:"notice",id:i(),text:C()});if("/exit"===e)return void A();const m=v(n,$);if(m)return 0===m.prompt.length?void D(`${m.skills.map(i=>`/${i.name}`).join(" ")} `):void N(n,u(m));U({kind:"notice",id:i(),text:`未知命令 ${e}`})})(n?S.draft:o?.name??S.draft)}});return t(e,{flexDirection:"column"},t(s,{items:q,children:i=>{const n="user"===i.kind||"assistant"===i.kind,o="tool"===i.kind||"denied"===i.kind;return t(e,{key:i.id,marginTop:n?1:0,marginLeft:o?3:1},t(a,{item:i}))}}),t(e,{marginLeft:1},t(l,{live:S.live})),t(d,{status:S.status}),F?t(h,{matches:I,selected:K}):null,O)}
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
export interface SlashCommand {
|
|
2
|
+
readonly kind: "command";
|
|
2
3
|
readonly name: string;
|
|
3
4
|
readonly description: string;
|
|
4
5
|
}
|
|
6
|
+
export interface SlashSkillSummary {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly source: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SlashSkillEntry {
|
|
12
|
+
readonly kind: "skill";
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly description: string;
|
|
15
|
+
readonly skillName: string;
|
|
16
|
+
readonly source: string;
|
|
17
|
+
}
|
|
18
|
+
export type SlashEntry = SlashCommand | SlashSkillEntry;
|
|
5
19
|
export declare const SLASH_COMMANDS: readonly SlashCommand[];
|
|
20
|
+
export declare function truncateDisplay(text: string, maxWidth: number): string;
|
|
21
|
+
export declare function skillSlashName(skillName: string): string;
|
|
22
|
+
export declare function buildSkillEntries(skills: readonly SlashSkillSummary[]): SlashSkillEntry[];
|
|
23
|
+
export declare function currentSlashToken(input: string): string;
|
|
24
|
+
export declare function filterSlashEntries(input: string, skills?: readonly SlashSkillSummary[]): SlashEntry[];
|
|
6
25
|
export declare function filterCommands(input: string): SlashCommand[];
|
|
26
|
+
export declare function findSkillBySlashName(token: string, skills: readonly SlashSkillSummary[]): SlashSkillSummary | undefined;
|
|
27
|
+
export interface SkillInvocation {
|
|
28
|
+
readonly skills: readonly SlashSkillSummary[];
|
|
29
|
+
readonly prompt: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function parseSkillInvocation(input: string, skills: readonly SlashSkillSummary[]): SkillInvocation | undefined;
|
|
32
|
+
export declare function buildSkillInvocationPrompt(invocation: SkillInvocation): string;
|
|
33
|
+
export declare function buildSkillListLines(skills: readonly SlashSkillSummary[], width?: number): string[];
|
|
34
|
+
export declare function formatSkillList(skills: readonly SlashSkillSummary[], width?: number): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SLASH_COMMANDS=[{name:"/compact",description:"压缩上下文,释放 token"},{name:"/think",description:"开关 thinking/reasoning (on | off)"},{name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{name:"/help",description:"列出可用命令"},{name:"/exit",description:"退出对话"}];export function
|
|
1
|
+
import{SKILL_TOOL_ID as n}from"../../../skills/library.js";import{displayWidth as e}from"./markdown.js";export const SLASH_COMMANDS=[{kind:"command",name:"/compact",description:"压缩上下文,释放 token"},{kind:"command",name:"/think",description:"开关 thinking/reasoning (on | off)"},{kind:"command",name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{kind:"command",name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{kind:"command",name:"/skills",description:"列出可加载的 SKILL"},{kind:"command",name:"/help",description:"列出可用命令"},{kind:"command",name:"/exit",description:"退出对话"}];function t(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,t){if(e(n)<=t)return n;let i="",o=0;for(const r of n){const n=e(r);if(o+n>t-1)break;i+=r,o+=n}return`${i}…`}export function skillSlashName(n){return`/${n}`}export function buildSkillEntries(n){return[...n].filter(n=>!SLASH_COMMANDS.some(e=>e.name===skillSlashName(n.name))).sort((n,e)=>n.name.localeCompare(e.name)).map(n=>{const e=t(n.description);return{kind:"skill",name:skillSlashName(n.name),skillName:n.name,source:n.source,description:e.length>0?`${n.source} · ${e}`:n.source}})}export function currentSlashToken(n){const e=n.split(/\s+/),t=e.at(-1)??"";return t.startsWith("/")?t:e[0]??""}export function filterSlashEntries(n,e=[]){const t=currentSlashToken(n).toLowerCase();return[...SLASH_COMMANDS,...buildSkillEntries(e)].filter(n=>n.name.toLowerCase().startsWith(t))}export function filterCommands(n){const e=currentSlashToken(n).toLowerCase();return SLASH_COMMANDS.filter(n=>n.name.toLowerCase().startsWith(e))}export function findSkillBySlashName(n,e){const t=n.toLowerCase();return e.find(n=>skillSlashName(n.name).toLowerCase()===t)}export function parseSkillInvocation(n,e){let t=n.trimStart();const i=[],o=new Set;for(;;){const n=/^(\/\S+)(\s*)/.exec(t);if(!n)break;const r=findSkillBySlashName(n[1]??"",e);if(!r)break;o.has(r.name)||(i.push(r),o.add(r.name)),t=t.slice(n[0].length)}if(0!==i.length)return{skills:i,prompt:t.trim()}}export function buildSkillInvocationPrompt(e){const t=e.skills.map(n=>`- ${n.name}`).join("\n");return[`请先调用 \`${n}\` 工具加载以下 skill,并严格按它们的说明处理后续请求:`,t,"","用户请求:",e.prompt].join("\n")}const i=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const i=[...n].sort((n,e)=>n.name.localeCompare(e.name)),o=Math.min(Math.max(...i.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(i.length)} 个)· 用法: /<skill-name> 你的请求`,...i.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(o)} [${n.source}] ${t(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as o,useState as r}from"react";import{Box as e,Text as n,useInput as t,useStdout as i}from"ink";export function ConfirmSelect({prompt:l,args:c,onDecide:s}){const[d,u]=r("no"),{stdout:m}=i(),f=m.columns
|
|
1
|
+
import{createElement as o,useState as r}from"react";import{Box as e,Text as n,useInput as t,useStdout as i}from"ink";export function ConfirmSelect({prompt:l,args:c,onDecide:s}){const[d,u]=r("no"),{stdout:m}=i(),f=m.columns||80;t((o,r)=>{if(r.leftArrow||r.rightArrow||r.upArrow||r.downArrow)return void u(o=>"yes"===o?"no":"yes");if(r.return||o.includes("\r")||o.includes("\n"))return void s("yes"===d);const e=o.toLowerCase();r.escape||"n"===e?s(!1):"y"===e&&s(!0)});const a=c.length>0&&"{}"!==c;return o(e,{flexDirection:"column",width:f},o(e,{flexDirection:"column",borderStyle:"round",borderColor:"yellow",paddingX:2},o(n,null,l),a?o(n,{dimColor:!0},c):null,o(e,{marginTop:1},o(n,"yes"===d?{color:"green"}:{},("yes"===d?"❯ ":" ")+"Yes"),o(n,"no"===d?{color:"green"}:{},` ${"no"===d?"❯ ":" "}No`))),o(e,{marginLeft:1},o(n,{dimColor:!0},"←→/y/n 选择 · Enter 确认 · Esc 取消 · Shift+Tab 自动批准本次及后续")))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SUMMARY_ACK as t,SUMMARY_PREFIX as o}from"@roll-agent/runtime";import{formatToolInput as n}from"../../utils/tool-format.js";const
|
|
1
|
+
import{SUMMARY_ACK as t,SUMMARY_PREFIX as o}from"@roll-agent/runtime";import{formatToolInput as n}from"../../utils/tool-format.js";import{GLYPHS as e}from"../../utils/glyphs.js";const r=new Set(["error-text","error-json","execution-denied"]);function s(t){return"string"==typeof t?t:t.map(t=>"text"===t.type?t.text:"").join("")}function i(t){const o=t.indexOf("__");return o>=0?`${t.slice(0,o)}.${t.slice(o+2)}`:t}export function messagesToHistory(c,l="h"){const a=new Map;for(const t of c)if("tool"===t.role&&"string"!=typeof t.content)for(const o of t.content)if("tool-result"===o.type){const t=o.output.type;a.set(o.toolCallId,!r.has(t))}const f=[];return c.forEach((r,c)=>{const p=`${l}-${String(c)}`;if("user"===r.role){const t=s(r.content);return t.startsWith(o)?void f.push({kind:"compaction",id:p,notice:`${e.compact} 已恢复的上下文摘要`}):void(t.length>0&&f.push({kind:"user",id:p,text:t}))}if("assistant"===r.role){const o=s(r.content);if(o.startsWith(t))return;o.length>0&&f.push({kind:"assistant",id:p,text:o}),"string"!=typeof r.content&&r.content.forEach((t,o)=>{"tool-call"===t.type&&f.push({kind:"tool",id:`${p}-${String(o)}`,name:i(t.toolName),args:n(t.input),ok:a.get(t.toolCallId)??!0})})}}),f}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as
|
|
1
|
+
import{createElement as r}from"react";import{Box as o,Text as t}from"ink";import{parseThinking as e}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as l}from"./tool-label.js";const i=["已取消执行","策略拒绝执行"];function c(r){const o=r.trimStart();return i.some(r=>o.startsWith(r))}export function HistoryItemView({item:i}){switch(i.kind){case"banner":return r(o,{flexDirection:"column"},...i.lines.map((o,e)=>r(t,{key:String(e)},...o.spans.map((o,e)=>r(t,{key:String(e),...void 0!==o.color?{color:o.color}:{},...!0===o.dim?{dimColor:!0}:{}},o.text)))));case"user":return r(o,null,r(t,{color:"cyan",bold:!0},"▌ "),r(t,{color:"cyan"},i.text));case"assistant":return c(i.text)?r(o,null,r(t,{dimColor:!0},"⊘ "),r(t,{dimColor:!0},i.text.trim())):r(o,{flexDirection:"column"},...e(i.text).map((o,e)=>o.thinking?r(t,{key:String(e),dimColor:!0},o.text):r(n,{key:String(e),text:o.text})));case"tool":{const o=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return r(t,null,r(t,i.ok?{color:"green"}:{color:"red"},i.ok?"✓ ":"✗ "),r(l,{name:i.name}),o.length>0?r(t,{dimColor:!0},o):null)}case"denied":return r(t,{dimColor:!0},`⊘ ${i.name} ${i.label}`);case"compaction":return r(t,{dimColor:!0},i.notice);case"notice":return r(o,null,r(t,{color:"yellow"},"⚠ "),r(t,{color:"yellow"},i.text));case"error":return r(o,null,r(t,{color:"red"},"✗ "),r(t,{color:"red"},i.message));default:return r(t,null,"")}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as n}from"react";import{Box as l,Text as t}from"ink";import{Spinner as e}from"./spinner.js";import{ThinkingText as
|
|
1
|
+
import{createElement as n}from"react";import{Box as l,Text as t}from"ink";import{Spinner as e}from"./spinner.js";import{ThinkingText as i}from"./thinking-text.js";import{ToolLabel as o}from"./tool-label.js";export function LiveRegion({live:r}){return n(l,{flexDirection:"column"},r.streamingText.length>0?n(l,{marginTop:1},n(i,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText})):null,r.thinking?n(l,{marginTop:1},n(e,null),n(t,{dimColor:!0}," 思考中…")):null,...r.activeTools.map(i=>n(l,{key:i.toolCallId,marginLeft:2},n(e,null),n(t,null," "),n(o,{name:i.name}),i.args.length>0?n(t,{dimColor:!0},` ${i.args}`):null)),r.compacting?n(l,null,n(e,null),n(t,{dimColor:!0}," 压缩上下文中…")):null)}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AgentSession } from "@roll-agent/runtime";
|
|
2
2
|
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
3
|
+
import { type BannerInfo } from "../banner.ts";
|
|
4
|
+
export declare const INK_HINTS = "/exit \u9000\u51FA \u00B7 / \u547D\u4EE4 \u00B7 Shift+Enter/Ctrl+J \u6362\u884C \u00B7 Alt+./Alt+, \u8C03\u63A8\u7406 \u00B7 Shift+Tab \u81EA\u52A8\u6279\u51C6";
|
|
3
5
|
export interface InkReplStore {
|
|
4
6
|
updateTitle(threadId: string, title: string): void;
|
|
5
7
|
countMessages(threadId: string): number;
|
|
@@ -7,6 +9,7 @@ export interface InkReplStore {
|
|
|
7
9
|
}
|
|
8
10
|
export interface RunInkReplOptions {
|
|
9
11
|
readonly model: string;
|
|
12
|
+
readonly banner?: BannerInfo;
|
|
10
13
|
readonly initialThinkingLevel?: ThinkingLevel;
|
|
11
14
|
readonly onThinkingChange?: (level: ThinkingLevel) => void;
|
|
12
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as
|
|
1
|
+
import{createElement as t}from"react";import{render as e}from"ink";import{ChatApp as i}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{buildBannerLines as s}from"../banner.js";import{log as r}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(a,l,m,d){let p=!1,c=!m;const g=t=>{p=!0,c||(l.updateTitle(a.id,o(t)),c=!0)};d.banner||r.info(`多轮对话已就绪(${INK_HINTS})`);const h=d.banner?[{kind:"banner",id:"banner",lines:s(d.banner,process.stdout.columns||80,{hints:INK_HINTS})}]:[],u=!0===process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode;let f;u&&process.stdin.setRawMode(!0);try{f=e(t(i,{session:a,model:d.model,contextWindow:a.getContextWindow(),availableSkills:a.getSkillSummaries(),onUserSubmit:g,onExit:()=>{f.unmount()},initialHistory:[...h,...n(a.getMessages())],...d.initialThinkingLevel?{initialThinkingLevel:d.initialThinkingLevel}:{},...d.onThinkingChange?{onThinkingChange:d.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}})}catch(t){throw u&&process.stdin.setRawMode(!1),t}await f.waitUntilExit(),a.abort(),m&&!p&&0===l.countMessages(a.id)&&l.deleteThread(a.id)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SlashEntry } from "./commands.ts";
|
|
3
3
|
export interface SlashPopupProps {
|
|
4
|
-
readonly matches: readonly
|
|
4
|
+
readonly matches: readonly SlashEntry[];
|
|
5
5
|
readonly selected: number;
|
|
6
6
|
}
|
|
7
7
|
export declare function SlashPopup({ matches, selected }: SlashPopupProps): ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as
|
|
1
|
+
import{createElement as o}from"react";import{Box as r,Text as t,useStdout as n}from"ink";const e=8;export function SlashPopup({matches:e,selected:l}){const{stdout:a}=n(),i=a.columns||80;if(0===e.length)return o(r,{borderStyle:"round",borderColor:"gray",paddingX:1},o(t,{dimColor:!0},"无匹配命令"));const d=Math.min(Math.max(l-8+1,0),Math.max(e.length-8,0)),m=e.slice(d,d+8),c=Math.max(i-4,20);return o(r,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1},...m.map((n,e)=>{const a=d+e===l;return o(r,{key:n.name,width:c},o(t,{wrap:"truncate-end"},o(t,a?{color:"cyan",bold:!0}:{dimColor:!0},`${a?"❯ ":" "}${n.name}`),"skill"===n.kind?o(t,{color:"magenta"}," ⚡"):null,o(t,{dimColor:!0},` ${n.description}`)))}),e.length>8?o(t,{dimColor:!0},` ${String(l+1)}/${String(e.length)} · ↑↓ 浏览`):null)}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { SessionEvent, SessionTokenUsage } from "@roll-agent/runtime";
|
|
2
2
|
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
3
|
+
import type { BannerLine } from "../banner.ts";
|
|
3
4
|
export interface ToolRowState {
|
|
4
5
|
readonly toolCallId: string;
|
|
5
6
|
readonly name: string;
|
|
6
7
|
readonly args: string;
|
|
7
8
|
}
|
|
8
9
|
export type HistoryItem = {
|
|
10
|
+
readonly kind: "banner";
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly lines: readonly BannerLine[];
|
|
13
|
+
} | {
|
|
9
14
|
readonly kind: "user";
|
|
10
15
|
readonly id: string;
|
|
11
16
|
readonly text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatToolInput as t}from"../../utils/tool-format.js";import{
|
|
1
|
+
import{formatToolInput as t}from"../../utils/tool-format.js";import{GLYPHS as e}from"../../utils/glyphs.js";import{endsInsideThink as i}from"./thinking-text.js";const n={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function o(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,i){return{history:i?.history??[],draft:"",live:n,status:{model:t,contextWindow:e,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,outputTokensPerSecond:void 0,thinkingLevel:i?.thinkingLevel??"medium",autoApprove:!1},phase:"idle",pendingConfirm:void 0}}export function buildConfirmPrompt(t){const e=t.reason?`(${t.reason})`:"";return`执行 ${t.agentName}.${t.toolName}${e}?`}function r(t){const i="auto"===t.reason?"自动压缩":"手动压缩",n=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`${e.compact} ${i}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${n}`:`${e.compact} ${i}:无需压缩`}const s=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function a(t){return"string"==typeof t?t:"object"==typeof t&&null!==t&&"output"in t&&"string"==typeof t.output?t.output:void 0}export function denialLabel(t){const e=a(t);if(void 0!==e)return s.find(([t])=>e.startsWith(t))?.[1]}function u(t,e,i){const n=t.live.activeTools.find(t=>t.toolCallId===i.toolCallId),o=n?.name??`${i.agentName}.${i.toolName}`,r=n?.args??"",s=i.isError?denialLabel(i.output):void 0,a=void 0!==s?{kind:"denied",id:e,name:o,label:s}:{kind:"tool",id:e,name:o,args:r,ok:!i.isError};return{...t,history:[...t.history,a],live:{...t.live,activeTools:t.live.activeTools.filter(t=>t.toolCallId!==i.toolCallId)}}}function l(e,s,a){switch(a.type){case"message-start":return{...e,live:{...e.live,thinking:!0}};case"text-delta":return{...e,live:{...e.live,thinking:!1,producedOutput:!0,streamingText:e.live.streamingText+a.delta}};case"tool-call":{const n=e.live.streamingText.length>0?[{kind:"assistant",id:s,text:o(e.live.streamingText,e.live.thinkTagOpen)}]:[];return{...e,history:[...e.history,...n],live:{...e.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:i(e.live.streamingText,e.live.thinkTagOpen),activeTools:[...e.live.activeTools,{toolCallId:a.toolCallId,name:`${a.agentName}.${a.toolName}`,args:t(a.input)}]}}}case"tool-result":return u(e,s,a);case"confirmation-required":return{...e,phase:"confirm",pendingConfirm:{approvalId:a.approvalId,prompt:buildConfirmPrompt(a),args:t(a.input)}};case"compaction-start":return{...e,live:{...e.live,compacting:!0}};case"context-compacted":return{...e,live:{...e.live,compacting:!1},history:[...e.history,{kind:"compaction",id:s,notice:r(a)}]};case"message-finish":{const t=[];return e.live.streamingText.length>0?t.push({kind:"assistant",id:s,text:o(e.live.streamingText,e.live.thinkTagOpen)}):!e.live.producedOutput&&(a.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:s,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),a.stoppedAtStepLimit&&t.push({kind:"notice",id:`${s}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...e,history:[...e.history,...t],live:{...n},status:{...e.status,turnUsage:a.totalUsage,sessionUsage:a.sessionUsage,contextInputTokens:a.contextInputTokens,outputTokensPerSecond:a.outputTokensPerSecond}}}case"error":return{...e,history:[...e.history,{kind:"error",id:s,message:a.message}],live:{...n}};default:return e}}export function chatReducer(t,e){switch(e.type){case"submit-user":return{...t,history:[...t.history,{kind:"user",id:e.id,text:e.text}],draft:"",live:{...n},phase:"busy",pendingConfirm:void 0};case"set-draft":return{...t,draft:e.value};case"set-thinking":return{...t,status:{...t.status,thinkingLevel:e.level}};case"set-auto":return{...t,status:{...t.status,autoApprove:e.value}};case"commit-history":return{...t,history:[...t.history,e.item],draft:""};case"start-compaction":return{...t,live:{...n},phase:"busy",pendingConfirm:void 0};case"session-event":return l(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...n}};default:return t}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as o}from"react";import{Box as t,Text as e,useStdout as s}from"ink";import{computeUsageParts as n,contextPressure as p,formatContextUsage as
|
|
1
|
+
import{createElement as o}from"react";import{Box as t,Text as e,useStdout as s}from"ink";import{computeUsageParts as n,contextPressure as p,formatContextUsage as i,formatThroughput as r,formatTokens as c,formatTurnUsage as l}from"../../utils/token-format.js";import{displayWidth as u}from"./markdown.js";import{GLYPHS as m}from"../../utils/glyphs.js";import{thinkingLabel as a}from"./thinking.js";const k={ok:{dimColor:!0},warn:{color:"yellow"},critical:{color:"red"}},f={off:"off",low:"low",medium:"med",high:"high"},d=["tps","session","turn","think"],h=" · ";export function composeStatusSegments(o,t){const e=n(o.turnUsage,o.sessionUsage,o.contextWindow,o.contextInputTokens),s=[{key:"model",full:o.model,compact:o.model,props:{color:"magenta"}},{key:"think",full:a(o.thinkingLevel),compact:`${m.think} ${f[o.thinkingLevel]}`,props:"off"===o.thinkingLevel?{color:"yellow"}:{dimColor:!0}}];o.autoApprove&&s.push({key:"auto",full:`${m.auto} auto-approve`,compact:`${m.auto} auto`,props:{color:"yellow"}});const y=i(e);void 0!==y&&void 0!==e.usedTokens&&void 0!==e.contextWindow&&s.push({key:"ctx",full:y,compact:`ctx ${c(e.usedTokens)}/${c(e.contextWindow)}`,props:k[p(e.percentLeft)]});const v=l(e);if(void 0!==v){const o=[];void 0!==e.inputTokens&&o.push(`↑${c(e.inputTokens)}`),void 0!==e.outputTokens&&o.push(`↓${c(e.outputTokens)}`),s.push({key:"turn",full:v,compact:o.join(" "),props:{dimColor:!0}})}const g=r(o.outputTokensPerSecond);void 0!==g&&s.push({key:"tps",full:g,compact:g.replace(" tok/s","t/s"),props:{dimColor:!0}}),void 0!==e.sessionTokens&&s.push({key:"session",full:`session ${c(e.sessionTokens)}`,compact:`Σ${c(e.sessionTokens)}`,props:{dimColor:!0}});const w=(o,t)=>o.map(o=>({key:o.key,text:o[t],props:o.props})),x=o=>u(o.map(o=>o.text).join(h))<=t,T=w(s,"full");if(x(T))return T;let $=s,j=w($,"compact");for(const o of d){if(x(j))break;$=$.filter(t=>t.key!==o),j=w($,"compact")}return j}export function StatusLine({status:n}){const{stdout:p}=s(),i=p.columns||80,r=composeStatusSegments(n,i);return o(t,{width:i},o(e,{wrap:"truncate-end"},...r.flatMap((t,s)=>[...s>0?[o(e,{key:`${t.key}-sep`,dimColor:!0},h)]:[],o(e,{key:t.key,...t.props},t.text)])))}
|
|
@@ -3,6 +3,7 @@ export interface TextPromptProps {
|
|
|
3
3
|
readonly value: string;
|
|
4
4
|
readonly disabled: boolean;
|
|
5
5
|
readonly slashActive: boolean;
|
|
6
|
+
readonly slashPopupActive: boolean;
|
|
6
7
|
readonly autoApprove: boolean;
|
|
7
8
|
readonly onChange: (value: string) => void;
|
|
8
9
|
readonly onSubmit: (value: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as r,useRef as t}from"react";import{Box as e,Text as n,useInput as o,useStdout as i}from"ink";function
|
|
1
|
+
import{createElement as r,useRef as t}from"react";import{Box as e,Text as n,useInput as o,useStdout as i}from"ink";import{GLYPHS as l}from"../../utils/glyphs.js";function u(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function TextPrompt(c){const{value:s,disabled:a,slashActive:d,slashPopupActive:f,autoApprove:m,onChange:p,onSubmit:h}=c,{stdout:v}=i(),b=v.columns||80,S=t(s);S.current=s;const g=r=>{S.current=r,p(r)};o((r,t)=>{if(t.meta&&("."===r||","===r))return;if(u(r))return;if(t.backspace||t.delete)return void g(S.current.slice(0,-1));if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void g(`${S.current}\n`);if(t.return||r.includes("\r")){if(d)return void c.onSlashRun();const t=r.split("\r",1)[0]??"";return void h(S.current+t)}if(f){if(t.upArrow)return void c.onSlashMove(-1);if(t.downArrow)return void c.onSlashMove(1);if(t.tab&&!t.shift)return void c.onSlashComplete()}d&&t.escape?g(""):t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&g(S.current+r)},{isActive:!a});const w=s.split("\n"),A=r(e,{flexDirection:"column"},...w.map((t,o)=>{const i=o===w.length-1,l=0===o?r(n,{color:"green"},"› "):r(n,null," ");return r(e,{key:String(o)},l,r(n,a?{dimColor:!0}:{},t),i&&!a?r(n,null,"▏"):null)})),C=d?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":m?"Shift+Tab 关闭 · Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",E=r(e,{marginLeft:1},...m?[r(n,{color:"yellow"},`${l.auto} auto`),r(n,{dimColor:!0},` · ${C}`)]:[r(n,{dimColor:!0},C)]);return r(e,{flexDirection:"column",width:b},r(e,{borderStyle:"round",borderColor:a?"gray":"cyan",paddingX:2},A),E)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{GLYPHS as n}from"../../utils/glyphs.js";const t=["off","low","medium","high"];export function cycleThinking(n,i){const o=t.indexOf(n),e=Math.min(Math.max(o+i,0),t.length-1);return t[e]??n}export function thinkingLabel(t){return`${n.think} ${t}`}
|
|
@@ -10,7 +10,7 @@ export interface UseSessionOptions {
|
|
|
10
10
|
}
|
|
11
11
|
export interface UseSessionResult {
|
|
12
12
|
readonly state: ChatUiState;
|
|
13
|
-
readonly submit: (text: string) => void;
|
|
13
|
+
readonly submit: (text: string, sendText?: string) => void;
|
|
14
14
|
readonly compact: () => void;
|
|
15
15
|
readonly resolveConfirm: (approved: boolean) => void;
|
|
16
16
|
readonly setDraft: (value: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{useCallback as t,useReducer as n,useRef as i}from"react";import{chatReducer as r,createInitialState as o}from"./state.js";import{log as s}from"../../utils/output.js";const a=32;function c(e){return e instanceof Error?e.message:String(e)}export function useSession(u,p){const[l,m]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),d=p.onThinkingChange,y=i(null),v=i(!1),f=i(!1),g=t(async t=>{let n,i="";const r=()=>{if(void 0!==n&&(clearTimeout(n),n=void 0),i.length>0){const t=i;i="",m({type:"session-event",id:e(),event:{type:"text-delta",delta:t}})}};try{for await(const o of t)if("debug"!==o.type)if("text-delta"!==o.type){if(r(),"confirmation-required"===o.type){if(v.current){u.approve(o.approvalId);continue}const t=new Promise(e=>{y.current=e});m({type:"session-event",id:e(),event:o});const n=await t;y.current=null,n?u.approve(o.approvalId):u.reject(o.approvalId,"用户取消"),m({type:"confirm-resolved"});continue}m({type:"session-event",id:e(),event:o})}else i+=o.delta,void 0===n&&(n=setTimeout(r,a));else s.debug(`chat.${o.stage} ${o.message}`)}catch(t){r(),m({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:c(t)}})}finally{r(),m({type:"turn-end"}),f.current=!1}},[u]),h=t(t=>{f.current||(f.current=!0,m({type:"submit-user",id:e(),text:t}),g(u.send(t)).catch(()=>{}))},[g,u]),k=t(()=>{f.current||(f.current=!0,m({type:"start-compaction"}),g(u.compact("manual")).catch(()=>{}))},[g,u]),x=t(e=>{y.current?.(e)},[]),T=t(e=>{m({type:"set-draft",value:e})},[]),b=t(e=>{m({type:"set-thinking",level:e}),d?.(e)},[d]),w=t(e=>{v.current=e,m({type:"set-auto",value:e}),e&&y.current?.(!0)},[]),j=t(()=>{w(!v.current)},[w]),H=t(e=>{m({type:"commit-history",item:e})},[]);return{state:l,submit:h,compact:k,resolveConfirm:x,setDraft:T,setThinking:b,setAutoMode:w,toggleAutoMode:j,commitHistory:H}}
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{useCallback as t,useReducer as n,useRef as i}from"react";import{chatReducer as r,createInitialState as o}from"./state.js";import{log as s}from"../../utils/output.js";const a=32;function c(e){return e instanceof Error?e.message:String(e)}export function useSession(u,p){const[l,m]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),d=p.onThinkingChange,y=i(null),v=i(!1),f=i(!1),g=t(async t=>{let n,i="";const r=()=>{if(void 0!==n&&(clearTimeout(n),n=void 0),i.length>0){const t=i;i="",m({type:"session-event",id:e(),event:{type:"text-delta",delta:t}})}};try{for await(const o of t)if("debug"!==o.type)if("text-delta"!==o.type){if(r(),"confirmation-required"===o.type){if(v.current){u.approve(o.approvalId);continue}const t=new Promise(e=>{y.current=e});m({type:"session-event",id:e(),event:o});const n=await t;y.current=null,n?u.approve(o.approvalId):u.reject(o.approvalId,"用户取消"),m({type:"confirm-resolved"});continue}m({type:"session-event",id:e(),event:o})}else i+=o.delta,void 0===n&&(n=setTimeout(r,a));else s.debug(`chat.${o.stage} ${o.message}`)}catch(t){r(),m({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:c(t)}})}finally{r(),m({type:"turn-end"}),f.current=!1}},[u]),h=t((t,n)=>{f.current||(f.current=!0,m({type:"submit-user",id:e(),text:t}),g(u.send(n??t)).catch(()=>{}))},[g,u]),k=t(()=>{f.current||(f.current=!0,m({type:"start-compaction"}),g(u.compact("manual")).catch(()=>{}))},[g,u]),x=t(e=>{y.current?.(e)},[]),T=t(e=>{m({type:"set-draft",value:e})},[]),b=t(e=>{m({type:"set-thinking",level:e}),d?.(e)},[d]),w=t(e=>{v.current=e,m({type:"set-auto",value:e}),e&&y.current?.(!0)},[]),j=t(()=>{w(!v.current)},[w]),H=t(e=>{m({type:"commit-history",item:e})},[]);return{state:l,submit:h,compact:k,resolveConfirm:x,setDraft:T,setThinking:b,setAutoMode:w,toggleAutoMode:j,commitHistory:H}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineCommand as e}from"citty";import{loadConfig as t}from"../../config/loader.js";import{resolveLLMCall as o,thinkingProviderOptions as
|
|
1
|
+
import{defineCommand as e}from"citty";import{loadConfig as t}from"../../config/loader.js";import{resolveLLMCall as o,thinkingProviderOptions as n}from"../../llm/providers.js";import{createInterface as s}from"node:readline/promises";import i from"chalk";import r from"cli-table3";import{isDebugLogEnabled as a,log as l}from"../utils/output.js";import{ChatRenderer as c,clackConfirm as d}from"../utils/chat-renderer.js";import{titleFromMessage as u}from"../chat/title.js";import{buildBannerLines as p,renderBannerText as m}from"../chat/banner.js";import{getCurrentVersion as g}from"../utils/update-checker.js";import{buildSkillInvocationPrompt as f,formatSkillList as h,parseSkillInvocation as v}from"../chat/ink/commands.js";const w=import.meta.url.endsWith(".ts")?"ts":"js";function y(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}function k(e){console.log(JSON.stringify(e,null,2))}function b(e){l.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function S(e){l.warn(`skill 目录加载警告:${e}`)}function x(e,t){const o=v(t,e.getSkillSummaries());return o&&o.prompt.length>0?f(o):t}async function C(e,t,o){l.debug(`chat.repl input waiting · ${o}`),e.resume();try{const n=await e.question(t);return l.debug(`chat.repl input received · ${o} · chars=${String(n.length)}`),n}catch(e){const t=e instanceof Error?e.message:String(e);return void l.debug(`chat.repl input closed · ${o} · ${t}`)}finally{e.pause()}}async function T(){return import("@roll-agent/runtime")}async function I(e){const t=e.runtime.provider??e.llm.defaultProvider,n=e.runtime.model??e.llm.defaultModel,s=e.llm.providers[t];if(!s)return l.error(`LLM provider "${t}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const i=await T(),{ConversationEngine:r,ThreadStore:c,RuntimeServer:d,createStdioConnection:u}=i,{model:p,providerOptions:m}=o(t,n,s.apiKey,"chat",s.baseUrl,e.runtime.thinkingLevel),g=new c(e.runtime.threadsDir),f=new r({config:e,model:p,store:g,policy:y(i,e),maxSteps:e.runtime.maxSteps,...m?{providerOptions:m}:{},debugEvents:a(),onAgentBootstrapIssue:b,onSkillLibraryIssue:S}),h=u(process.stdin,process.stdout),v=new d(f,h);h.onClose(()=>{v.abortAll(),f.dispose().catch(()=>{}).finally(()=>{g.close(),process.exit(0)})}),l.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function $(e,t){const{ThreadStore:o}=await T(),n=new o(e.runtime.threadsDir);try{const e=n.listThreads();if(t){const t=e.map(e=>({...e,messageCount:n.countMessages(e.id)}));return void console.log(JSON.stringify(t,null,2))}if(0===e.length)return void console.log('暂无会话。用 `roll chat "<message>"` 开始一个。');const o=new r({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const t of e)o.push([t.id,t.title??"-",String(n.countMessages(t.id)),t.updatedAt]);console.log(o.toString())}finally{n.close()}}export async function runJsonTurn(e,t){const o=[],n=[],s=[],i=[];let r,a,l,c,d="";for await(const u of e.send(t))switch(u.type){case"text-delta":d+=u.delta;break;case"tool-call":o.push({summary:`${u.agentName}.${u.toolName}`,agentName:u.agentName,toolName:u.toolName});break;case"confirmation-required":i.push({summary:`${u.agentName}.${u.toolName}`,agentName:u.agentName,toolName:u.toolName}),e.reject(u.approvalId,"json 模式不支持交互确认");break;case"step-finish":n.push({finishReason:u.finishReason,...u.usage?{usage:u.usage}:{}});break;case"message-finish":a=u.totalUsage,l=u.sessionUsage,c=u.contextInputTokens;break;case"context-compacted":s.push({reason:u.reason,strategy:u.strategy,removed:u.removed,kept:u.kept,...void 0!==u.truncatedTools?{truncatedTools:u.truncatedTools}:{},...void 0!==u.beforeInputTokens?{beforeInputTokens:u.beforeInputTokens}:{}});break;case"error":r=u.message}const u=e.getContextWindow();return void 0!==r?{status:"failed",stage:"execute",message:r,sessionId:e.id}:i.length>0?{status:"needs_confirmation",sessionId:e.id,message:"存在需要确认的工具调用,请在交互模式下执行或显式批准",pendingActions:i}:{status:"completed",sessionId:e.id,output:d,steps:o,...n.length>0?{stepUsages:n}:{},...a?{totalUsage:a}:{},...l?{sessionUsage:l}:{},...void 0!==u?{contextWindow:u}:{},...void 0!==c?{contextInputTokens:c}:{},...s.length>0?{compactions:s}:{}}}export async function runRepl(e,t,o,n={input:process.stdin,output:process.stdout}){const r=s({input:n.input,output:n.output});r.on("SIGINT",()=>r.close());const a=n.confirm??(async e=>{l.debug("chat.repl input waiting · confirm"),r.pause();const t=await d(e);return l.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),p=new c(a,e.getContextWindow()),m=e.getSkillSummaries();l.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let g=!o,w=!1;try{for(;;){const o=await C(r,i.green("› "),"prompt");if(void 0===o)break;const n=o.trim();if(0===n.length)continue;if("exit"===n||"quit"===n)break;if("/compact"===n){l.debug("chat.repl manual compact requested");for await(const t of e.compact("manual"))await p.handle(t,e);l.debug("chat.repl manual compact completed");continue}if("/skills"===n){l.info(h(m,(process.stdout.columns||96)-2));continue}const s=v(n,m);if(s&&0===s.prompt.length){l.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");continue}const a=s?f(s):n;g||(t.updateTitle(e.id,u(n)),g=!0),w=!0,l.debug(`chat.repl send start · chars=${String(n.length)}`);for await(const t of e.send(a))await p.handle(t,e);l.debug("chat.repl send completed")}}finally{r.close(),o&&!w&&0===t.countMessages(e.id)&&t.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:s}=t();if(e.server)return void await I(s);if(e.list)return void await $(s,e.json);const i=s.runtime.provider??s.llm.defaultProvider,r=s.runtime.model??s.llm.defaultModel,f=s.llm.providers[i];if(!f)return l.error(`LLM provider "${i}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);if(e.json&&!e.message)return l.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const h=await T(),{ConversationEngine:v,ThreadStore:C}=h,{model:N,providerOptions:j}=o(i,r,f.apiKey,"chat",f.baseUrl,s.runtime.thinkingLevel),L=new C(s.runtime.threadsDir),O=new v({config:s,model:N,store:L,policy:y(h,s),maxSteps:s.runtime.maxSteps,...j?{providerOptions:j}:{},debugEvents:a(),onAgentBootstrapIssue:b,onSkillLibraryIssue:S});try{let t;if(e.session)t=await O.resumeSession(e.session);else if(e.last){const e=L.listThreads()[0];if(!e)return l.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);t=await O.resumeSession(e.id)}else t=await O.createSession(e.message?{title:u(e.message)}:{});if(e.json&&e.message){const o=await runJsonTurn(t,x(t,e.message));return k(o),void("completed"!==o.status&&(process.exitCode=1))}if(l.info(`会话 ${t.id}`),s.runtime.compaction.enabled&&void 0===t.getContextWindow()&&l.warn(`未知模型 "${r}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const o=new c(d,t.getContextWindow());for await(const n of t.send(x(t,e.message)))await o.handle(n,t)}else{const o=!e.session&&!e.last,a=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode),c=await O.getContextSummary(),d={version:g(),model:r,agentCount:c.agentCount,skillCount:c.skillCount};let u=!1;if(a)try{const e=new URL(`../chat/ink/run-ink-repl.${w}`,import.meta.url).href,{runInkRepl:a}=await import(e);await a(t,L,o,{model:r,banner:d,initialThinkingLevel:s.runtime.thinkingLevel,onThinkingChange:e=>t.setProviderOptions(n(i,r,e))}),u=!0}catch(e){l.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}u||(process.stderr.write(`${m(p(d,process.stdout.columns||80))}\n`),await runRepl(t,L,o))}}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await O.dispose(),L.close()}}});
|
|
@@ -100,6 +100,12 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
|
|
|
100
100
|
readonly purpose: "`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。";
|
|
101
101
|
readonly defaultBehavior: "未配置时回退到 `runtime.approval.default`。";
|
|
102
102
|
readonly example: "runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto";
|
|
103
|
+
}, {
|
|
104
|
+
readonly path: "skills.dirs";
|
|
105
|
+
readonly title: "Chat 额外 skill 目录";
|
|
106
|
+
readonly purpose: "`roll chat` 除自动发现项目与用户级 `.agents/skills`(`npx skills add` 的标准安装位置)及已注册 Agent 的 SKILL.md 外,额外加载的 skill 目录。目录可以直接包含 SKILL.md,也可以是多个 skill 子目录的集合。";
|
|
107
|
+
readonly defaultBehavior: "默认为空。标准路径 `<项目>/.agents/skills` 与 `~/.agents/skills` 始终自动发现。";
|
|
108
|
+
readonly example: "skills:\n dirs:\n - ./openclaw-roll-core-skill-template";
|
|
103
109
|
}, {
|
|
104
110
|
readonly path: "browser.default-instance";
|
|
105
111
|
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:"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
|
+
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:"skills.dirs",title:"Chat 额外 skill 目录",purpose:"`roll chat` 除自动发现项目与用户级 `.agents/skills`(`npx skills add` 的标准安装位置)及已注册 Agent 的 SKILL.md 外,额外加载的 skill 目录。目录可以直接包含 SKILL.md,也可以是多个 skill 子目录的集合。",defaultBehavior:"默认为空。标准路径 `<项目>/.agents/skills` 与 `~/.agents/skills` 始终自动发现。",example:"skills:\n dirs:\n - ./openclaw-roll-core-skill-template"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=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,10 +1,9 @@
|
|
|
1
1
|
import type { RegisteredAgent } from "../../types/agent.ts";
|
|
2
|
+
import { type SkillReferenceDocument } from "../../skills/documents.ts";
|
|
3
|
+
import { getAgentSkillPath, resolveAgentSkillPath } from "../../skills/library.ts";
|
|
2
4
|
export type SkillDocumentSource = "filesystem" | "registry";
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
readonly path: string;
|
|
6
|
-
readonly content: string;
|
|
7
|
-
}
|
|
5
|
+
export type AgentSkillReferenceDocument = SkillReferenceDocument;
|
|
6
|
+
export { getAgentSkillPath, resolveAgentSkillPath };
|
|
8
7
|
export interface AgentSkillDocument {
|
|
9
8
|
readonly name: string;
|
|
10
9
|
readonly description: string;
|
|
@@ -22,7 +21,5 @@ export interface AgentSkillListItem {
|
|
|
22
21
|
export interface ResolveAgentSkillDocumentOptions {
|
|
23
22
|
readonly includeReferences?: boolean;
|
|
24
23
|
}
|
|
25
|
-
export declare function getAgentSkillPath(agent: RegisteredAgent): string;
|
|
26
|
-
export declare function resolveAgentSkillPath(agent: RegisteredAgent): string | undefined;
|
|
27
24
|
export declare function resolveAgentSkillDocument(agent: RegisteredAgent, options?: ResolveAgentSkillDocumentOptions): AgentSkillDocument;
|
|
28
25
|
export declare function listAgentSkills(agents: readonly RegisteredAgent[]): readonly AgentSkillListItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{readFileSync as e}from"node:fs";import{readReferencedSkillDocuments as i}from"../../skills/documents.js";import{getAgentSkillPath as n,resolveAgentSkillPath as s}from"../../skills/library.js";export{n as getAgentSkillPath,s as resolveAgentSkillPath};export function resolveAgentSkillDocument(n,l={}){const r=s(n);if(r){const s=e(r,"utf-8");return{name:n.skill.name,description:n.skill.description,source:"filesystem",path:r,content:s,...l.includeReferences?{references:i(r,s)}:{}}}return{name:n.skill.name,description:n.skill.description,source:"registry",content:t(n),...l.includeReferences?{references:[]}:{}}}export function listAgentSkills(e){return e.map(e=>{const i=s(e);return{name:e.skill.name,description:e.skill.description,source:i?"filesystem":"registry",...i?{path:i}:{}}})}function t(e){const i=["---",`name: ${l(e.skill.name)}`,`description: ${l(e.skill.description)}`];e.skill.license&&i.push(`license: ${l(e.skill.license)}`),e.skill.compatibility&&i.push(`compatibility: ${l(e.skill.compatibility)}`),i.push("---");const n=e.skillBody?.trim();return`${i.join("\n")}\n\n${n&&n.length>0?n:e.skill.description}\n`}function l(e){return JSON.stringify(e)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import s from"chalk";import{isCancel as e,select as t}from"@clack/prompts";import{createSpinner as n,log as
|
|
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)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"is-unicode-supported";const o=t();export const GLYPHS=o?{think:"🧠",auto:"⏵⏵",compact:"🗜"}:{think:"think",auto:">>",compact:"*"};
|
package/dist/config/defaults.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LLM_PROVIDER_OPTIONS=["anthropic","openai","qwen","deepseek"];export const DEFAULT_LLM_PROVIDER="anthropic";export const DEFAULT_LLM_MODELS={anthropic:"claude-sonnet-4-6",openai:"gpt-5.5",qwen:"qwen3.6-plus",deepseek:"deepseek-v4-flash"};export const DEFAULT_CONFIG={llm:{defaultProvider:"anthropic",defaultModel:DEFAULT_LLM_MODELS.anthropic,providers:{}},ask:{},runtime:{maxSteps:80,turnTimeoutMs:3e5,threadsDir:"~/.roll-agent/threads",thinkingLevel:"medium",approval:{default:"guarded",overrides:{}},compaction:{enabled:!0,strategy:"summarize",threshold:.75,keepRecentTurns:4,keepRecentTokens:32e3}},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}},skills:{dirs:[]},agents:{dataDir:"~/.roll-agent/agents"},install:{fetchRetries:3,preferOffline:!1,networkTimeoutMs:12e4},browser:{instances:{}}};export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
|
package/dist/config/loader.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as n,existsSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as o}from"node:path";import{parse as e}from"yaml";import{agentsConfigSchema as i,installConfigSchema as s,rollConfigSchema as a}from"./schema.js";import{DEFAULT_CONFIG as f,CONFIG_FILE_NAMES as c}from"./defaults.js";import{decodeFromYaml as 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)},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 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}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -169,6 +169,14 @@ export declare const runtimeConfigSchema: z.ZodObject<{
|
|
|
169
169
|
keepRecentTokens?: number | undefined;
|
|
170
170
|
} | undefined;
|
|
171
171
|
}>;
|
|
172
|
+
export declare const skillsConfigSchema: z.ZodObject<{
|
|
173
|
+
/** 额外的 skill 目录(canonical `.agents/skills` 之外的补充来源)。 */
|
|
174
|
+
dirs: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
175
|
+
}, "strip", z.ZodTypeAny, {
|
|
176
|
+
dirs: string[];
|
|
177
|
+
}, {
|
|
178
|
+
dirs?: string[] | undefined;
|
|
179
|
+
}>;
|
|
172
180
|
export declare const agentsConfigSchema: z.ZodObject<{
|
|
173
181
|
dataDir: z.ZodString;
|
|
174
182
|
/** per-agent 环境变量:键为 agent name,值为 key-value 对 */
|
|
@@ -652,6 +660,14 @@ export declare const rollConfigSchema: z.ZodObject<{
|
|
|
652
660
|
keepRecentTokens?: number | undefined;
|
|
653
661
|
} | undefined;
|
|
654
662
|
}>>;
|
|
663
|
+
skills: z.ZodDefault<z.ZodObject<{
|
|
664
|
+
/** 额外的 skill 目录(canonical `.agents/skills` 之外的补充来源)。 */
|
|
665
|
+
dirs: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
666
|
+
}, "strip", z.ZodTypeAny, {
|
|
667
|
+
dirs: string[];
|
|
668
|
+
}, {
|
|
669
|
+
dirs?: string[] | undefined;
|
|
670
|
+
}>>;
|
|
655
671
|
agents: z.ZodObject<{
|
|
656
672
|
dataDir: z.ZodString;
|
|
657
673
|
/** per-agent 环境变量:键为 agent name,值为 key-value 对 */
|
|
@@ -909,6 +925,9 @@ export declare const rollConfigSchema: z.ZodObject<{
|
|
|
909
925
|
llmModel?: string | undefined;
|
|
910
926
|
confirmThreshold?: number | undefined;
|
|
911
927
|
};
|
|
928
|
+
skills: {
|
|
929
|
+
dirs: string[];
|
|
930
|
+
};
|
|
912
931
|
browser: {
|
|
913
932
|
instances: Record<string, {
|
|
914
933
|
mode: "managed-cdp" | "remote-cdp" | "existing-session";
|
|
@@ -989,6 +1008,9 @@ export declare const rollConfigSchema: z.ZodObject<{
|
|
|
989
1008
|
preferOffline?: boolean | undefined;
|
|
990
1009
|
networkTimeoutMs?: number | undefined;
|
|
991
1010
|
} | undefined;
|
|
1011
|
+
skills?: {
|
|
1012
|
+
dirs?: string[] | undefined;
|
|
1013
|
+
} | undefined;
|
|
992
1014
|
browser?: {
|
|
993
1015
|
defaultInstance?: string | undefined;
|
|
994
1016
|
instances?: Record<string, {
|
|
@@ -1037,6 +1059,7 @@ export declare const rollConfigSchema: z.ZodObject<{
|
|
|
1037
1059
|
}>;
|
|
1038
1060
|
export type RollConfig = z.infer<typeof rollConfigSchema>;
|
|
1039
1061
|
export type RuntimeConfig = z.infer<typeof runtimeConfigSchema>;
|
|
1062
|
+
export type SkillsConfig = z.infer<typeof skillsConfigSchema>;
|
|
1040
1063
|
export type RuntimeApprovalConfig = z.infer<typeof runtimeApprovalConfigSchema>;
|
|
1041
1064
|
export type BrowserConfig = z.infer<typeof browserConfigSchema>;
|
|
1042
1065
|
export type InstallConfig = z.infer<typeof installConfigSchema>;
|
package/dist/config/schema.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as e}from"zod";const t=["zhipin","yupao"],o=["managed-cdp","remote-cdp","existing-session"],n=["chrome","chromium","msedge"],i=["guarded","auto","deny"],r=["auto","confirm","deny"],a=["summarize","truncate"],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 runtimeApprovalConfigSchema=e.object({default:e.enum(i).default("guarded"),overrides:e.record(e.string(),e.enum(r)).default({})});export const runtimeCompactionConfigSchema=e.object({enabled:e.boolean().default(!0),strategy:e.enum(a).default("summarize"),threshold:e.number().min(.1).max(.95).default(.75),keepRecentTurns:e.number().int().min(1).default(4),keepRecentTokens:e.number().int().min(1).default(32e3)});export const runtimeThinkingLevels=["off","low","medium","high"];export const runtimeConfigSchema=e.object({provider:e.string().optional(),model:e.string().optional(),maxSteps:e.number().int().min(1).default(80),turnTimeoutMs:e.number().int().min(1e4).default(3e5),threadsDir:e.string().default("~/.roll-agent/threads"),contextWindow:e.number().int().min(1).optional(),thinkingLevel:e.enum(runtimeThinkingLevels).default("medium"),approval:runtimeApprovalConfigSchema.default({}),compaction:runtimeCompactionConfigSchema.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(t).optional(),mode:e.enum(o).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((t,o)=>{"managed-cdp"===t.mode&&void 0===t.cdpPort&&o.addIssue({code:e.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==t.mode&&"existing-session"!==t.mode||void 0!==t.cdpUrl||o.addIssue({code:e.ZodIssueCode.custom,path:["cdpUrl"],message:`${t.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((t,o)=>{const n=Object.entries(t.instances);void 0!==t.defaultInstance&&void 0===t.instances[t.defaultInstance]&&o.addIssue({code:e.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${t.defaultInstance}" is not declared in browser.instances`});const i=new Map,r=new Map;for(const[t,a]of n){if(void 0!==a.cdpPort){const n=i.get(a.cdpPort);void 0!==n?o.addIssue({code:e.ZodIssueCode.custom,path:["instances",t,"cdpPort"],message:`cdpPort ${String(a.cdpPort)} is already used by browser instance "${n}"`}):i.set(a.cdpPort,t)}const n=r.get(a.userDataDir);void 0!==n?o.addIssue({code:e.ZodIssueCode.custom,path:["instances",t,"userDataDir"],message:`userDataDir is already used by browser instance "${n}"`}):r.set(a.userDataDir,t)}});export const rollConfigSchema=e.object({llm:llmConfigSchema,ask:askConfigSchema,runtime:runtimeConfigSchema.default({}),agents:agentsConfigSchema,install:installConfigSchema.default({}),browser:browserConfigSchema.default({})});
|
|
1
|
+
import{z as e}from"zod";const t=["zhipin","yupao"],o=["managed-cdp","remote-cdp","existing-session"],n=["chrome","chromium","msedge"],i=["guarded","auto","deny"],r=["auto","confirm","deny"],a=["summarize","truncate"],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 runtimeApprovalConfigSchema=e.object({default:e.enum(i).default("guarded"),overrides:e.record(e.string(),e.enum(r)).default({})});export const runtimeCompactionConfigSchema=e.object({enabled:e.boolean().default(!0),strategy:e.enum(a).default("summarize"),threshold:e.number().min(.1).max(.95).default(.75),keepRecentTurns:e.number().int().min(1).default(4),keepRecentTokens:e.number().int().min(1).default(32e3)});export const runtimeThinkingLevels=["off","low","medium","high"];export const runtimeConfigSchema=e.object({provider:e.string().optional(),model:e.string().optional(),maxSteps:e.number().int().min(1).default(80),turnTimeoutMs:e.number().int().min(1e4).default(3e5),threadsDir:e.string().default("~/.roll-agent/threads"),contextWindow:e.number().int().min(1).optional(),thinkingLevel:e.enum(runtimeThinkingLevels).default("medium"),approval:runtimeApprovalConfigSchema.default({}),compaction:runtimeCompactionConfigSchema.default({})});export const skillsConfigSchema=e.object({dirs:e.array(e.string().trim().min(1)).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(t).optional(),mode:e.enum(o).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((t,o)=>{"managed-cdp"===t.mode&&void 0===t.cdpPort&&o.addIssue({code:e.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==t.mode&&"existing-session"!==t.mode||void 0!==t.cdpUrl||o.addIssue({code:e.ZodIssueCode.custom,path:["cdpUrl"],message:`${t.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((t,o)=>{const n=Object.entries(t.instances);void 0!==t.defaultInstance&&void 0===t.instances[t.defaultInstance]&&o.addIssue({code:e.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${t.defaultInstance}" is not declared in browser.instances`});const i=new Map,r=new Map;for(const[t,a]of n){if(void 0!==a.cdpPort){const n=i.get(a.cdpPort);void 0!==n?o.addIssue({code:e.ZodIssueCode.custom,path:["instances",t,"cdpPort"],message:`cdpPort ${String(a.cdpPort)} is already used by browser instance "${n}"`}):i.set(a.cdpPort,t)}const n=r.get(a.userDataDir);void 0!==n?o.addIssue({code:e.ZodIssueCode.custom,path:["instances",t,"userDataDir"],message:`userDataDir is already used by browser instance "${n}"`}):r.set(a.userDataDir,t)}});export const rollConfigSchema=e.object({llm:llmConfigSchema,ask:askConfigSchema,runtime:runtimeConfigSchema.default({}),skills:skillsConfigSchema.default({}),agents:agentsConfigSchema,install:installConfigSchema.default({}),browser:browserConfigSchema.default({})});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SkillReferenceDocument {
|
|
2
|
+
readonly relativePath: string;
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly content: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function collectReferencedSkillPaths(content: string): readonly string[];
|
|
7
|
+
export declare function readReferencedSkillDocuments(skillPath: string, content: string): readonly SkillReferenceDocument[];
|
|
8
|
+
export declare function readReferencedSkillDocument(skillPath: string, referencePath: string): SkillReferenceDocument | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as t,readFileSync as e,realpathSync as n,statSync as o}from"node:fs";import{dirname as r,extname as s,isAbsolute as c,normalize as i,relative as l,resolve as f,sep as a}from"node:path";const u=/(?:\.\/)?references\/[A-Za-z0-9._~!$&+,;=:@%/-]+/g,h=new Set([".md",".yaml",".yml",".json",".txt"]);export function collectReferencedSkillPaths(t){const e=new Set;for(const n of t.matchAll(u)){const t=d(n[0]);t&&e.add(t)}return[...e].sort()}export function readReferencedSkillDocuments(t,e){const o=r(t),s=n(o),c=[];for(const t of collectReferencedSkillPaths(e)){const e=p(o,s,t);e&&c.push(e)}return c}export function readReferencedSkillDocument(t,e){const o=d(e.split("/").join(a));if(!o)return;const s=r(t);return p(s,n(s),o)}function d(t){const e=((t.startsWith("./")?t.slice(2):t).split("#",1)[0]??"").split("?",1)[0]??"",n=i(e);if(0!==n.length&&!c(n)&&!n.startsWith("..")&&"references"!==n&&n.startsWith(`references${a}`)&&h.has(s(n).toLowerCase()))return n}function p(r,s,i){const u=f(r,i);if(!t(u))return;const h=n(u),d=l(s,h);return d.startsWith("..")||c(d)||!o(h).isFile()?void 0:{relativePath:i.split(a).join("/"),path:h,content:e(h,"utf-8")}}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RegisteredAgent } from "../types/agent.ts";
|
|
2
|
+
export declare const SKILL_SOURCES: readonly ["agent", "project", "user", "config"];
|
|
3
|
+
export type SkillSource = (typeof SKILL_SOURCES)[number];
|
|
4
|
+
export interface SkillSummary {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
readonly source: SkillSource;
|
|
8
|
+
}
|
|
9
|
+
export interface LoadedSkill {
|
|
10
|
+
readonly summary: SkillSummary;
|
|
11
|
+
readonly content: string;
|
|
12
|
+
readonly referencePaths: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface SkillLibrary {
|
|
15
|
+
list(): readonly SkillSummary[];
|
|
16
|
+
load(name: string): LoadedSkill | undefined;
|
|
17
|
+
loadReference(name: string, referencePath: string): string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface CreateSkillLibraryOptions {
|
|
20
|
+
readonly agents?: readonly RegisteredAgent[];
|
|
21
|
+
readonly extraDirs?: readonly string[];
|
|
22
|
+
readonly cwd?: string;
|
|
23
|
+
readonly home?: string;
|
|
24
|
+
readonly onIssue?: (message: string) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare const SKILL_FILE_NAME = "SKILL.md";
|
|
27
|
+
export declare const SKILL_TOOL_ID = "roll__skill";
|
|
28
|
+
export declare function getAgentSkillPath(agent: RegisteredAgent): string;
|
|
29
|
+
export declare function resolveAgentSkillPath(agent: RegisteredAgent): string | undefined;
|
|
30
|
+
export declare function findProjectSkillsDir(cwd: string): string | undefined;
|
|
31
|
+
export declare function createSkillLibrary(options?: CreateSkillLibraryOptions): SkillLibrary;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as t,readdirSync as r,readFileSync as n,realpathSync as e,statSync as o}from"node:fs";import{homedir as s}from"node:os";import{basename as i,dirname as a,join as c,resolve as l}from"node:path";import m from"gray-matter";import{collectReferencedSkillPaths as u,readReferencedSkillDocument as f}from"./documents.js";export const SKILL_SOURCES=["agent","project","user","config"];export const SKILL_FILE_NAME="SKILL.md";export const SKILL_TOOL_ID="roll__skill";export function getAgentSkillPath(t){return l(t.installPath,"SKILL.md")}export function resolveAgentSkillPath(r){const n=getAgentSkillPath(r);return t(n)?n:void 0}export function findProjectSkillsDir(r){let n=l(r);for(;;){const r=c(n,".agents","skills");if(t(r))return r;const e=a(n);if(e===n)return;n=e}}function d(t){try{return o(t).isDirectory()}catch{return!1}}function h(t){try{return e(t)}catch{return}}function p(t,r,e,o){let s,i;try{s=n(t,"utf-8")}catch(r){return void o?.(`无法读取 ${t}: ${r instanceof Error?r.message:String(r)}`)}try{i=m(s).data}catch(r){return void o?.(`SKILL.md frontmatter 解析失败 ${t}: ${r instanceof Error?r.message:String(r)}`)}return{summary:{name:"string"==typeof i.name&&i.name.length>0?i.name:r,description:"string"==typeof i.description?i.description.trim():"",source:e},skillPath:t}}function y(n,e,o){if(!d(n))return[];const s=c(n,"SKILL.md");if(t(s)){const t=p(s,i(n),e,o);return t?[t]:[]}const a=[];let l;try{l=r(n)}catch{return[]}for(const r of l.sort()){const s=c(n,r);if(!d(s))continue;const i=c(s,"SKILL.md");if(!t(i))continue;const l=p(i,r,e,o);l&&a.push(l)}return a}function g(t){return t.map(t=>{const r=resolveAgentSkillPath(t),n=t.skillBody?.trim();return{summary:{name:t.skill.name,description:t.skill.description,source:"agent"},...void 0!==r?{skillPath:r}:{},...n&&n.length>0?{fallbackContent:n}:{}}})}export function createSkillLibrary(t={}){const r=t.onIssue,e=t.cwd??process.cwd(),o=t.home??s(),i=[...g(t.agents??[])],a=findProjectSkillsDir(e);void 0!==a&&i.push(...y(a,"project",r)),i.push(...y(c(o,".agents","skills"),"user",r));for(const n of t.extraDirs??[])i.push(...y(n,"config",r));const l=new Map,m=new Set;for(const t of i){const n=void 0!==t.skillPath?h(t.skillPath):void 0;void 0!==n&&m.has(n)||(l.has(t.summary.name)?r?.(`skill "${t.summary.name}" 重名,保留 ${l.get(t.summary.name)?.summary.source} 来源,跳过 ${t.summary.source} 来源`):(l.set(t.summary.name,t),void 0!==n&&m.add(n)))}return{list:()=>[...l.values()].map(t=>t.summary),load:t=>{const e=l.get(t);if(e){if(void 0!==e.skillPath){let t;try{t=n(e.skillPath,"utf-8")}catch(t){return void r?.(`无法读取 ${e.skillPath}: ${t instanceof Error?t.message:String(t)}`)}return{summary:e.summary,content:t,referencePaths:u(t)}}return void 0!==e.fallbackContent?{summary:e.summary,content:e.fallbackContent,referencePaths:[]}:{summary:e.summary,content:e.summary.description,referencePaths:[]}}},loadReference:(t,r)=>{const n=l.get(t);if(n&&void 0!==n.skillPath)return f(n.skillPath,r)?.content}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"types": "./dist/registry/*.d.ts",
|
|
28
28
|
"default": "./dist/registry/*.js"
|
|
29
29
|
},
|
|
30
|
+
"./skills/*": {
|
|
31
|
+
"types": "./dist/skills/*.d.ts",
|
|
32
|
+
"default": "./dist/skills/*.js"
|
|
33
|
+
},
|
|
30
34
|
"./tool-runtime/*": {
|
|
31
35
|
"types": "./dist/tool-runtime/*.d.ts",
|
|
32
36
|
"default": "./dist/tool-runtime/*.js"
|
|
@@ -78,7 +82,7 @@
|
|
|
78
82
|
"react": "19.2.7",
|
|
79
83
|
"yaml": "^2.7.0",
|
|
80
84
|
"zod": "^3.25.76",
|
|
81
|
-
"@roll-agent/runtime": "0.
|
|
85
|
+
"@roll-agent/runtime": "0.4.0"
|
|
82
86
|
},
|
|
83
87
|
"devDependencies": {
|
|
84
88
|
"@types/cross-spawn": "6.0.6",
|