@roll-agent/core 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cli/chat/ink/app.js +1 -1
  2. package/dist/cli/chat/ink/commands.js +1 -1
  3. package/dist/cli/chat/ink/display-width.d.ts +1 -0
  4. package/dist/cli/chat/ink/display-width.js +1 -0
  5. package/dist/cli/chat/ink/editor-keymap.d.ts +29 -0
  6. package/dist/cli/chat/ink/editor-keymap.js +1 -0
  7. package/dist/cli/chat/ink/history-item.js +1 -1
  8. package/dist/cli/chat/ink/line-buffer.d.ts +21 -0
  9. package/dist/cli/chat/ink/line-buffer.js +1 -0
  10. package/dist/cli/chat/ink/markdown.d.ts +0 -1
  11. package/dist/cli/chat/ink/markdown.js +1 -1
  12. package/dist/cli/chat/ink/run-ink-repl.d.ts +1 -1
  13. package/dist/cli/chat/ink/run-ink-repl.js +1 -1
  14. package/dist/cli/chat/ink/state.d.ts +5 -0
  15. package/dist/cli/chat/ink/state.js +1 -1
  16. package/dist/cli/chat/ink/status-line.js +1 -1
  17. package/dist/cli/chat/ink/text-prompt.d.ts +4 -0
  18. package/dist/cli/chat/ink/text-prompt.js +1 -1
  19. package/dist/cli/chat/ink/use-session.d.ts +1 -0
  20. package/dist/cli/chat/ink/use-session.js +1 -1
  21. package/dist/cli/commands/ask.js +1 -1
  22. package/dist/cli/commands/chat.js +1 -1
  23. package/dist/cli/commands/config-guidance.d.ts +1 -1
  24. package/dist/cli/commands/config-guidance.js +1 -1
  25. package/dist/cli/commands/config-setup.js +1 -1
  26. package/dist/cli/commands/run.js +1 -1
  27. package/dist/cli/utils/chat-renderer.js +1 -1
  28. package/dist/llm/providers.d.ts +12 -0
  29. package/dist/llm/providers.js +1 -1
  30. package/dist/mcp/client-manager.d.ts +5 -1
  31. package/dist/mcp/client-manager.js +1 -1
  32. package/dist/mcp/sampling-handler.d.ts +14 -2
  33. package/dist/mcp/sampling-handler.js +1 -1
  34. package/dist/skills/documents.js +1 -1
  35. package/dist/skills/library.d.ts +6 -0
  36. package/dist/skills/library.js +1 -1
  37. package/package.json +3 -2
@@ -1 +1 @@
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
+ 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 a}from"./use-session.js";import{HistoryItemView as m}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 c,buildSkillListLines as u,filterSlashEntries as g,parseSkillInvocation as k,SLASH_COMMANDS as v}from"./commands.js";import{bannerTextLine as j}from"../banner.js";import{cycleThinking as x}from"./thinking.js";function C(){return v.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(y){const{session:b,model:L,contextWindow:M,onUserSubmit:T,onExit:A}=y,$=y.availableSkills??[],{state:S,submit:w,compact:H,cancel:W,resolveConfirm:D,setDraft:E,setThinking:P,setAutoMode:R,toggleAutoMode:U,commitHistory:q}=a(b,{model:L,contextWindow:M,...y.initialHistory?{initialHistory:y.initialHistory}:{},...y.initialThinkingLevel?{initialThinkingLevel:y.initialThinkingLevel}:{},...y.onThinkingChange?{onThinkingChange:y.onThinkingChange}:{}}),z=n(()=>[...S.history],[S.history]),[B,F]=o(0),G="idle"===S.phase&&S.draft.startsWith("/"),I=G&&!0===S.draft.split(/\s+/).at(-1)?.startsWith("/"),J=I?g(S.draft,$):[],K=Math.max(J.length-1,0),N=Math.min(B,K);r((i,t)=>{"busy"===S.phase&&t.escape&&!t.meta?W():t.tab&&t.shift?U():t.meta&&"."===i?P(x(S.status.thinkingLevel,1)):t.meta&&","===i&&P(x(S.status.thinkingLevel,-1))});const O=(i,t)=>{const n=i.trim();0!==n.length?(T(n),w(n,t)):E("")},Q="confirm"===S.phase&&void 0!==S.pendingConfirm?t(p,{prompt:S.pendingConfirm.prompt,args:S.pendingConfirm.args,onDecide:D}):t(f,{value:S.draft,disabled:"idle"!==S.phase,slashActive:G,slashPopupActive:I,autoApprove:S.status.autoApprove,onChange:E,onSubmit:O,onSlashMove:i=>{F(t=>Math.min(Math.max(Math.min(t,K)+i,0),K))},onSlashComplete:()=>{const i=J[N];if(i){const t=S.draft.split(/\s+/);t[t.length-1]=i.name,E(`${t.join(" ")} `),F(0)}},onSlashRun:()=>{const t=S.draft.trim().split(/\s+/,1)[0]??"",n=v.some(i=>i.name===t),o=J[N];if(!n&&"skill"===o?.kind){const i=S.draft.split(/\s+/);return i[i.length-1]=o.name,E(`${i.join(" ")} `),void F(0)}(t=>{E("");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 P("off"===s?"off":"on"===s?"off"===r?"medium":r:"off"===r?"medium":"off");if("/effort"===e)return void("low"===s||"medium"===s||"high"===s?P(s):q({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===s?R(!0):"off"===s?R(!1):U());if("/skills"===e){const t=(process.stdout.columns||80)-2,[n,...o]=u($,t);return void q({kind:"banner",id:i(),lines:[j(n??""),...o.map(i=>j(i,{dim:!0}))]})}if("/help"===e)return void q({kind:"notice",id:i(),text:C()});if("/exit"===e)return void A();const a=k(n,$);if(a)return 0===a.prompt.length?void E(`${a.skills.map(i=>`/${i.name}`).join(" ")} `):void O(n,c(a));q({kind:"notice",id:i(),text:`未知命令 ${e}`})})(n?S.draft:o?.name??S.draft)}});return t(e,{flexDirection:"column"},t(s,{items:z,children:i=>{const n="user"===i.kind||"assistant"===i.kind,o="tool"===i.kind||"denied"===i.kind||"cancelled"===i.kind;return t(e,{key:i.id,marginTop:n?1:0,marginLeft:o?3:1},t(m,{item:i}))}}),t(e,{marginLeft:1},t(l,{live:S.live})),t(d,{status:S.status}),G?t(h,{matches:J,selected:N}):null,Q)}
@@ -1 +1 @@
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
+ import{SKILL_TOOL_ID as n}from"../../../skills/library.js";import{displayWidth as e}from"./display-width.js";const t=new Intl.Segmenter(void 0,{granularity:"grapheme"}),i="…";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 r(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,r){if(r<=0)return"";if(e(n)<=r)return n;const o=r-e(i);let a="",s=0;for(const{segment:i}of t.segment(n)){const n=e(i);if(s+n>o)break;a+=i,s+=n}return`${a}${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=r(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=[],r=new Set;for(;;){const n=/^(\/\S+)(\s*)/.exec(t);if(!n)break;const o=findSkillBySlashName(n[1]??"",e);if(!o)break;r.has(o.name)||(i.push(o),r.add(o.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 o=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const t=[...n].sort((n,e)=>n.name.localeCompare(e.name)),i=Math.min(Math.max(...t.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(t.length)} 个)· 用法: /<skill-name> 你的请求`,...t.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(i)} [${n.source}] ${r(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
@@ -0,0 +1 @@
1
+ export declare function displayWidth(text: string): number;
@@ -0,0 +1 @@
1
+ import t from"string-width";export function displayWidth(i){return t(i)}
@@ -0,0 +1,29 @@
1
+ import type { Key } from "ink";
2
+ import type { LineBufferState } from "./line-buffer.ts";
3
+ declare const EDITOR_KEY_BINDINGS: {
4
+ readonly leftArrow: "move-left";
5
+ readonly rightArrow: "move-right";
6
+ readonly "ctrl+leftArrow": "move-word-left";
7
+ readonly "meta+leftArrow": "move-word-left";
8
+ readonly "meta+b": "move-word-left";
9
+ readonly "ctrl+rightArrow": "move-word-right";
10
+ readonly "meta+rightArrow": "move-word-right";
11
+ readonly "meta+f": "move-word-right";
12
+ readonly home: "move-line-start";
13
+ readonly "ctrl+a": "move-line-start";
14
+ readonly end: "move-line-end";
15
+ readonly "ctrl+e": "move-line-end";
16
+ readonly upArrow: "move-up";
17
+ readonly downArrow: "move-down";
18
+ readonly backspace: "delete-backward";
19
+ readonly delete: "delete-forward";
20
+ readonly "ctrl+w": "delete-word-backward";
21
+ readonly "meta+backspace": "delete-word-backward";
22
+ readonly "ctrl+backspace": "delete-word-backward";
23
+ readonly "ctrl+u": "kill-line-start";
24
+ readonly "ctrl+k": "kill-line-end";
25
+ };
26
+ export type EditorCommand = (typeof EDITOR_KEY_BINDINGS)[keyof typeof EDITOR_KEY_BINDINGS];
27
+ export declare function resolveEditorCommand(input: string, key: Key): EditorCommand | undefined;
28
+ export declare function applyEditorCommand(command: EditorCommand, state: LineBufferState): LineBufferState;
29
+ export {};
@@ -0,0 +1 @@
1
+ import{deleteBackward as e,deleteForward as r,deleteWordBackward as o,killToLineEnd as t,killToLineStart as l,moveDown as d,moveLeft as n,moveRight as a,moveToLineEnd as i,moveToLineStart as w,moveUp as m,moveWordLeft as c,moveWordRight as v}from"./line-buffer.js";const f={leftArrow:"move-left",rightArrow:"move-right","ctrl+leftArrow":"move-word-left","meta+leftArrow":"move-word-left","meta+b":"move-word-left","ctrl+rightArrow":"move-word-right","meta+rightArrow":"move-word-right","meta+f":"move-word-right",home:"move-line-start","ctrl+a":"move-line-start",end:"move-line-end","ctrl+e":"move-line-end",upArrow:"move-up",downArrow:"move-down",backspace:"delete-backward",delete:"delete-forward","ctrl+w":"delete-word-backward","meta+backspace":"delete-word-backward","ctrl+backspace":"delete-word-backward","ctrl+u":"kill-line-start","ctrl+k":"kill-line-end"},s=["leftArrow","rightArrow","upArrow","downArrow","home","end","backspace","delete"];function k(e,r){const o=s.find(e=>r[e])??(1===e.length?e.toLowerCase():void 0);if(void 0!==o)return`${r.ctrl?"ctrl+":""}${r.meta?"meta+":""}${o}`}function u(e){return e in f}export function resolveEditorCommand(e,r){const o=k(e,r);if(void 0!==o&&u(o))return f[o]}const h={"move-left":n,"move-right":a,"move-word-left":c,"move-word-right":v,"move-line-start":w,"move-line-end":i,"move-up":m,"move-down":d,"delete-backward":e,"delete-forward":r,"delete-word-backward":o,"kill-line-start":l,"kill-line-end":t};export function applyEditorCommand(e,r){return h[e](r)}
@@ -1 +1 @@
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
+ import{createElement as r}from"react";import{Box as o,Text as e}from"ink";import{parseThinking as t}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,t)=>r(e,{key:String(t)},...o.spans.map((o,t)=>r(e,{key:String(t),...void 0!==o.color?{color:o.color}:{},...!0===o.dim?{dimColor:!0}:{}},o.text)))));case"user":return r(o,null,r(e,{color:"cyan",bold:!0},"▌ "),r(e,{color:"cyan"},i.text));case"assistant":return c(i.text)?r(o,null,r(e,{dimColor:!0},"⊘ "),r(e,{dimColor:!0},i.text.trim())):r(o,{flexDirection:"column"},...t(i.text).map((o,t)=>o.thinking?r(e,{key:String(t),dimColor:!0},o.text):r(n,{key:String(t),text:o.text})));case"tool":{const o=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return r(e,null,r(e,i.ok?{color:"green"}:{color:"red"},i.ok?"✓ ":"✗ "),r(l,{name:i.name}),o.length>0?r(e,{dimColor:!0},o):null)}case"denied":return r(e,{dimColor:!0},`⊘ ${i.name} ${i.label}`);case"cancelled":{const o=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return r(e,{color:"yellow"},"■ ",r(l,{name:i.name}),o.length>0?r(e,{dimColor:!0},o):null,r(e,{dimColor:!0}," 已中断"))}case"compaction":return r(e,{dimColor:!0},i.notice);case"notice":return r(o,null,r(e,{color:"yellow"},"⚠ "),r(e,{color:"yellow"},i.text));case"error":return r(o,null,r(e,{color:"red"},"✗ "),r(e,{color:"red"},i.message));default:return r(e,null,"")}}
@@ -0,0 +1,21 @@
1
+ export interface LineBufferState {
2
+ readonly value: string;
3
+ readonly cursor: number;
4
+ readonly goalColumn: number | undefined;
5
+ }
6
+ export declare function createLineBuffer(value: string, cursor?: number): LineBufferState;
7
+ export declare function graphemeAt(value: string, offset: number): string;
8
+ export declare function insertText(state: LineBufferState, text: string): LineBufferState;
9
+ export declare function deleteBackward(state: LineBufferState): LineBufferState;
10
+ export declare function deleteForward(state: LineBufferState): LineBufferState;
11
+ export declare function deleteWordBackward(state: LineBufferState): LineBufferState;
12
+ export declare function killToLineStart(state: LineBufferState): LineBufferState;
13
+ export declare function killToLineEnd(state: LineBufferState): LineBufferState;
14
+ export declare function moveLeft(state: LineBufferState): LineBufferState;
15
+ export declare function moveRight(state: LineBufferState): LineBufferState;
16
+ export declare function moveWordLeft(state: LineBufferState): LineBufferState;
17
+ export declare function moveWordRight(state: LineBufferState): LineBufferState;
18
+ export declare function moveToLineStart(state: LineBufferState): LineBufferState;
19
+ export declare function moveToLineEnd(state: LineBufferState): LineBufferState;
20
+ export declare function moveUp(state: LineBufferState): LineBufferState;
21
+ export declare function moveDown(state: LineBufferState): LineBufferState;
@@ -0,0 +1 @@
1
+ import{displayWidth as r}from"./display-width.js";const e=new Intl.Segmenter(void 0,{granularity:"grapheme"}),n=new Intl.Segmenter(void 0,{granularity:"word"});function t(r,e){return 0===e?0:r.lastIndexOf("\n",e-1)+1}function o(r,e){const n=t(r,e),o=r.indexOf("\n",e);return{start:n,end:-1===o?r.length:o}}function u(r){const[n]=e.segment(r);return n?.segment}function l(r,e){if(e>=r.length)return r.length;const n=u(r.slice(e));return void 0===n?r.length:e+n.length}function c(r,n){if(n<=0)return 0;const o=t(r,n);if(n===o)return o-1;let u=o;for(const t of e.segment(r.slice(o,n)))u=o+t.index;return u}function a(r,n,t){const o=Math.max(0,Math.min(n,r.length));if(0===o)return 0;if(o===r.length)return r.length;const u=e.segment(r).containing(o);return void 0===u||u.index===o?o:"backward"===t?u.index:u.index+u.segment.length}function i(r,e,n,t=""){const o=r.value.slice(0,e)+t+r.value.slice(n);return{value:o,cursor:a(o,e+t.length,"forward"),goalColumn:void 0}}function s(r){return!0===r.isWordLike}function v(r,e){let t=0;for(const o of n.segment(r.slice(0,e)))s(o)&&(t=o.index);return t}function f(r,e){for(const t of n.segment(r.slice(e)))if(s(t))return e+t.index+t.segment.length;return r.length}function g(e,n){return r(e.slice(t(e,n),n))}function d(n,t,o,u){let l=t,c=0;for(const a of e.segment(n.slice(t,o))){const e=r(a.segment);if(c+e>u)break;c+=e,l=t+a.index+a.segment.length}return l}function m(r){return void 0===r.goalColumn?r:{value:r.value,cursor:r.cursor,goalColumn:void 0}}export function createLineBuffer(r,e){return{value:r,cursor:a(r,Math.max(0,Math.min(e??r.length,r.length)),"backward"),goalColumn:void 0}}export function graphemeAt(r,e){return e<0||e>=r.length?"":u(r.slice(e))??""}export function insertText(r,e){return 0===e.length?m(r):i(r,r.cursor,r.cursor,e)}export function deleteBackward(r){if(0===r.cursor)return m(r);return i(r,c(r.value,r.cursor),r.cursor)}export function deleteForward(r){if(r.cursor>=r.value.length)return m(r);const e=l(r.value,r.cursor);return i(r,r.cursor,e)}export function deleteWordBackward(r){if(0===r.cursor)return m(r);return i(r,v(r.value,r.cursor),r.cursor)}export function killToLineStart(r){const{start:e}=o(r.value,r.cursor);return e===r.cursor?m(r):i(r,e,r.cursor)}export function killToLineEnd(r){const{end:e}=o(r.value,r.cursor);return r.cursor===e?e===r.value.length?m(r):i(r,r.cursor,e+1):i(r,r.cursor,e)}export function moveLeft(r){return 0===r.cursor?m(r):{value:r.value,cursor:c(r.value,r.cursor),goalColumn:void 0}}export function moveRight(r){return r.cursor>=r.value.length?m(r):{value:r.value,cursor:l(r.value,r.cursor),goalColumn:void 0}}export function moveWordLeft(r){return{value:r.value,cursor:v(r.value,r.cursor),goalColumn:void 0}}export function moveWordRight(r){return{value:r.value,cursor:f(r.value,r.cursor),goalColumn:void 0}}export function moveToLineStart(r){return{value:r.value,cursor:o(r.value,r.cursor).start,goalColumn:void 0}}export function moveToLineEnd(r){return{value:r.value,cursor:o(r.value,r.cursor).end,goalColumn:void 0}}export function moveUp(r){const{start:e}=o(r.value,r.cursor);if(0===e)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e-1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveDown(r){const{end:e}=o(r.value,r.cursor);if(e===r.value.length)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e+1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}
@@ -1,5 +1,4 @@
1
1
  import type { ReactElement } from "react";
2
- export declare function displayWidth(text: string): number;
3
2
  export declare function Markdown({ text }: {
4
3
  text: string;
5
4
  }): ReactElement;
@@ -1 +1 @@
1
- import{createElement as e}from"react";import{Box as t,Text as r}from"ink";import{marked as n}from"marked";const o={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x27;":"'"};function c(e){return e.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g,e=>o[e]??e)}function a(t,n){return void 0===t?[]:t.map((t,o)=>{const s=`${n}-${String(o)}`;switch(t.type){case"strong":return e(r,{key:s,bold:!0},...a(t.tokens,s));case"em":return e(r,{key:s,italic:!0},...a(t.tokens,s));case"codespan":return e(r,{key:s,color:"yellow"},c(t.text));case"del":return e(r,{key:s,strikethrough:!0},...a(t.tokens,s));case"link":return e(r,{key:s},e(r,{color:"cyan",underline:!0},t.text),e(r,{dimColor:!0},` (${t.href})`));case"br":return"\n";case"text":return void 0!==t.tokens?e(r,{key:s},...a(t.tokens,s)):c(t.text);default:return c(t.raw)}})}const s=[[4352,4447],[9001,9002],[9989,9989],[10060,10062],[11035,11093],[11904,12350],[12353,13311],[13312,19903],[19968,40959],[40960,42191],[44032,55203],[63744,64255],[65040,65049],[65072,65135],[65280,65376],[65504,65510],[126976,129791],[131072,196605],[196608,262141]],i=[[768,879],[8203,8207],[65024,65039]];function u(e,t){return t.some(([t,r])=>e>=t&&e<=r)}export function displayWidth(e){let t=0;for(const r of e){const e=r.codePointAt(0)??0;u(e,i)||(t+=u(e,s)?2:1)}return t}function l(e){return void 0===e?"":e.map(e=>{switch(e.type){case"strong":case"em":case"del":return l(e.tokens);case"codespan":return c(e.text);case"link":return`${e.text} (${e.href})`;case"br":return"\n";case"text":return void 0!==e.tokens?l(e.tokens):c(e.text);default:return c(e.raw)}}).join("")}function k(e){return l(e.tokens).split("\n").reduce((e,t)=>Math.max(e,displayWidth(t)),0)}function d(n,o,c,s){return e(t,{key:c},...n.map((n,i)=>e(t,{key:`${c}-${String(i)}`,width:(o[i]??0)+2},e(r,s?{bold:!0}:{},...a(n.tokens,`${c}-${String(i)}`)))))}function m(n,o){const c=n.header.map((e,t)=>{let r=k(e);for(const e of n.rows){const n=e[t];void 0!==n&&(r=Math.max(r,k(n)))}return r}),a=e(t,{key:`${o}-sep`},...c.map((n,c)=>e(t,{key:`${o}-sep-${String(c)}`,width:n+2},e(r,{dimColor:!0},"─".repeat(n)))));return e(t,{key:o,flexDirection:"column"},d(n.header,c,`${o}-h`,!0),a,...n.rows.map((e,t)=>d(e,c,`${o}-r${String(t)}`,!1)))}function p(n,o){switch(n.type){case"space":return null;case"heading":return e(r,{key:o,bold:!0,color:"cyan"},...a(n.tokens,o));case"paragraph":return e(r,{key:o},...a(n.tokens,o));case"blockquote":return e(t,{key:o},e(r,{color:"gray"},"│ "),e(t,{flexDirection:"column"},...y(n.tokens??[],o)));case"code":return e(t,{key:o,paddingLeft:2},e(r,{dimColor:!0},n.text));case"list":return e(t,{key:o,flexDirection:"column"},...n.items.map((c,a)=>{const s=`${o}-${String(a)}`,i=n.ordered?`${String(Number(n.start||1)+a)}. `:"• ";return e(t,{key:s},e(r,{color:"cyan"},i),e(t,{flexDirection:"column"},...y(c.tokens,s)))}));case"hr":return e(r,{key:o,dimColor:!0},"────────");case"table":return m(n,o);case"text":return void 0!==n.tokens?e(r,{key:o},...a(n.tokens,o)):e(r,{key:o},c(n.text));default:return e(r,{key:o},n.raw)}}function y(r,n){return r.map((e,t)=>p(e,`${n}-${String(t)}`)).filter(e=>null!==e).map((r,o)=>0===o?r:e(t,{key:`${n}-gap-${String(o)}`,marginTop:1},r))}export function Markdown({text:o}){let c;try{c=n.lexer(o)}catch{return e(r,null,o)}return e(t,{flexDirection:"column"},...y(c,"md"))}
1
+ import{createElement as e}from"react";import{Box as t,Text as r}from"ink";import{marked as n}from"marked";import{displayWidth as o}from"./display-width.js";const a={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x27;":"'"};function c(e){return e.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g,e=>a[e]??e)}function s(t,n){return void 0===t?[]:t.map((t,o)=>{const a=`${n}-${String(o)}`;switch(t.type){case"strong":return e(r,{key:a,bold:!0},...s(t.tokens,a));case"em":return e(r,{key:a,italic:!0},...s(t.tokens,a));case"codespan":return e(r,{key:a,color:"yellow"},c(t.text));case"del":return e(r,{key:a,strikethrough:!0},...s(t.tokens,a));case"link":return e(r,{key:a},e(r,{color:"cyan",underline:!0},t.text),e(r,{dimColor:!0},` (${t.href})`));case"br":return"\n";case"text":return void 0!==t.tokens?e(r,{key:a},...s(t.tokens,a)):c(t.text);default:return c(t.raw)}})}function i(e){return void 0===e?"":e.map(e=>{switch(e.type){case"strong":case"em":case"del":return i(e.tokens);case"codespan":return c(e.text);case"link":return`${e.text} (${e.href})`;case"br":return"\n";case"text":return void 0!==e.tokens?i(e.tokens):c(e.text);default:return c(e.raw)}}).join("")}function u(e){return i(e.tokens).split("\n").reduce((e,t)=>Math.max(e,o(t)),0)}function l(n,o,a,c){return e(t,{key:a},...n.map((n,i)=>e(t,{key:`${a}-${String(i)}`,width:(o[i]??0)+2},e(r,c?{bold:!0}:{},...s(n.tokens,`${a}-${String(i)}`)))))}function k(n,o){const a=n.header.map((e,t)=>{let r=u(e);for(const e of n.rows){const n=e[t];void 0!==n&&(r=Math.max(r,u(n)))}return r}),c=e(t,{key:`${o}-sep`},...a.map((n,a)=>e(t,{key:`${o}-sep-${String(a)}`,width:n+2},e(r,{dimColor:!0},"─".repeat(n)))));return e(t,{key:o,flexDirection:"column"},l(n.header,a,`${o}-h`,!0),c,...n.rows.map((e,t)=>l(e,a,`${o}-r${String(t)}`,!1)))}function m(n,o){switch(n.type){case"space":return null;case"heading":return e(r,{key:o,bold:!0,color:"cyan"},...s(n.tokens,o));case"paragraph":return e(r,{key:o},...s(n.tokens,o));case"blockquote":return e(t,{key:o},e(r,{color:"gray"},"│ "),e(t,{flexDirection:"column"},...d(n.tokens??[],o)));case"code":return e(t,{key:o,paddingLeft:2},e(r,{dimColor:!0},n.text));case"list":return e(t,{key:o,flexDirection:"column"},...n.items.map((a,c)=>{const s=`${o}-${String(c)}`,i=n.ordered?`${String(Number(n.start||1)+c)}. `:"• ";return e(t,{key:s},e(r,{color:"cyan"},i),e(t,{flexDirection:"column"},...d(a.tokens,s)))}));case"hr":return e(r,{key:o,dimColor:!0},"────────");case"table":return k(n,o);case"text":return void 0!==n.tokens?e(r,{key:o},...s(n.tokens,o)):e(r,{key:o},c(n.text));default:return e(r,{key:o},n.raw)}}function d(r,n){return r.map((e,t)=>m(e,`${n}-${String(t)}`)).filter(e=>null!==e).map((r,o)=>0===o?r:e(t,{key:`${n}-gap-${String(o)}`,marginTop:1},r))}export function Markdown({text:o}){let a;try{a=n.lexer(o)}catch{return e(r,null,o)}return e(t,{flexDirection:"column"},...d(a,"md"))}
@@ -1,7 +1,7 @@
1
1
  import type { AgentSession } from "@roll-agent/runtime";
2
2
  import type { ThinkingLevel } from "../../../llm/providers.ts";
3
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";
4
+ export declare const INK_HINTS = "/exit \u9000\u51FA \u00B7 Esc \u4E2D\u65AD \u00B7 / \u547D\u4EE4 \u00B7 Shift+Enter/Ctrl+J \u6362\u884C \u00B7 Alt+./Alt+, \u8C03\u63A8\u7406 \u00B7 Shift+Tab \u81EA\u52A8\u6279\u51C6";
5
5
  export interface InkReplStore {
6
6
  updateTitle(threadId: string, title: string): void;
7
7
  countMessages(threadId: string): number;
@@ -1 +1 @@
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
+ 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 a}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · Esc 中断 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(r,l,m,d){let p=!1,c=!m;const g=t=>{p=!0,c||(l.updateTitle(r.id,o(t)),c=!0)};d.banner||a.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:r,model:d.model,contextWindow:r.getContextWindow(),availableSkills:r.getSkillSummaries(),onUserSubmit:g,onExit:()=>{f.unmount()},initialHistory:[...h,...n(r.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(),await r.close(),m&&!p&&0===l.countMessages(r.id)&&l.deleteThread(r.id)}
@@ -30,6 +30,11 @@ export type HistoryItem = {
30
30
  readonly id: string;
31
31
  readonly name: string;
32
32
  readonly label: string;
33
+ } | {
34
+ readonly kind: "cancelled";
35
+ readonly id: string;
36
+ readonly name: string;
37
+ readonly args: string;
33
38
  } | {
34
39
  readonly kind: "compaction";
35
40
  readonly id: string;
@@ -1 +1 @@
1
- import{formatToolInput as t,formatApprovalDetails as e}from"../../utils/tool-format.js";import{GLYPHS as i}from"../../utils/glyphs.js";import{endsInsideThink as o}from"./thinking-text.js";const n=2e3,r={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function s(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,i){return{history:i?.history??[],draft:"",live:r,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 a(t){const e="auto"===t.reason?"自动压缩":"手动压缩",o=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`${i.compact} ${e}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${o}`:`${i.compact} ${e}:无需压缩`}const l=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function u(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=u(t);if(void 0!==e)return l.find(([t])=>e.startsWith(t))?.[1]}function c(t,e,i){const o=t.live.activeTools.find(t=>t.toolCallId===i.toolCallId),n=o?.name??`${i.agentName}.${i.toolName}`,r=o?.args??"",s=i.isError?denialLabel(i.output):void 0,a=void 0!==s?{kind:"denied",id:e,name:n,label:s}:{kind:"tool",id:e,name:n,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 d(i,l,u){switch(u.type){case"message-start":return{...i,live:{...i.live,thinking:!0}};case"text-delta":return{...i,live:{...i.live,thinking:!1,producedOutput:!0,streamingText:i.live.streamingText+u.delta}};case"tool-call":{const e=i.live.streamingText.length>0?[{kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}]:[];return{...i,history:[...i.history,...e],live:{...i.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:o(i.live.streamingText,i.live.thinkTagOpen),activeTools:[...i.live.activeTools,{toolCallId:u.toolCallId,name:`${u.agentName}.${u.toolName}`,args:t(u.input)}]}}}case"tool-output-delta":{const t=i.live.activeTools.map(t=>{if(t.toolCallId!==u.toolCallId)return t;const e=`${t.outputTail??""}${u.delta}`,i=e.length>n?e.slice(e.length-n):e;return{...t,outputTail:i}});return{...i,live:{...i.live,activeTools:t}}}case"tool-result":return c(i,l,u);case"confirmation-required":return{...i,phase:"confirm",pendingConfirm:{approvalId:u.approvalId,prompt:buildConfirmPrompt(u),args:e(u.input)}};case"compaction-start":return{...i,live:{...i.live,compacting:!0}};case"context-compacted":return{...i,live:{...i.live,compacting:!1},history:[...i.history,{kind:"compaction",id:l,notice:a(u)}]};case"message-finish":{const t=[];return i.live.streamingText.length>0?t.push({kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}):!i.live.producedOutput&&(u.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:l,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),u.stoppedAtStepLimit&&t.push({kind:"notice",id:`${l}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...i,history:[...i.history,...t],live:{...r},status:{...i.status,turnUsage:u.totalUsage,sessionUsage:u.sessionUsage,contextInputTokens:u.contextInputTokens,outputTokensPerSecond:u.outputTokensPerSecond}}}case"error":return{...i,history:[...i.history,{kind:"error",id:l,message:u.message}],live:{...r}};default:return i}}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:{...r},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:{...r},phase:"busy",pendingConfirm:void 0};case"session-event":return d(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...r}};default:return t}}
1
+ import{formatToolInput as t,formatApprovalDetails as e}from"../../utils/tool-format.js";import{GLYPHS as i}from"../../utils/glyphs.js";import{endsInsideThink as n}from"./thinking-text.js";const o=2e3,r={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function s(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,i){return{history:i?.history??[],draft:"",live:r,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 a(t){const e="auto"===t.reason?"自动压缩":"手动压缩",n=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`${i.compact} ${e}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${n}`:`${i.compact} ${e}:无需压缩`}const l=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function u(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=u(t);if(void 0!==e)return l.find(([t])=>e.startsWith(t))?.[1]}function c(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 d(i,l,u){switch(u.type){case"message-start":return{...i,live:{...i.live,thinking:!0}};case"text-delta":return{...i,live:{...i.live,thinking:!1,producedOutput:!0,streamingText:i.live.streamingText+u.delta}};case"tool-call":{const e=i.live.streamingText.length>0?[{kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}]:[];return{...i,history:[...i.history,...e],live:{...i.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:n(i.live.streamingText,i.live.thinkTagOpen),activeTools:[...i.live.activeTools,{toolCallId:u.toolCallId,name:`${u.agentName}.${u.toolName}`,args:t(u.input)}]}}}case"tool-output-delta":{const t=i.live.activeTools.map(t=>{if(t.toolCallId!==u.toolCallId)return t;const e=`${t.outputTail??""}${u.delta}`,i=e.length>o?e.slice(e.length-o):e;return{...t,outputTail:i}});return{...i,live:{...i.live,activeTools:t}}}case"tool-result":return c(i,l,u);case"confirmation-required":return{...i,phase:"confirm",pendingConfirm:{approvalId:u.approvalId,prompt:buildConfirmPrompt(u),args:e(u.input)}};case"compaction-start":return{...i,live:{...i.live,compacting:!0}};case"context-compacted":return{...i,live:{...i.live,compacting:!1},history:[...i.history,{kind:"compaction",id:l,notice:a(u)}]};case"message-finish":{const t=[];return i.live.streamingText.length>0?t.push({kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}):!i.live.producedOutput&&(u.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:l,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),u.stoppedAtStepLimit&&t.push({kind:"notice",id:`${l}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...i,history:[...i.history,...t],live:{...r},status:{...i.status,turnUsage:u.totalUsage,sessionUsage:u.sessionUsage,contextInputTokens:u.contextInputTokens,outputTokensPerSecond:u.outputTokensPerSecond}}}case"turn-cancelled":{const t=i.live.activeTools.map((t,e)=>({kind:"cancelled",id:`${l}-tool-${String(e)}`,name:t.name,args:t.args}));return{...i,history:[...i.history,...t,{kind:"notice",id:l,text:u.message}],live:{...r}}}case"error":return{...i,history:[...i.history,{kind:"error",id:l,message:u.message}],live:{...r}};default:return i}}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:{...r},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:{...r},phase:"busy",pendingConfirm:void 0};case"session-event":return d(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...r}};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 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)])))}
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 l,formatTurnUsage as c}from"../../utils/token-format.js";import{displayWidth as u}from"./display-width.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"}},d={off:"off",low:"low",medium:"med",high:"high"},f=["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} ${d[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 ${l(e.usedTokens)}/${l(e.contextWindow)}`,props:k[p(e.percentLeft)]});const v=c(e);if(void 0!==v){const o=[];void 0!==e.inputTokens&&o.push(`↑${l(e.inputTokens)}`),void 0!==e.outputTokens&&o.push(`↓${l(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 ${l(e.sessionTokens)}`,compact:`Σ${l(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 f){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)])))}
@@ -11,4 +11,8 @@ export interface TextPromptProps {
11
11
  readonly onSlashComplete: () => void;
12
12
  readonly onSlashRun: () => void;
13
13
  }
14
+ export declare function cursorPositionOf(lines: readonly string[], cursor: number): {
15
+ row: number;
16
+ col: number;
17
+ };
14
18
  export declare function TextPrompt(props: TextPromptProps): ReactElement;
@@ -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";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
+ import{createElement as r,useLayoutEffect as t,useRef as e,useState as n}from"react";import{Box as o,Text as l,useInput as i,usePaste as u,useStdout as c}from"ink";import{GLYPHS as s}from"../../utils/glyphs.js";import{applyEditorCommand as f,resolveEditorCommand as a}from"./editor-keymap.js";import{createLineBuffer as d,graphemeAt as h,insertText as m}from"./line-buffer.js";function p(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function cursorPositionOf(r,t){let e=t;for(const[t,n]of r.entries()){if(e<=n.length)return{row:t,col:e};e-=n.length+1}const n=r.length-1;return{row:n,col:r[n]?.length??0}}export function TextPrompt(v){const{value:g,disabled:w,slashActive:S,slashPopupActive:b,autoApprove:A,onChange:y,onSubmit:C}=v,{stdout:E}=c(),x=E.columns||80,[k]=n(()=>d(g)),j=e(k),[,P]=n(0);t(()=>{j.current.value!==g&&(j.current=d(g),P(r=>r+1))},[g]);const T=r=>{const t=j.current.value!==r.value;j.current=r,P(r=>r+1),t&&y(r.value)};i((r,t)=>{if(t.meta&&("."===r||","===r))return;if(p(r))return;if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void T(m(j.current,"\n"));if(t.return||r.includes("\r")){if(S)return void v.onSlashRun();const t=r.split("\r",1)[0]??"",e=t.length>0?m(j.current,t):j.current;return void C(e.value)}if(b){if(t.upArrow)return void v.onSlashMove(-1);if(t.downArrow)return void v.onSlashMove(1);if(t.tab&&!t.shift)return void v.onSlashComplete()}if(S&&t.escape)return void T(d(""));const e=a(r,t);void 0===e?t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&T(m(j.current,r)):T(f(e,j.current))},{isActive:!w}),u(r=>{T(m(j.current,r.replace(/\r\n?/g,"\n")))},{isActive:!w});const $=j.current,D=$.value.split("\n"),J=cursorPositionOf(D,$.cursor),M=r(o,{flexDirection:"column"},...D.map((t,e)=>{const n=0===e?r(l,{color:"green"},"› "):r(l,null," ");if(w)return r(o,{key:String(e)},n,r(l,{dimColor:!0},t));if(e!==J.row)return r(o,{key:String(e)},n,r(l,null,t));const i=h(t,J.col),u=t.slice(0,J.col),c=t.slice(J.col+i.length);return r(o,{key:String(e)},n,u.length>0?r(l,null,u):null,r(l,{inverse:!0},""===i?" ":i),c.length>0?r(l,null,c):null)})),O=w?"Esc 中断本轮":S?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":A?"Shift+Tab 关闭 · Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",L=r(o,{marginLeft:1},...A?[r(l,{color:"yellow"},`${s.auto} auto`),r(l,{dimColor:!0},` · ${O}`)]:[r(l,{dimColor:!0},O)]);return r(o,{flexDirection:"column",width:x},r(o,{borderStyle:"round",borderColor:w?"yellow":"cyan",paddingX:2},M),L)}
@@ -12,6 +12,7 @@ export interface UseSessionResult {
12
12
  readonly state: ChatUiState;
13
13
  readonly submit: (text: string, sendText?: string) => void;
14
14
  readonly compact: () => void;
15
+ readonly cancel: () => void;
15
16
  readonly resolveConfirm: (approved: boolean) => void;
16
17
  readonly setDraft: (value: string) => void;
17
18
  readonly setThinking: (level: ThinkingLevel) => 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,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
+ 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 c=32;function a(e){return e instanceof Error?e.message:String(e)}export function useSession(u,l){const[p,m]=n(r,o(l.model,l.contextWindow,{...l.initialHistory?{history:l.initialHistory}:{},...l.initialThinkingLevel?{thinkingLevel:l.initialThinkingLevel}:{}})),d=l.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,c));else s.debug(`chat.${o.stage} ${o.message}`)}catch(t){r(),m({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:a(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(()=>{f.current&&u.cancel()},[u]),T=t(e=>{y.current?.(e)},[]),b=t(e=>{m({type:"set-draft",value:e})},[]),w=t(e=>{m({type:"set-thinking",level:e}),d?.(e)},[d]),j=t(e=>{v.current=e,m({type:"set-auto",value:e}),e&&y.current?.(!0)},[]),H=t(()=>{j(!v.current)},[j]),I=t(e=>{m({type:"commit-history",item:e})},[]);return{state:p,submit:h,compact:k,cancel:x,resolveConfirm:T,setDraft:b,setThinking:w,setAutoMode:j,toggleAutoMode:H,commitHistory:I}}
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{getAgentEnv as t,getMissingAgentEnvRuntimeIssues as o,inspectAgentEnvRequirements as r}from"../../config/helpers.js";import{shouldSkipRuntimeReadinessForTool as n}from"../../config/runtime-env.js";import{createProviderModel as i,resolveLLMCall as a}from"../../llm/providers.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{AgentStore as m}from"../../registry/store.js";import{resolveTransportWithDevSpawnSpec as c}from"../../registry/dev-spawn.js";import{routeWithLLM as u}from"../../router/llm-router.js";import{extractToolInput as d}from"../../tool-runtime/argument-extractor.js";import{formatValidationIssuesMessage as g}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as v}from"../utils/agent-tools.js";import{log as j,redactToolArgsForLog as y}from"../utils/output.js";import{extractTextContent as $,isToolErrorResult as N}from"../utils/tool-results.js";const k=.5;function x(e){console.log(JSON.stringify(e,null,2))}function w(e){const s=$("object"==typeof e.result&&null!==e.result&&"content"in e.result?e.result.content:void 0);for(const e of s)console.log(e)}export default e({meta:{description:"用 LLM 从自然语言中选择 Agent 和 MCP tool"},args:{message:{type:"positional",description:"自然语言消息",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},async run({args:e}){const{config:C}=s(),h=new m(C.agents.dataDir).list();if(0===h.length)return j.error("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。"),void(process.exitCode=1);const M=s=>{if(e.json)x(s);else if("success"===s.status)w(s)},L=C.llm.defaultProvider,S=C.ask.llmModel??C.llm.defaultModel,b=C.llm.providers[L];if(!b)return j.error(`LLM provider "${L}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const{model:A,providerOptions:I}=a(L,S,b.apiKey,"structured-output",b.baseUrl);let O;j.info(`分析意图: "${e.message}"`);try{O=await u(e.message,h,A,I)}catch(e){const s={status:"failed",stage:"route",message:`LLM 路由失败: ${e instanceof Error?e.message:String(e)}`};return j.error(s.message),M(s),void(process.exitCode=1)}j.info(`路由决策: ${O.agentName}.${O.toolName} (置信度: ${String(O.confidence)})`);const T=C.ask.confirmThreshold??k;if(O.confidence<T){const e={status:"needs_confirmation",decision:O,message:`置信度 ${String(O.confidence)} 低于阈值 ${String(T)},跳过执行。 可使用 \`roll run ${O.agentName} ${O.toolName}\` 手动调用。`};return j.warn(e.message),M(e),void(process.exitCode=1)}const J=h.find(e=>e.skill.name===O.agentName);if(!J){const e={status:"failed",stage:"route",decision:O,message:`Agent "${O.agentName}" 未找到(LLM 返回了无效的 Agent 名称)`};return j.error(e.message),M(e),void(process.exitCode=1)}const P=i(L,C.llm.defaultModel,b.apiKey,b.baseUrl),E=new l;let K="connect";try{j.info(`连接 Agent "${J.skill.name}"...`);const s=t(C,J.skill.name),i=c(J),a=await E.connect(J.skill.name,i,J.installPath,{samplingModel:P,...s?{env:s}:{}}),l=v((await a.listTools()).tools),m=l.find(e=>e.name===O.toolName);if(!m){const e={status:"failed",stage:"route",decision:O,message:p(J.skill.name,O.toolName,l)};return j.error(e.message),M(e),void(process.exitCode=1)}K="execute";const u=await d(e.message,m,A,I),k={...O,input:u},x=r(J.skill.name,J.skill.env,C.agents.env),w=n(m.name)?[]:o(x),h=f(m,k.input,{runtimeIssues:w});if(!h.ok){const e={status:"needs_input",decision:k,validationIssues:h.issues,runtimeIssues:h.runtimeIssues,message:g(J.skill.name,O.toolName,h.issues,h.runtimeIssues)};return j.warn(e.message),M(e),void(process.exitCode=1)}j.info(`调用 ${J.skill.name}.${O.toolName}`),j.debug(`调用参数: ${JSON.stringify(y(k.input))}`);const L=await a.callTool({name:O.toolName,arguments:k.input});if(N(L)){const e={status:"failed",stage:"execute",decision:k,message:$(L.content).join("\n")||"Tool 调用失败"};return j.error(e.message),M(e),void(process.exitCode=1)}M({status:"success",decision:k,result:L}),j.success("调用完成")}catch(e){const s={status:"failed",stage:K,decision:O,message:e instanceof Error?e.message:String(e)};j.error(s.message),M(s),process.exitCode=1}finally{await E.disconnectAll()}}});
1
+ import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{getAgentEnv as t,getMissingAgentEnvRuntimeIssues as o,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{shouldSkipRuntimeReadinessForTool as i}from"../../config/runtime-env.js";import{resolveLLMCall as r,toSamplingConnectOptions as a}from"../../llm/providers.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{AgentStore as m}from"../../registry/store.js";import{resolveTransportWithDevSpawnSpec as c}from"../../registry/dev-spawn.js";import{routeWithLLM as u}from"../../router/llm-router.js";import{extractToolInput as d}from"../../tool-runtime/argument-extractor.js";import{formatValidationIssuesMessage as g}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as v}from"../utils/agent-tools.js";import{log as j,redactToolArgsForLog as y}from"../utils/output.js";import{extractTextContent as $,isToolErrorResult as k}from"../utils/tool-results.js";const N=.5;function x(e){console.log(JSON.stringify(e,null,2))}function w(e){const s=$("object"==typeof e.result&&null!==e.result&&"content"in e.result?e.result.content:void 0);for(const e of s)console.log(e)}export default e({meta:{description:"用 LLM 从自然语言中选择 Agent 和 MCP tool"},args:{message:{type:"positional",description:"自然语言消息",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},async run({args:e}){const{config:h}=s(),C=new m(h.agents.dataDir).list();if(0===C.length)return j.error("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。"),void(process.exitCode=1);const L=s=>{if(e.json)x(s);else if("success"===s.status)w(s)},M=h.llm.defaultProvider,S=h.ask.llmModel??h.llm.defaultModel,b=h.llm.providers[M];if(!b)return j.error(`LLM provider "${M}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const{model:A,providerOptions:I}=r(M,S,b.apiKey,"structured-output",b.baseUrl);let O;j.info(`分析意图: "${e.message}"`);try{O=await u(e.message,C,A,I)}catch(e){const s={status:"failed",stage:"route",message:`LLM 路由失败: ${e instanceof Error?e.message:String(e)}`};return j.error(s.message),L(s),void(process.exitCode=1)}j.info(`路由决策: ${O.agentName}.${O.toolName} (置信度: ${String(O.confidence)})`);const T=h.ask.confirmThreshold??N;if(O.confidence<T){const e={status:"needs_confirmation",decision:O,message:`置信度 ${String(O.confidence)} 低于阈值 ${String(T)},跳过执行。 可使用 \`roll run ${O.agentName} ${O.toolName}\` 手动调用。`};return j.warn(e.message),L(e),void(process.exitCode=1)}const J=C.find(e=>e.skill.name===O.agentName);if(!J){const e={status:"failed",stage:"route",decision:O,message:`Agent "${O.agentName}" 未找到(LLM 返回了无效的 Agent 名称)`};return j.error(e.message),L(e),void(process.exitCode=1)}const P=r(M,h.llm.defaultModel,b.apiKey,"sampling",b.baseUrl,h.runtime.thinkingLevel),E=new l;let K="connect";try{j.info(`连接 Agent "${J.skill.name}"...`);const s=t(h,J.skill.name),r=c(J),l=await E.connect(J.skill.name,r,J.installPath,{...a(P),...s?{env:s}:{}}),m=v((await l.listTools()).tools),u=m.find(e=>e.name===O.toolName);if(!u){const e={status:"failed",stage:"route",decision:O,message:p(J.skill.name,O.toolName,m)};return j.error(e.message),L(e),void(process.exitCode=1)}K="execute";const N=await d(e.message,u,A,I),x={...O,input:N},w=n(J.skill.name,J.skill.env,h.agents.env),C=i(u.name)?[]:o(w),M=f(u,x.input,{runtimeIssues:C});if(!M.ok){const e={status:"needs_input",decision:x,validationIssues:M.issues,runtimeIssues:M.runtimeIssues,message:g(J.skill.name,O.toolName,M.issues,M.runtimeIssues)};return j.warn(e.message),L(e),void(process.exitCode=1)}j.info(`调用 ${J.skill.name}.${O.toolName}`),j.debug(`调用参数: ${JSON.stringify(y(x.input))}`);const S=await l.callTool({name:O.toolName,arguments:x.input});if(k(S)){const e={status:"failed",stage:"execute",decision:x,message:$(S.content).join("\n")||"Tool 调用失败"};return j.error(e.message),L(e),void(process.exitCode=1)}L({status:"success",decision:x,result:S}),j.success("调用完成")}catch(e){const s={status:"failed",stage:K,decision:O,message:e instanceof Error?e.message:String(e)};j.error(s.message),L(s),process.exitCode=1}finally{await E.disconnectAll()}}});
@@ -1 +1 @@
1
- var e=this&&this.__rewriteRelativeImportExtension||function(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,o,s,n,i){return o?t?".jsx":".js":!s||n&&i?s+n+"."+i.toLowerCase()+"js":e}):e};import{defineCommand as t}from"citty";import{inspectLlmConfigReadiness as o}from"../../config/helpers.js";import{loadConfig as s}from"../../config/loader.js";import{resolveLLMCall as n,thinkingProviderOptions as i}from"../../llm/providers.js";import{createInterface as r}from"node:readline/promises";import a from"chalk";import l from"cli-table3";import{isDebugLogEnabled as c,log as d}from"../utils/output.js";import{ChatRenderer as u,clackConfirm as p}from"../utils/chat-renderer.js";import{titleFromMessage as m}from"../chat/title.js";import{buildBannerLines as g,renderBannerText as f}from"../chat/banner.js";import{getCurrentVersion as h}from"../utils/update-checker.js";import{buildSkillInvocationPrompt as v,formatSkillList as w,parseSkillInvocation as y}from"../chat/ink/commands.js";const b=import.meta.url.endsWith(".ts")?"ts":"js";function k(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}function C(e){console.log(JSON.stringify(e,null,2))}function x(e){d.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function S(e){d.warn(`skill 目录加载警告:${e}`)}function T(e,t){const o=y(t,e.getSkillSummaries());return o&&o.prompt.length>0?v(o):t}async function I(e,t,o){d.debug(`chat.repl input waiting · ${o}`),e.resume();try{const s=await e.question(t);return d.debug(`chat.repl input received · ${o} · chars=${String(s.length)}`),s}catch(e){const t=e instanceof Error?e.message:String(e);return void d.debug(`chat.repl input closed · ${o} · ${t}`)}finally{e.pause()}}async function j(){return import("@roll-agent/runtime")}async function $(t,o){const s=new URL(`./setup.${b}`,import.meta.url).href;return(await import(e(s))).runChatOnboarding({},{provider:t,model:o})}export function resolveChatLlmReadiness(e){return o(e,{provider:e.runtime.provider??e.llm.defaultProvider,model:e.runtime.model??e.llm.defaultModel})}async function N(e){const t=resolveChatLlmReadiness(e);if(!t.configured||!t.providerConfig)return d.error(t.message),void(process.exitCode=1);const o=t.providerConfig,s=t.provider,i=t.model,r=await j(),{ConversationEngine:a,ThreadStore:l,RuntimeServer:u,createStdioConnection:p}=r,{model:m,providerOptions:g}=n(s,i,o.apiKey,"chat",o.baseUrl,e.runtime.thinkingLevel),f=new l(e.runtime.threadsDir),h=new a({config:e,model:m,store:f,policy:k(r,e),maxSteps:e.runtime.maxSteps,...g?{providerOptions:g}:{},debugEvents:c(),onAgentBootstrapIssue:x,onSkillLibraryIssue:S}),v=p(process.stdin,process.stdout),w=new u(h,v);v.onClose(()=>{w.abortAll(),h.dispose().catch(()=>{}).finally(()=>{f.close(),process.exit(0)})}),d.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function R(e,t){const{ThreadStore:o}=await j(),s=new o(e.runtime.threadsDir);try{const e=s.listThreads();if(t){const t=e.map(e=>({...e,messageCount:s.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 l({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const t of e)o.push([t.id,t.title??"-",String(s.countMessages(t.id)),t.updatedAt]);console.log(o.toString())}finally{s.close()}}export async function runJsonTurn(e,t){const o=[],s=[],n=[],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":s.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":n.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,...s.length>0?{stepUsages:s}:{},...a?{totalUsage:a}:{},...l?{sessionUsage:l}:{},...void 0!==u?{contextWindow:u}:{},...void 0!==c?{contextInputTokens:c}:{},...n.length>0?{compactions:n}:{}}}export async function runRepl(e,t,o,s={input:process.stdin,output:process.stdout}){const n=r({input:s.input,output:s.output});n.on("SIGINT",()=>n.close());const i=s.confirm??(async e=>{d.debug("chat.repl input waiting · confirm"),n.pause();const t=await p(e);return d.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),l=new u(i,e.getContextWindow()),c=e.getSkillSummaries();d.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let g=!o,f=!1;try{for(;;){const o=await I(n,a.green("› "),"prompt");if(void 0===o)break;const s=o.trim();if(0===s.length)continue;if("exit"===s||"quit"===s)break;if("/compact"===s){d.debug("chat.repl manual compact requested");for await(const t of e.compact("manual"))await l.handle(t,e);d.debug("chat.repl manual compact completed");continue}if("/skills"===s){d.info(w(c,(process.stdout.columns||96)-2));continue}const i=y(s,c);if(i&&0===i.prompt.length){d.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");continue}const r=i?v(i):s;g||(t.updateTitle(e.id,m(s)),g=!0),f=!0,d.debug(`chat.repl send start · chars=${String(s.length)}`);for await(const t of e.send(r))await l.handle(t,e);d.debug("chat.repl send completed")}}finally{n.close(),o&&!f&&0===t.countMessages(e.id)&&t.deleteThread(e.id)}}export default t({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}){let{config:t}=s();if(e.server)return void await N(t);if(e.list)return void await R(t,e.json);let o=resolveChatLlmReadiness(t);if(!o.configured){!0===process.stdin.isTTY&&!0===process.stderr.isTTY&&!e.json&&await $(o.provider,o.model)&&(t=s().config,o=resolveChatLlmReadiness(t))}if(!o.configured||!o.providerConfig)return d.error(o.message),void(process.exitCode=1);const r=o.providerConfig,a=o.provider,l=o.model;if(e.json&&!e.message)return d.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const v=await j(),{ConversationEngine:w,ThreadStore:y}=v,{model:I,providerOptions:L}=n(a,l,r.apiKey,"chat",r.baseUrl,t.runtime.thinkingLevel),O=new y(t.runtime.threadsDir),U=new w({config:t,model:I,store:O,policy:k(v,t),maxSteps:t.runtime.maxSteps,...L?{providerOptions:L}:{},debugEvents:c(),onAgentBootstrapIssue:x,onSkillLibraryIssue:S,sessionExecEnabled:void 0===e.message});let E;try{let o;if(e.session)o=await U.resumeSession(e.session);else if(e.last){const e=O.listThreads()[0];if(!e)return d.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);o=await U.resumeSession(e.id)}else o=await U.createSession(e.message?{title:m(e.message)}:{});if(E=o,e.json&&e.message){const t=await runJsonTurn(o,T(o,e.message));return C(t),void("completed"!==t.status&&(process.exitCode=1))}if(d.info(`会话 ${o.id}`),t.runtime.compaction.enabled&&void 0===o.getContextWindow()&&d.warn(`未知模型 "${l}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const t=new u(p,o.getContextWindow());for await(const s of o.send(T(o,e.message)))await t.handle(s,o)}else{const s=!e.session&&!e.last,n=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode),r=await U.getContextSummary(),c={version:h(),model:l,agentCount:r.agentCount,skillCount:r.skillCount};let u=!1;if(n)try{const e=new URL(`../chat/ink/run-ink-repl.${b}`,import.meta.url).href,{runInkRepl:n}=await import(e);await n(o,O,s,{model:l,banner:c,initialThinkingLevel:t.runtime.thinkingLevel,onThinkingChange:e=>o.setProviderOptions(i(a,l,e))}),u=!0}catch(e){d.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}u||(process.stderr.write(`${f(g(c,process.stdout.columns||80))}\n`),await runRepl(o,O,s))}}catch(e){d.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{E?.abort(),await U.dispose(),O.close()}}});
1
+ var e=this&&this.__rewriteRelativeImportExtension||function(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,o,s,n,i){return o?t?".jsx":".js":!s||n&&i?s+n+"."+i.toLowerCase()+"js":e}):e};import{defineCommand as t}from"citty";import{inspectLlmConfigReadiness as o}from"../../config/helpers.js";import{loadConfig as s}from"../../config/loader.js";import{resolveLLMCall as n,thinkingProviderOptions as i}from"../../llm/providers.js";import{createInterface as r}from"node:readline/promises";import a from"chalk";import l from"cli-table3";import{isDebugLogEnabled as c,log as d}from"../utils/output.js";import{ChatRenderer as u,clackConfirm as p}from"../utils/chat-renderer.js";import{titleFromMessage as m}from"../chat/title.js";import{buildBannerLines as g,renderBannerText as f}from"../chat/banner.js";import{getCurrentVersion as h}from"../utils/update-checker.js";import{buildSkillInvocationPrompt as v,formatSkillList as w,parseSkillInvocation as y}from"../chat/ink/commands.js";const b=import.meta.url.endsWith(".ts")?"ts":"js";function k(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}function C(e){console.log(JSON.stringify(e,null,2))}function x(e){d.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function S(e){d.warn(`skill 目录加载警告:${e}`)}function T(e,t){const o=y(t,e.getSkillSummaries());return o&&o.prompt.length>0?v(o):t}async function I(e,t,o){d.debug(`chat.repl input waiting · ${o}`),e.resume();try{const s=await e.question(t);return d.debug(`chat.repl input received · ${o} · chars=${String(s.length)}`),s}catch(e){const t=e instanceof Error?e.message:String(e);return void d.debug(`chat.repl input closed · ${o} · ${t}`)}finally{e.pause()}}async function j(){return import("@roll-agent/runtime")}async function $(t,o){const s=new URL(`./setup.${b}`,import.meta.url).href;return(await import(e(s))).runChatOnboarding({},{provider:t,model:o})}export function resolveChatLlmReadiness(e){return o(e,{provider:e.runtime.provider??e.llm.defaultProvider,model:e.runtime.model??e.llm.defaultModel})}async function N(e){const t=resolveChatLlmReadiness(e);if(!t.configured||!t.providerConfig)return d.error(t.message),void(process.exitCode=1);const o=t.providerConfig,s=t.provider,i=t.model,r=await j(),{ConversationEngine:a,ThreadStore:l,RuntimeServer:u,createStdioConnection:p}=r,{model:m,providerOptions:g}=n(s,i,o.apiKey,"chat",o.baseUrl,e.runtime.thinkingLevel),f=new l(e.runtime.threadsDir),h=new a({config:e,model:m,store:f,policy:k(r,e),maxSteps:e.runtime.maxSteps,...g?{providerOptions:g}:{},debugEvents:c(),onAgentBootstrapIssue:x,onSkillLibraryIssue:S}),v=p(process.stdin,process.stdout),w=new u(h,v);v.onClose(()=>{w.abortAll().then(()=>h.dispose()).catch(()=>{}).finally(()=>{f.close(),process.exit(0)})}),d.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function R(e,t){const{ThreadStore:o}=await j(),s=new o(e.runtime.threadsDir);try{const e=s.listThreads();if(t){const t=e.map(e=>({...e,messageCount:s.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 l({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const t of e)o.push([t.id,t.title??"-",String(s.countMessages(t.id)),t.updatedAt]);console.log(o.toString())}finally{s.close()}}export async function runJsonTurn(e,t){const o=[],s=[],n=[],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":s.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":n.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"turn-cancelled":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,...s.length>0?{stepUsages:s}:{},...a?{totalUsage:a}:{},...l?{sessionUsage:l}:{},...void 0!==u?{contextWindow:u}:{},...void 0!==c?{contextInputTokens:c}:{},...n.length>0?{compactions:n}:{}}}export async function runRepl(e,t,o,s={input:process.stdin,output:process.stdout}){const n=r({input:s.input,output:s.output});n.on("SIGINT",()=>n.close());const i=s.confirm??(async e=>{d.debug("chat.repl input waiting · confirm"),n.pause();const t=await p(e);return d.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),l=new u(i,e.getContextWindow()),c=e.getSkillSummaries();d.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let g=!o,f=!1;try{for(;;){const o=await I(n,a.green("› "),"prompt");if(void 0===o)break;const s=o.trim();if(0===s.length)continue;if("exit"===s||"quit"===s)break;if("/compact"===s){d.debug("chat.repl manual compact requested");for await(const t of e.compact("manual"))await l.handle(t,e);d.debug("chat.repl manual compact completed");continue}if("/skills"===s){d.info(w(c,(process.stdout.columns||96)-2));continue}const i=y(s,c);if(i&&0===i.prompt.length){d.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");continue}const r=i?v(i):s;g||(t.updateTitle(e.id,m(s)),g=!0),f=!0,d.debug(`chat.repl send start · chars=${String(s.length)}`);for await(const t of e.send(r))await l.handle(t,e);d.debug("chat.repl send completed")}}finally{n.close(),o&&!f&&0===t.countMessages(e.id)&&t.deleteThread(e.id)}}export default t({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}){let{config:t}=s();if(e.server)return void await N(t);if(e.list)return void await R(t,e.json);let o=resolveChatLlmReadiness(t);if(!o.configured){!0===process.stdin.isTTY&&!0===process.stderr.isTTY&&!e.json&&await $(o.provider,o.model)&&(t=s().config,o=resolveChatLlmReadiness(t))}if(!o.configured||!o.providerConfig)return d.error(o.message),void(process.exitCode=1);const r=o.providerConfig,a=o.provider,l=o.model;if(e.json&&!e.message)return d.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const v=await j(),{ConversationEngine:w,ThreadStore:y}=v,{model:I,providerOptions:L}=n(a,l,r.apiKey,"chat",r.baseUrl,t.runtime.thinkingLevel),O=new y(t.runtime.threadsDir),U=new w({config:t,model:I,store:O,policy:k(v,t),maxSteps:t.runtime.maxSteps,...L?{providerOptions:L}:{},debugEvents:c(),onAgentBootstrapIssue:x,onSkillLibraryIssue:S,sessionExecEnabled:void 0===e.message});let E;try{let o;if(e.session)o=await U.resumeSession(e.session);else if(e.last){const e=O.listThreads()[0];if(!e)return d.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);o=await U.resumeSession(e.id)}else o=await U.createSession(e.message?{title:m(e.message)}:{});if(E=o,e.json&&e.message){const t=await runJsonTurn(o,T(o,e.message));return C(t),void("completed"!==t.status&&(process.exitCode=1))}if(d.info(`会话 ${o.id}`),t.runtime.compaction.enabled&&void 0===o.getContextWindow()&&d.warn(`未知模型 "${l}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const t=new u(p,o.getContextWindow());for await(const s of o.send(T(o,e.message)))await t.handle(s,o)}else{const s=!e.session&&!e.last,n=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode),r=await U.getContextSummary(),c={version:h(),model:l,agentCount:r.agentCount,skillCount:r.skillCount};let u=!1;if(n)try{const e=new URL(`../chat/ink/run-ink-repl.${b}`,import.meta.url).href,{runInkRepl:n}=await import(e);await n(o,O,s,{model:l,banner:c,initialThinkingLevel:t.runtime.thinkingLevel,onThinkingChange:e=>o.setProviderOptions(i(a,l,e))}),u=!0}catch(e){d.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}u||(process.stderr.write(`${f(g(c,process.stdout.columns||80))}\n`),await runRepl(o,O,s))}}catch(e){d.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await(E?.close()),await U.dispose(),O.close()}}});
@@ -117,7 +117,7 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
117
117
  }, {
118
118
  readonly path: "runtime.shell.session.enabled";
119
119
  readonly title: "Chat 会话式长命令执行";
120
- readonly purpose: "开启后在 POSIX shell 后端注册 `roll__exec_command` + `roll__exec_poll` 工具:命令在后台会话执行、跨轮存活,模型轮询进度并读取退出码,适合超过单轮超时的长脚本。Windows 原生 session exec 暂未支持。仅交互 REPL 与 `--server` 长驻模式注册(单条消息 / `--json` 单轮会话随进程结束,不提供该工具);`--server` 下 `exec_command` 需 `runtime.approval.overrides` 里 `roll.exec_command: auto` 显式授权。";
120
+ readonly purpose: "开启后在 macOS/Linux POSIX shell Windows PowerShell 7+ 后端注册 `roll__exec_command`、`roll__exec_poll`、`roll__exec_list` 工具:命令在后台会话执行、跨轮存活,模型可列出会话、轮询进度并读取退出码,适合超过单轮超时的长脚本。仅交互 REPL 与 `--server` 长驻模式注册(单条消息 / `--json` 单轮会话随进程结束,不提供这些工具);`--server` 下 `exec_command` 需 `runtime.approval.overrides` 里 `roll.exec_command: auto` 显式授权。";
121
121
  readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\uFF08\u5173\u95ED\uFF09\u3002\u9700\u540C\u65F6\u5F00\u542F runtime.shell.enabled\uFF1B\u80CC\u666F\u4F1A\u8BDD\u968F roll chat \u8FDB\u7A0B\u9000\u51FA\u800C\u7EC8\u6B62\u3002`;
122
122
  readonly example: "runtime:\n shell:\n enabled: true\n session:\n enabled: true\n approval:\n overrides:\n roll.exec_command: auto";
123
123
  readonly setupCommand: "roll config setup shell";
@@ -1 +1 @@
1
- import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as l}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"runtime.approval.default",title:"Chat 工具确认默认策略",purpose:"`roll chat` 调用工具时的默认确认策略。`guarded` 使用内置读写启发式,`auto` 默认放行非破坏性工具,`deny` 默认拒绝。",defaultBehavior:`默认值为 \`${e.runtime.approval.default}\`。此配置不影响 \`roll ask\`。`,example:`runtime:\n approval:\n default: ${e.runtime.approval.default}`},{path:"runtime.approval.overrides",title:"Chat 工具确认精确覆盖",purpose:"`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。",defaultBehavior:"未配置时回退到 `runtime.approval.default`。",example:"runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto"},{path:"runtime.shell.enabled",title:"Chat 内建 shell 工具",purpose:"开启后 `roll chat` 注册内建 shell 工具:macOS/Linux 为 `roll__bash`,Windows 原生为 `roll__powershell`(需 PowerShell 7+)。命令继承 roll 进程环境变量;默认确认策略可由 `runtime.approval` 及精确 override 覆盖。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.enabled)}\`(关闭)。Windows 未检测到 pwsh 7+ 时会跳过注册。`,example:"runtime:\n shell:\n enabled: true",setupCommand:"roll config setup shell"},{path:"runtime.shell.auto-approve-safe",title:"只读命令免确认",purpose:"开启后用内建规则分类器识别 POSIX known-safe 只读命令(如 `ls`、`cat`、`git status`),在 `guarded`/`auto` 策略下免确认执行;dangerous(`rm -rf`、`sudo`)与无法识别的命令默认仍需确认。工具级显式 approval override 优先;Windows PowerShell 当前全部按 unknown 处理。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.autoApproveSafe)}\`。关闭后 POSIX 回归默认逐条确认;Windows PowerShell 默认逐条确认,显式 approval override 可覆盖。`,example:"runtime:\n shell:\n enabled: true\n auto-approve-safe: false",setupCommand:"roll config setup shell"},{path:"runtime.shell.session.enabled",title:"Chat 会话式长命令执行",purpose:"开启后在 POSIX shell 后端注册 `roll__exec_command` + `roll__exec_poll` 工具:命令在后台会话执行、跨轮存活,模型轮询进度并读取退出码,适合超过单轮超时的长脚本。Windows 原生 session exec 暂未支持。仅交互 REPL 与 `--server` 长驻模式注册(单条消息 / `--json` 单轮会话随进程结束,不提供该工具);`--server` 下 `exec_command` 需 `runtime.approval.overrides` 里 `roll.exec_command: auto` 显式授权。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.session.enabled)}\`(关闭)。需同时开启 runtime.shell.enabled;背景会话随 roll chat 进程退出而终止。`,example:"runtime:\n shell:\n enabled: true\n session:\n enabled: true\n approval:\n overrides:\n roll.exec_command: auto",setupCommand:"roll config setup shell"},{path:"skills.dirs",title:"Chat 额外 skill 目录",purpose:"`roll chat` 除自动发现项目与用户级 `.agents/skills`(`npx skills add` 的标准安装位置)及已注册 Agent 的 SKILL.md 外,额外加载的 skill 目录。目录可以直接包含 SKILL.md,也可以是多个 skill 子目录的集合。",defaultBehavior:"默认为空。标准路径 `<项目>/.agents/skills` 与 `~/.agents/skills` 始终自动发现。",example:"skills:\n dirs:\n - ./openclaw-roll-core-skill-template"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=l(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>t(e.path,r))}function t(e,t){const r=l(e.split(".")).join(".");if(r===t)return!0;const a=r.split("."),n=t.split(".");return a.length===n.length&&a.every((e,l)=>e.startsWith("<")||e===n[l])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
1
+ import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as l}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"runtime.approval.default",title:"Chat 工具确认默认策略",purpose:"`roll chat` 调用工具时的默认确认策略。`guarded` 使用内置读写启发式,`auto` 默认放行非破坏性工具,`deny` 默认拒绝。",defaultBehavior:`默认值为 \`${e.runtime.approval.default}\`。此配置不影响 \`roll ask\`。`,example:`runtime:\n approval:\n default: ${e.runtime.approval.default}`},{path:"runtime.approval.overrides",title:"Chat 工具确认精确覆盖",purpose:"`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。",defaultBehavior:"未配置时回退到 `runtime.approval.default`。",example:"runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto"},{path:"runtime.shell.enabled",title:"Chat 内建 shell 工具",purpose:"开启后 `roll chat` 注册内建 shell 工具:macOS/Linux 为 `roll__bash`,Windows 原生为 `roll__powershell`(需 PowerShell 7+)。命令继承 roll 进程环境变量;默认确认策略可由 `runtime.approval` 及精确 override 覆盖。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.enabled)}\`(关闭)。Windows 未检测到 pwsh 7+ 时会跳过注册。`,example:"runtime:\n shell:\n enabled: true",setupCommand:"roll config setup shell"},{path:"runtime.shell.auto-approve-safe",title:"只读命令免确认",purpose:"开启后用内建规则分类器识别 POSIX known-safe 只读命令(如 `ls`、`cat`、`git status`),在 `guarded`/`auto` 策略下免确认执行;dangerous(`rm -rf`、`sudo`)与无法识别的命令默认仍需确认。工具级显式 approval override 优先;Windows PowerShell 当前全部按 unknown 处理。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.autoApproveSafe)}\`。关闭后 POSIX 回归默认逐条确认;Windows PowerShell 默认逐条确认,显式 approval override 可覆盖。`,example:"runtime:\n shell:\n enabled: true\n auto-approve-safe: false",setupCommand:"roll config setup shell"},{path:"runtime.shell.session.enabled",title:"Chat 会话式长命令执行",purpose:"开启后在 macOS/Linux POSIX shell Windows PowerShell 7+ 后端注册 `roll__exec_command`、`roll__exec_poll`、`roll__exec_list` 工具:命令在后台会话执行、跨轮存活,模型可列出会话、轮询进度并读取退出码,适合超过单轮超时的长脚本。仅交互 REPL 与 `--server` 长驻模式注册(单条消息 / `--json` 单轮会话随进程结束,不提供这些工具);`--server` 下 `exec_command` 需 `runtime.approval.overrides` 里 `roll.exec_command: auto` 显式授权。",defaultBehavior:`默认值为 \`${String(e.runtime.shell.session.enabled)}\`(关闭)。需同时开启 runtime.shell.enabled;背景会话随 roll chat 进程退出而终止。`,example:"runtime:\n shell:\n enabled: true\n session:\n enabled: true\n approval:\n overrides:\n roll.exec_command: auto",setupCommand:"roll config setup shell"},{path:"skills.dirs",title:"Chat 额外 skill 目录",purpose:"`roll chat` 除自动发现项目与用户级 `.agents/skills`(`npx skills add` 的标准安装位置)及已注册 Agent 的 SKILL.md 外,额外加载的 skill 目录。目录可以直接包含 SKILL.md,也可以是多个 skill 子目录的集合。",defaultBehavior:"默认为空。标准路径 `<项目>/.agents/skills` 与 `~/.agents/skills` 始终自动发现。",example:"skills:\n dirs:\n - ./openclaw-roll-core-skill-template"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=l(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>t(e.path,r))}function t(e,t){const r=l(e.split(".")).join(".");if(r===t)return!0;const a=r.split("."),n=t.split(".");return a.length===n.length&&a.every((e,l)=>e.startsWith("<")||e===n[l])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
@@ -1 +1 @@
1
- import{existsSync as e,readFileSync as t,writeFileSync as r}from"node:fs";import{homedir as n}from"node:os";import{resolve as a}from"node:path";import{stringify as i}from"yaml";import{DEFAULT_CONFIG as o,DEFAULT_LLM_MODELS as s,DEFAULT_LLM_PROVIDER as l,LLM_PROVIDER_OPTIONS as c}from"../../config/defaults.js";import{inspectAgentEnvRequirements as u}from"../../config/helpers.js";import{loadAgentsConfig as m,parseConfigDocument as f,resolveConfigPath as d,validateConfigText as g}from"../../config/loader.js";import{encodePathToYaml as p}from"../../config/key-codec.js";import{detectKnownConfigMigrations as h,formatConfigMigrationError as v}from"../../config/migration.js";import{AgentStore as w}from"../../registry/store.js";import{flattenAgentEnvDeclarations as y,isSecretEnvName as k}from"./config-guidance.js";import{ConfigSetupCancelledError as $,clackPromptAdapter as b}from"./config-prompts.js";const A=/\$\{[^}]+\}/u;export async function runConfigSetup(e,t,r=b){try{r.intro("Roll 配置向导"),q();switch(await x(e,r)){case"llm":r.outro(await setupLlm(r));break;case"install":r.outro(await setupInstall(r));break;case"agent":r.outro(await setupAgentEnv(t,r));break;case"shell":r.outro(await setupShell(r))}}catch(e){if(e instanceof $)return void(process.exitCode=1);throw e}}async function x(e,t){if(void 0===e)return t.select({message:"要配置哪一类内容?",options:[{value:"llm",label:"LLM",hint:"默认模型和 provider key"},{value:"install",label:"Install network",hint:"npm registry、重试和超时"},{value:"agent",label:"Agent env",hint:"配置某个 Agent 需要的环境变量"},{value:"shell",label:"Chat shell 工具",hint:"roll chat 内建本机命令执行能力(默认关闭)"}]});if("llm"===e||"install"===e||"agent"===e||"shell"===e)return e;if("bash"===e)return"shell";throw new Error(`未知 setup 模块: ${e}。可用: llm, install, agent, shell`)}export async function setupLlm(e){q();const t=await e.select({message:"选择默认 LLM provider",options:c.map(e=>({value:e,label:e,hint:s[e]})),initialValue:l}),r=await e.text({message:"默认 model",defaultValue:s[t],required:!0}),n=await e.password({message:`${t} API key(可直接输入真实 key,也可输入 \${ENV_VAR})`,required:!0}),a=await e.text({message:"自定义 base-url(可选,留空则不写)",required:!1}),i=q();return N(i.document,["llm","defaultProvider"],t),N(i.document,["llm","defaultModel"],r),N(i.document,["llm","providers",t,"apiKey"],n),a.trim().length>0?N(i.document,["llm","providers",t,"baseUrl"],a.trim()):O(i.document,["llm","providers",t,"baseUrl"]),R(i),D(e,`${t} API key`,n),`已配置 LLM: ${t}/${r}(写入 ${i.configPath})`}export async function setupInstall(e){q();const t=await e.select({message:"当前安装/更新网络更接近哪种场景?",options:[{value:"default-network",label:"默认网络 / 海外网络",hint:"不设置 registry"},{value:"china-dev",label:"国内开发机",hint:"使用 https://registry.npmmirror.com"},{value:"private-registry",label:"企业内网 / 私有 registry"},{value:"advanced",label:"高级自定义"}]}),r=await S(t,e),n=q();switch(r.scenario){case"default-network":P(n.document),O(n.document,["install","registry"]);break;case"china-dev":P(n.document),N(n.document,["install","registry"],"https://registry.npmmirror.com");break;case"private-registry":P(n.document),N(n.document,["install","registry"],r.registry);break;case"advanced":{const{registry:e,fetchRetries:t,preferOffline:a,networkTimeoutMs:i}=r.values;N(n.document,["install","fetchRetries"],t),N(n.document,["install","preferOffline"],a),N(n.document,["install","networkTimeoutMs"],i),e.length>0?N(n.document,["install","registry"],e):O(n.document,["install","registry"]);break}}return R(n),`已配置 install 网络参数(写入 ${n.configPath})`}export async function setupShell(e,t=process.platform){q();const r=await e.confirm({message:"启用 roll chat 内建 shell 工具(允许模型在本机执行命令;默认确认策略受 runtime.approval 及显式 override 控制)?",initialValue:!1});let n=!0,a=!1;const i="win32"===t;r&&i&&e.warn("Windows 原生 shell 当前仅支持 PowerShell 7 one-shot;安全命令自动放行和 session exec 暂不生效。默认每条命令都需确认;显式 approval override(roll.powershell: auto)可覆盖此默认策略。"),r&&!i&&(n=await e.confirm({message:"安全只读命令(ls/grep 等)自动放行,无需逐条确认?",initialValue:!0}),a=await e.confirm({message:"启用长跑命令会话(session exec,供 dev server 等常驻进程使用)?",initialValue:!1}));const o=q();N(o.document,["runtime","shell","enabled"],r),r&&!i&&(N(o.document,["runtime","shell","autoApproveSafe"],n),N(o.document,["runtime","shell","session","enabled"],a)),R(o);return`已${r?"启用":"禁用"} chat shell 工具${r&&i?";Windows 当前仅启用 PowerShell one-shot,默认逐条确认(显式 approval override 可覆盖)":""}(写入 ${o.configPath})`}export async function setupBash(e){return setupShell(e)}async function S(e,t){switch(e){case"default-network":case"china-dev":return{scenario:e};case"private-registry":return{scenario:e,registry:(await t.text({message:"私有 registry URL",placeholder:"https://registry.example.com",required:!0})).trim()};case"advanced":return{scenario:e,values:await V(t)}}}async function V(e){const t=await e.text({message:"npm registry(留空则不写)",placeholder:"https://registry.npmmirror.com"}),r=await e.text({message:"fetch-retries",defaultValue:"3",required:!0,validate:T(0,10)}),n=await e.confirm({message:"是否启用 prefer-offline?",initialValue:!1}),a=await e.text({message:"network-timeout-ms",defaultValue:"120000",required:!0,validate:T(1e4,Number.MAX_SAFE_INTEGER)});return{registry:t.trim(),fetchRetries:Number.parseInt(r,10),preferOffline:n,networkTimeoutMs:Number.parseInt(a,10)}}export async function setupAgentEnv(e,t){q();const{agentsConfig:r}=m(),n=new w(r.dataDir),a=await E(n.list(),e,t),i=u(a.skill.name,a.skill.env,r.env);if(!i)throw new Error(`Agent "${a.skill.name}" 未声明环境变量需求。`);const o={...r.env?.[a.skill.name]??{}},s=y(a.skill.env),l=new Map(i.items.map(e=>[e.name,e]));for(const e of s.filter(e=>e.required)){const r=l.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}if(await t.confirm({message:"是否配置可选环境变量?",initialValue:!1}))for(const e of s.filter(e=>!e.required)){const r=l.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}const c=q();N(c.document,["agents","env",a.skill.name],o),R(c);for(const[e,r]of Object.entries(o))k(e)&&D(t,e,r);return Y(t,a),`已配置 Agent 环境变量: ${a.skill.name}(写入 ${c.configPath})`}async function E(e,t,r){if(void 0!==t){const r=e.find(e=>e.skill.name===t);if(!r)throw new Error(`Agent "${t}" 未找到`);if(!M(r))throw new Error(`Agent "${t}" 未声明环境变量需求。`);return r}const n=e.filter(M);if(0===n.length)throw new Error("没有已注册且声明环境变量需求的 Agent。");const a=await r.select({message:"选择要配置的 Agent",options:n.map(e=>({value:e.skill.name,label:e.skill.name,hint:e.skill.description}))}),i=n.find(e=>e.skill.name===a);if(!i)throw new Error(`Agent "${a}" 未找到`);return i}function M(e){return y(e.skill.env).length>0}async function j(e,t){const{declaration:r,processEnvValue:n,source:a,yamlValue:i}=e,o="agents.env"===a&&C(i),s=[r.required?"必填":"可选",o?"回车保留当前值":void 0].filter(e=>void 0!==e).join(","),l=`${r.name}(${s})`,c=[r.purpose?`用途: ${r.purpose}`:void 0,r.example?`示例: ${r.example}`:void 0,r.default?`默认: ${r.default}`:void 0,o?"当前 YAML 已配置,回车保留当前值。":void 0,void 0===i||o?void 0:"当前 YAML 值为空或占位符未解析,会被视为缺失。","process.env"===a?"当前来源: 当前 shell 临时环境变量,尚未持久写入 YAML。":void 0].filter(e=>void 0!==e).join("\n");if(c.length>0&&t.info(c),"process.env"===a&&"string"==typeof n&&n.length>0){if(await t.confirm({message:`是否将 ${r.name} 的当前 shell 值写入 roll.config.yaml?`,initialValue:!0}))return n}const u=r.required&&!o&&void 0===r.default&&"process.env"!==a,m={message:l,...o&&void 0!==i?{defaultValue:i}:{},...void 0!==r.example?{placeholder:r.example}:{},required:u},f=k(r.name)?await t.password({message:l,required:u}):await t.text(m);return 0===f.trim().length?o?i:void 0:f}function P(e){N(e,["install","fetchRetries"],o.install.fetchRetries),N(e,["install","preferOffline"],o.install.preferOffline),N(e,["install","networkTimeoutMs"],o.install.networkTimeoutMs)}function q(){const r=d(),i=r??a(n(),"roll.config.yaml");if(void 0===r||!e(i))return{configPath:i,existed:!1,document:L()};const o=t(i,"utf-8"),s=f(o,i),l=h(s);if(l.needsMigration)throw new Error(v(i,l));return{configPath:i,existed:!0,raw:o,document:s}}function L(){return{llm:{"default-provider":o.llm.defaultProvider,"default-model":o.llm.defaultModel,providers:{}},ask:{},agents:{"data-dir":o.agents.dataDir}}}function R(e){const t=i(e.document,{lineWidth:0});g(t,e.configPath),e.existed&&void 0!==e.raw&&r(I(e.configPath),e.raw,"utf-8"),r(e.configPath,t,"utf-8")}function I(e){const t=new Date;return`${e}.bak.${[t.getFullYear().toString().padStart(4,"0"),(t.getMonth()+1).toString().padStart(2,"0"),t.getDate().toString().padStart(2,"0"),"-",t.getHours().toString().padStart(2,"0"),t.getMinutes().toString().padStart(2,"0"),t.getSeconds().toString().padStart(2,"0")].join("")}`}function N(e,t,r){const n=p(t),a=n[n.length-1];if(void 0===a)throw new Error("配置路径不能为空");let i=e;for(const e of n.slice(0,-1)){const t=i[e];("object"!=typeof t||null===t||Array.isArray(t))&&(i[e]={}),i=i[e]}i[a]=r}function O(e,t){const r=p(t),n=r[r.length-1];if(void 0===n)return;let a=e;for(const e of r.slice(0,-1)){if("object"!=typeof a||null===a||Array.isArray(a))return;a=a[e]}"object"!=typeof a||null===a||Array.isArray(a)||delete a[n]}function T(e,t){return r=>{if(!/^\d+$/u.test(r))return"请输入整数";const n=Number.parseInt(r,10);return n<e||n>t?`请输入 ${String(e)} 到 ${String(t)} 之间的整数`:void 0}}function C(e){return"string"==typeof e&&e.length>0&&!A.test(e)}function D(e,t,r){0===r.trim().length||A.test(r)||e.warn(`${t} 将以明文写入 roll.config.yaml,建议改用 \${ENV_VAR} 引用并避免把配置文件提交到版本库。`)}function Y(e,t){const r=t.skill.name;switch(t.runtime.ownership){case"core-managed":e.info(`需重启后生效:roll agent stop ${r} && roll agent start ${r}`);break;case"external-managed":e.info(`请重启你自行管理的 "${r}" 进程以加载新环境变量。`);break;case"on-demand":e.info(`下次 roll run / roll ask 调用 "${r}" 时自动生效。`)}}
1
+ import{existsSync as e,readFileSync as t,writeFileSync as r}from"node:fs";import{homedir as n}from"node:os";import{resolve as a}from"node:path";import{stringify as i}from"yaml";import{DEFAULT_CONFIG as o,DEFAULT_LLM_MODELS as l,DEFAULT_LLM_PROVIDER as s,LLM_PROVIDER_OPTIONS as c}from"../../config/defaults.js";import{inspectAgentEnvRequirements as u}from"../../config/helpers.js";import{loadAgentsConfig as m,parseConfigDocument as f,resolveConfigPath as d,validateConfigText as g}from"../../config/loader.js";import{encodePathToYaml as p}from"../../config/key-codec.js";import{detectKnownConfigMigrations as h,formatConfigMigrationError as v}from"../../config/migration.js";import{AgentStore as w}from"../../registry/store.js";import{flattenAgentEnvDeclarations as y,isSecretEnvName as k}from"./config-guidance.js";import{ConfigSetupCancelledError as $,clackPromptAdapter as b}from"./config-prompts.js";const A=/\$\{[^}]+\}/u;export async function runConfigSetup(e,t,r=b){try{r.intro("Roll 配置向导"),q();switch(await x(e,r)){case"llm":r.outro(await setupLlm(r));break;case"install":r.outro(await setupInstall(r));break;case"agent":r.outro(await setupAgentEnv(t,r));break;case"shell":r.outro(await setupShell(r))}}catch(e){if(e instanceof $)return void(process.exitCode=1);throw e}}async function x(e,t){if(void 0===e)return t.select({message:"要配置哪一类内容?",options:[{value:"llm",label:"LLM",hint:"默认模型和 provider key"},{value:"install",label:"Install network",hint:"npm registry、重试和超时"},{value:"agent",label:"Agent env",hint:"配置某个 Agent 需要的环境变量"},{value:"shell",label:"Chat shell 工具",hint:"roll chat 内建本机命令执行能力(默认关闭)"}]});if("llm"===e||"install"===e||"agent"===e||"shell"===e)return e;if("bash"===e)return"shell";throw new Error(`未知 setup 模块: ${e}。可用: llm, install, agent, shell`)}export async function setupLlm(e){q();const t=await e.select({message:"选择默认 LLM provider",options:c.map(e=>({value:e,label:e,hint:l[e]})),initialValue:s}),r=await e.text({message:"默认 model",defaultValue:l[t],required:!0}),n=await e.password({message:`${t} API key(可直接输入真实 key,也可输入 \${ENV_VAR})`,required:!0}),a=await e.text({message:"自定义 base-url(可选,留空则不写)",required:!1}),i=q();return N(i.document,["llm","defaultProvider"],t),N(i.document,["llm","defaultModel"],r),N(i.document,["llm","providers",t,"apiKey"],n),a.trim().length>0?N(i.document,["llm","providers",t,"baseUrl"],a.trim()):O(i.document,["llm","providers",t,"baseUrl"]),R(i),C(e,`${t} API key`,n),`已配置 LLM: ${t}/${r}(写入 ${i.configPath})`}export async function setupInstall(e){q();const t=await e.select({message:"当前安装/更新网络更接近哪种场景?",options:[{value:"default-network",label:"默认网络 / 海外网络",hint:"不设置 registry"},{value:"china-dev",label:"国内开发机",hint:"使用 https://registry.npmmirror.com"},{value:"private-registry",label:"企业内网 / 私有 registry"},{value:"advanced",label:"高级自定义"}]}),r=await S(t,e),n=q();switch(r.scenario){case"default-network":P(n.document),O(n.document,["install","registry"]);break;case"china-dev":P(n.document),N(n.document,["install","registry"],"https://registry.npmmirror.com");break;case"private-registry":P(n.document),N(n.document,["install","registry"],r.registry);break;case"advanced":{const{registry:e,fetchRetries:t,preferOffline:a,networkTimeoutMs:i}=r.values;N(n.document,["install","fetchRetries"],t),N(n.document,["install","preferOffline"],a),N(n.document,["install","networkTimeoutMs"],i),e.length>0?N(n.document,["install","registry"],e):O(n.document,["install","registry"]);break}}return R(n),`已配置 install 网络参数(写入 ${n.configPath})`}export async function setupShell(e,t=process.platform){q();const r=await e.confirm({message:"启用 roll chat 内建 shell 工具(允许模型在本机执行命令;默认确认策略受 runtime.approval 及显式 override 控制)?",initialValue:!1});let n=!0,a=!1;const i="win32"===t;r&&i&&e.warn("Windows PowerShell 命令当前全部按 unknown 处理,默认每条命令都需确认;显式 approval override(roll.powershell: auto、roll.exec_command: auto)可覆盖对应工具的默认策略。"),r&&!i&&(n=await e.confirm({message:"安全只读命令(ls/grep 等)自动放行,无需逐条确认?",initialValue:!0})),r&&(a=await e.confirm({message:"启用长跑命令会话(session exec,供 dev server 等常驻进程使用)?",initialValue:!1}));const o=q();N(o.document,["runtime","shell","enabled"],r),r&&!i&&N(o.document,["runtime","shell","autoApproveSafe"],n),r&&N(o.document,["runtime","shell","session","enabled"],a),R(o);return`已${r?"启用":"禁用"} chat shell 工具${r&&i?";Windows PowerShell 默认逐条确认(显式 approval override 可覆盖)":""}(写入 ${o.configPath})`}export async function setupBash(e){return setupShell(e)}async function S(e,t){switch(e){case"default-network":case"china-dev":return{scenario:e};case"private-registry":return{scenario:e,registry:(await t.text({message:"私有 registry URL",placeholder:"https://registry.example.com",required:!0})).trim()};case"advanced":return{scenario:e,values:await V(t)}}}async function V(e){const t=await e.text({message:"npm registry(留空则不写)",placeholder:"https://registry.npmmirror.com"}),r=await e.text({message:"fetch-retries",defaultValue:"3",required:!0,validate:T(0,10)}),n=await e.confirm({message:"是否启用 prefer-offline?",initialValue:!1}),a=await e.text({message:"network-timeout-ms",defaultValue:"120000",required:!0,validate:T(1e4,Number.MAX_SAFE_INTEGER)});return{registry:t.trim(),fetchRetries:Number.parseInt(r,10),preferOffline:n,networkTimeoutMs:Number.parseInt(a,10)}}export async function setupAgentEnv(e,t){q();const{agentsConfig:r}=m(),n=new w(r.dataDir),a=await E(n.list(),e,t),i=u(a.skill.name,a.skill.env,r.env);if(!i)throw new Error(`Agent "${a.skill.name}" 未声明环境变量需求。`);const o={...r.env?.[a.skill.name]??{}},l=y(a.skill.env),s=new Map(i.items.map(e=>[e.name,e]));for(const e of l.filter(e=>e.required)){const r=s.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}if(await t.confirm({message:"是否配置可选环境变量?",initialValue:!1}))for(const e of l.filter(e=>!e.required)){const r=s.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}const c=q();N(c.document,["agents","env",a.skill.name],o),R(c);for(const[e,r]of Object.entries(o))k(e)&&C(t,e,r);return D(t,a),`已配置 Agent 环境变量: ${a.skill.name}(写入 ${c.configPath})`}async function E(e,t,r){if(void 0!==t){const r=e.find(e=>e.skill.name===t);if(!r)throw new Error(`Agent "${t}" 未找到`);if(!M(r))throw new Error(`Agent "${t}" 未声明环境变量需求。`);return r}const n=e.filter(M);if(0===n.length)throw new Error("没有已注册且声明环境变量需求的 Agent。");const a=await r.select({message:"选择要配置的 Agent",options:n.map(e=>({value:e.skill.name,label:e.skill.name,hint:e.skill.description}))}),i=n.find(e=>e.skill.name===a);if(!i)throw new Error(`Agent "${a}" 未找到`);return i}function M(e){return y(e.skill.env).length>0}async function j(e,t){const{declaration:r,processEnvValue:n,source:a,yamlValue:i}=e,o="agents.env"===a&&_(i),l=[r.required?"必填":"可选",o?"回车保留当前值":void 0].filter(e=>void 0!==e).join(","),s=`${r.name}(${l})`,c=[r.purpose?`用途: ${r.purpose}`:void 0,r.example?`示例: ${r.example}`:void 0,r.default?`默认: ${r.default}`:void 0,o?"当前 YAML 已配置,回车保留当前值。":void 0,void 0===i||o?void 0:"当前 YAML 值为空或占位符未解析,会被视为缺失。","process.env"===a?"当前来源: 当前 shell 临时环境变量,尚未持久写入 YAML。":void 0].filter(e=>void 0!==e).join("\n");if(c.length>0&&t.info(c),"process.env"===a&&"string"==typeof n&&n.length>0){if(await t.confirm({message:`是否将 ${r.name} 的当前 shell 值写入 roll.config.yaml?`,initialValue:!0}))return n}const u=r.required&&!o&&void 0===r.default&&"process.env"!==a,m={message:s,...o&&void 0!==i?{defaultValue:i}:{},...void 0!==r.example?{placeholder:r.example}:{},required:u},f=k(r.name)?await t.password({message:s,required:u}):await t.text(m);return 0===f.trim().length?o?i:void 0:f}function P(e){N(e,["install","fetchRetries"],o.install.fetchRetries),N(e,["install","preferOffline"],o.install.preferOffline),N(e,["install","networkTimeoutMs"],o.install.networkTimeoutMs)}function q(){const r=d(),i=r??a(n(),"roll.config.yaml");if(void 0===r||!e(i))return{configPath:i,existed:!1,document:L()};const o=t(i,"utf-8"),l=f(o,i),s=h(l);if(s.needsMigration)throw new Error(v(i,s));return{configPath:i,existed:!0,raw:o,document:l}}function L(){return{llm:{"default-provider":o.llm.defaultProvider,"default-model":o.llm.defaultModel,providers:{}},ask:{},agents:{"data-dir":o.agents.dataDir}}}function R(e){const t=i(e.document,{lineWidth:0});g(t,e.configPath),e.existed&&void 0!==e.raw&&r(I(e.configPath),e.raw,"utf-8"),r(e.configPath,t,"utf-8")}function I(e){const t=new Date;return`${e}.bak.${[t.getFullYear().toString().padStart(4,"0"),(t.getMonth()+1).toString().padStart(2,"0"),t.getDate().toString().padStart(2,"0"),"-",t.getHours().toString().padStart(2,"0"),t.getMinutes().toString().padStart(2,"0"),t.getSeconds().toString().padStart(2,"0")].join("")}`}function N(e,t,r){const n=p(t),a=n[n.length-1];if(void 0===a)throw new Error("配置路径不能为空");let i=e;for(const e of n.slice(0,-1)){const t=i[e];("object"!=typeof t||null===t||Array.isArray(t))&&(i[e]={}),i=i[e]}i[a]=r}function O(e,t){const r=p(t),n=r[r.length-1];if(void 0===n)return;let a=e;for(const e of r.slice(0,-1)){if("object"!=typeof a||null===a||Array.isArray(a))return;a=a[e]}"object"!=typeof a||null===a||Array.isArray(a)||delete a[n]}function T(e,t){return r=>{if(!/^\d+$/u.test(r))return"请输入整数";const n=Number.parseInt(r,10);return n<e||n>t?`请输入 ${String(e)} 到 ${String(t)} 之间的整数`:void 0}}function _(e){return"string"==typeof e&&e.length>0&&!A.test(e)}function C(e,t,r){0===r.trim().length||A.test(r)||e.warn(`${t} 将以明文写入 roll.config.yaml,建议改用 \${ENV_VAR} 引用并避免把配置文件提交到版本库。`)}function D(e,t){const r=t.skill.name;switch(t.runtime.ownership){case"core-managed":e.info(`需重启后生效:roll agent stop ${r} && roll agent start ${r}`);break;case"external-managed":e.info(`请重启你自行管理的 "${r}" 进程以加载新环境变量。`);break;case"on-demand":e.info(`下次 roll run / roll ask 调用 "${r}" 时自动生效。`)}}
@@ -1 +1 @@
1
- import{readFileSync as t}from"node:fs";import{defineCommand as o}from"citty";import{loadConfig as n}from"../../config/loader.js";import{getAgentEnv as r,getMissingAgentEnvRuntimeIssues as e,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{AgentStore as s}from"../../registry/store.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{createProviderModel as c}from"../../llm/providers.js";import{formatValidationIssuesMessage as u}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as g}from"../utils/agent-tools.js";import{extractTextContent as h,formatToolResultForJsonOutput as m,isToolErrorResult as d}from"../utils/tool-results.js";import{log as b,redactToolArgsForLog as w}from"../utils/output.js";import{shouldSkipRuntimeReadinessForTool as y}from"../../config/runtime-env.js";export default o({meta:{description:"直接调用已注册 Agent 的指定 MCP tool"},args:{agent:{type:"positional",description:"Agent 名称",required:!1},tool:{type:"positional",description:"MCP tool 名称",required:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1},bail:{type:"boolean",description:"batch 模式下遇到第一条失败即停止",default:!1},"input-json":{type:"string",description:"以 JSON 字符串提供完整 tool 输入对象;不能与 --input-file 同用"},"input-file":{type:"string",description:"从 JSON 文件读取完整 tool 输入对象;不能与 --input-json 同用"},"batch-json":{type:"string",description:"以 JSON array 提供多条调用;每项包含 agent/tool/input"},"batch-file":{type:"string",description:"从 JSON 文件读取 batch array;不能与 --batch-json 同用"},"batch-stdin":{type:"boolean",description:"从 stdin 读取 batch array;不能与 --batch-json 或 --batch-file 同用",default:!1}},async run({args:t,rawArgs:o}){const{config:r}=n(),e=new s(r.agents.dataDir),i=new l,a=new Map;try{const n=I(r),s={store:e,config:r,clientManager:i,agentConnections:a,...n?{samplingModel:n}:{}};if(k(o)&&M(o)>0)return b.error("batch 模式不接受 agent/tool 位置参数;请在每个 batch item 中声明 agent 和 tool"),void(process.exitCode=1);const l=parseBatchInput(o,{readStdin:T});if(l){const o=await q({items:l,...s,bail:!0===t.bail});return t.json?console.log(JSON.stringify(o.map(formatRunToolResultForJsonOutput),null,2)):K(o),void(o.some(t=>!t.ok)&&(process.exitCode=1))}const c=R(t.agent,"agent"),u=R(t.tool,"tool");if(!c||!u)return b.error("roll run 需要提供 agent/tool 位置参数,或使用 --batch-json / --batch-file / --batch-stdin 进入 batch 模式"),void(process.exitCode=1);let f;try{f=resolveToolArgs(o)}catch(t){return b.error(t instanceof Error?t.message:String(t)),void(process.exitCode=1)}const p=await B({item:{agent:c,tool:u,input:f},index:0,...s});p.ok?(t.json?console.log(JSON.stringify(m(p.result),null,2)):D(p.result),b.success("调用完成")):(t.json?console.log(JSON.stringify(formatRunToolResultForJsonOutput(p),null,2)):(b.error(p.error),void 0!==p.result&&D(p.result)),process.exitCode=1)}catch(t){const o=t instanceof Error?t.message:String(t),n=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";b.error(`${o}${n}`),process.exitCode=1}finally{await i.disconnectAll()}}});const j=new Set(["json","verbose","v","bail","help","h","version"]),S=new Set(["batch-stdin"]),$=new Set(["config","input-json","input-file","batch-json","batch-file"]);function v(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function E(t,o){let n;try{n=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!v(n))throw new Error(`${o} 必须是 JSON object`);return n}function N(t,o){let n;try{n=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!Array.isArray(n))throw new Error(`${o} 必须是 JSON array`);return n}function J(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]!==`--${o}`){n++;continue}const r=t[n+1];if(!r||r.startsWith("--"))throw new Error(`选项 --${o} 需要提供值`);return r}}function O(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]===`--${o}`)return!0;n++}return!1}function k(t){return x(t,"batch-json")||x(t,"batch-file")||x(t,"batch-stdin")}function x(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]===`--${o}`)return!0;n++}return!1}export function parseExplicitToolInput(o){const n=J(o,"input-json"),r=J(o,"input-file"),e=W(o);if([n,r,e].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 positional JSON、--input-json 和 --input-file");if(n)return E(n,"--input-json");if(r){return E(t(r,"utf-8"),`输入文件 ${r}`)}return e?E(e,"positional JSON input"):void 0}export function parseBatchInput(o,n={}){const r=J(o,"batch-json"),e=J(o,"batch-file"),i=O(o,"batch-stdin"),s=J(o,"input-json"),l=J(o,"input-file");if([r,e,i?"--batch-stdin":void 0].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 --batch-json、--batch-file 和 --batch-stdin");if(!r&&!e&&!i)return;if(s||l)throw new Error("batch 模式不能同时使用 --input-json 或 --input-file");const a=r?"--batch-json":e?`batch 文件 ${e}`:"--batch-stdin",c=N(r??(e?t(e,"utf-8"):A(n)),a);if(0===c.length)throw new Error(`${a} 至少需要包含一条调用`);return c.map((t,o)=>C(t,o))}function A(t){if(!t.readStdin)throw new Error("--batch-stdin 需要可用的 stdin 读取器");return t.readStdin()}function T(){if(process.stdin.isTTY)throw new Error("--batch-stdin 需要从 stdin 管道或文件重定向读取 JSON array");return t(0,"utf-8")}function C(t,o){const n=`batch[${String(o)}]`;if(!v(t))throw new Error(`${n} 必须是 JSON object`);const r=t.agent,e=t.tool,i="input"in t?t.input:{},s=t.label;if("string"!=typeof r||0===r.trim().length)throw new Error(`${n}.agent 必须是非空字符串`);if("string"!=typeof e||0===e.trim().length)throw new Error(`${n}.tool 必须是非空字符串`);if(void 0!==i&&!v(i))throw new Error(`${n}.input 必须是 JSON object`);if(void 0!==s&&"string"!=typeof s)throw new Error(`${n}.label 必须是字符串`);return{agent:r,tool:e,input:i??{},...void 0!==s?{label:s}:{}}}function W(t){const o=[];for(const n of t){if(n.startsWith("--"))break;o.push(n)}const n=o.slice(2);if(0!==n.length){if(1===n.length&&n[0]?.trim().startsWith("{"))return n[0];throw new Error("roll run 只接受 agent/tool 两个位置参数;tool 输入请使用 --key value、--input-json、--input-file,或第三个位置参数 JSON object")}}function M(t){let o=0;for(const n of t){if(n.startsWith("--"))return o;o+=1}return o}export function resolveToolArgs(t){return{...parseExplicitToolInput(t)??{},...parseToolArgs(t)}}export function parseToolArgs(t){const o={};let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){const r=t[n];if(!r?.startsWith("--")){n++;continue}const e=r.slice(2),i=t[n+1];if(j.has(e)||S.has(e))n++;else if($.has(e))n+=i&&!i.startsWith("--")?2:1;else if(!i||i.startsWith("--"))o[e]=!0,n++;else{const t=Number(i);o[e]=Number.isNaN(t)?i:t,n+=2}}return o}function R(t,o){if("string"==typeof t&&0!==t.trim().length)return t;b.error(`缺少必填位置参数:${o}`)}function I(t){const o=t.llm.defaultProvider,n=t.llm.providers[o];return n?c(o,t.llm.defaultModel,n.apiKey,n.baseUrl):void 0}async function P(t){const o=t.agentConnections.get(t.item.agent);if(o)return o;const n=t.store.findByName(t.item.agent);if(!n)return`Agent "${t.item.agent}" 未注册。使用 \`roll agent list\` 查看已注册 Agent。`;b.info(`连接 Agent "${n.skill.name}"...`);const e=r(t.config,n.skill.name),s=a(n),l=await t.clientManager.connect(n.skill.name,s,n.installPath,{...t.samplingModel?{samplingModel:t.samplingModel}:{},...e?{env:e}:{}}),c=g((await l.listTools()).tools),u={agent:n,client:l,tools:c,envReport:i(n.skill.name,n.skill.env,t.config.agents.env)};return t.agentConnections.set(t.item.agent,u),u}async function B(t){const o=F(t.item,t.index);try{const n=await P(t);if("string"==typeof n)return{...o,ok:!1,error:n};const r=n.tools.find(o=>o.name===t.item.tool);if(!r)return{...o,ok:!1,error:p(n.agent.skill.name,t.item.tool,n.tools)};const i=y(r.name)?[]:e(n.envReport),s=f(r,t.item.input,{runtimeIssues:i});if(!s.ok)return{...o,ok:!1,error:u(n.agent.skill.name,t.item.tool,s.issues,s.runtimeIssues)};b.info(`调用 ${n.agent.skill.name}.${t.item.tool}`),b.debug(`调用参数: ${JSON.stringify(w(t.item.input))}`);const l=await n.client.callTool({name:t.item.tool,arguments:t.item.input});return d(l)?{...o,ok:!1,error:"tool 返回 isError=true",result:l}:{...o,ok:!0,result:l}}catch(t){const n=t instanceof Error?t.message:String(t),r=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";return{...o,ok:!1,error:`${n}${r}`}}}function F(t,o){return{index:o,agent:t.agent,tool:t.tool,...t.label?{label:t.label}:{}}}async function q(t){const o=[];for(const[n,r]of t.items.entries()){const e=await B({...t,item:r,index:n});if(o.push(e),!e.ok&&t.bail)break}return o}function D(t){if("object"==typeof t&&null!==t&&"content"in t)for(const o of h(t.content))console.log(o);else"string"!=typeof t?console.log(JSON.stringify(t,null,2)):console.log(t)}export function formatRunToolResultForJsonOutput(t){return t.ok||"result"in t?{...t,result:m(t.result)}:t}function K(t){for(const o of t){const t=o.label?`[${String(o.index+1)}] ${o.label} (${o.agent}.${o.tool})`:`[${String(o.index+1)}] ${o.agent}.${o.tool}`;console.log(t),o.ok?D(o.result):console.log(o.error)}}
1
+ import{readFileSync as t}from"node:fs";import{defineCommand as o}from"citty";import{loadConfig as n}from"../../config/loader.js";import{getAgentEnv as r,getMissingAgentEnvRuntimeIssues as e,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{AgentStore as s}from"../../registry/store.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{resolveTransportWithDevSpawnSpec as a}from"../../registry/dev-spawn.js";import{resolveLLMCall as c,toSamplingConnectOptions as u}from"../../llm/providers.js";import{formatValidationIssuesMessage as f}from"../../tool-runtime/messages.js";import{preflightToolCall as p}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as g,normalizeListedTools as h}from"../utils/agent-tools.js";import{extractTextContent as m,formatToolResultForJsonOutput as d,isToolErrorResult as b}from"../utils/tool-results.js";import{log as w,redactToolArgsForLog as y}from"../utils/output.js";import{shouldSkipRuntimeReadinessForTool as j}from"../../config/runtime-env.js";export default o({meta:{description:"直接调用已注册 Agent 的指定 MCP tool"},args:{agent:{type:"positional",description:"Agent 名称",required:!1},tool:{type:"positional",description:"MCP tool 名称",required:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1},bail:{type:"boolean",description:"batch 模式下遇到第一条失败即停止",default:!1},"input-json":{type:"string",description:"以 JSON 字符串提供完整 tool 输入对象;不能与 --input-file 同用"},"input-file":{type:"string",description:"从 JSON 文件读取完整 tool 输入对象;不能与 --input-json 同用"},"batch-json":{type:"string",description:"以 JSON array 提供多条调用;每项包含 agent/tool/input"},"batch-file":{type:"string",description:"从 JSON 文件读取 batch array;不能与 --batch-json 同用"},"batch-stdin":{type:"boolean",description:"从 stdin 读取 batch array;不能与 --batch-json 或 --batch-file 同用",default:!1}},async run({args:t,rawArgs:o}){const{config:r}=n(),e=new s(r.agents.dataDir),i=new l,a=new Map;try{const n=P(r),s={store:e,config:r,clientManager:i,agentConnections:a,...n?{samplingCall:n}:{}};if(x(o)&&I(o)>0)return w.error("batch 模式不接受 agent/tool 位置参数;请在每个 batch item 中声明 agent 和 tool"),void(process.exitCode=1);const l=parseBatchInput(o,{readStdin:T});if(l){const o=await D({items:l,...s,bail:!0===t.bail});return t.json?console.log(JSON.stringify(o.map(formatRunToolResultForJsonOutput),null,2)):L(o),void(o.some(t=>!t.ok)&&(process.exitCode=1))}const c=M(t.agent,"agent"),u=M(t.tool,"tool");if(!c||!u)return w.error("roll run 需要提供 agent/tool 位置参数,或使用 --batch-json / --batch-file / --batch-stdin 进入 batch 模式"),void(process.exitCode=1);let f;try{f=resolveToolArgs(o)}catch(t){return w.error(t instanceof Error?t.message:String(t)),void(process.exitCode=1)}const p=await F({item:{agent:c,tool:u,input:f},index:0,...s});p.ok?(t.json?console.log(JSON.stringify(d(p.result),null,2)):K(p.result),w.success("调用完成")):(t.json?console.log(JSON.stringify(formatRunToolResultForJsonOutput(p),null,2)):(w.error(p.error),void 0!==p.result&&K(p.result)),process.exitCode=1)}catch(t){const o=t instanceof Error?t.message:String(t),n=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";w.error(`${o}${n}`),process.exitCode=1}finally{await i.disconnectAll()}}});const S=new Set(["json","verbose","v","bail","help","h","version"]),$=new Set(["batch-stdin"]),v=new Set(["config","input-json","input-file","batch-json","batch-file"]);function E(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function N(t,o){let n;try{n=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!E(n))throw new Error(`${o} 必须是 JSON object`);return n}function J(t,o){let n;try{n=JSON.parse(t)}catch(t){throw new Error(`${o} 不是合法 JSON: ${t instanceof Error?t.message:String(t)}`)}if(!Array.isArray(n))throw new Error(`${o} 必须是 JSON array`);return n}function O(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]!==`--${o}`){n++;continue}const r=t[n+1];if(!r||r.startsWith("--"))throw new Error(`选项 --${o} 需要提供值`);return r}}function k(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]===`--${o}`)return!0;n++}return!1}function x(t){return A(t,"batch-json")||A(t,"batch-file")||A(t,"batch-stdin")}function A(t,o){let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){if(t[n]===`--${o}`)return!0;n++}return!1}export function parseExplicitToolInput(o){const n=O(o,"input-json"),r=O(o,"input-file"),e=R(o);if([n,r,e].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 positional JSON、--input-json 和 --input-file");if(n)return N(n,"--input-json");if(r){return N(t(r,"utf-8"),`输入文件 ${r}`)}return e?N(e,"positional JSON input"):void 0}export function parseBatchInput(o,n={}){const r=O(o,"batch-json"),e=O(o,"batch-file"),i=k(o,"batch-stdin"),s=O(o,"input-json"),l=O(o,"input-file");if([r,e,i?"--batch-stdin":void 0].filter(t=>void 0!==t).length>1)throw new Error("不能同时使用 --batch-json、--batch-file 和 --batch-stdin");if(!r&&!e&&!i)return;if(s||l)throw new Error("batch 模式不能同时使用 --input-json 或 --input-file");const a=r?"--batch-json":e?`batch 文件 ${e}`:"--batch-stdin",c=J(r??(e?t(e,"utf-8"):C(n)),a);if(0===c.length)throw new Error(`${a} 至少需要包含一条调用`);return c.map((t,o)=>W(t,o))}function C(t){if(!t.readStdin)throw new Error("--batch-stdin 需要可用的 stdin 读取器");return t.readStdin()}function T(){if(process.stdin.isTTY)throw new Error("--batch-stdin 需要从 stdin 管道或文件重定向读取 JSON array");return t(0,"utf-8")}function W(t,o){const n=`batch[${String(o)}]`;if(!E(t))throw new Error(`${n} 必须是 JSON object`);const r=t.agent,e=t.tool,i="input"in t?t.input:{},s=t.label;if("string"!=typeof r||0===r.trim().length)throw new Error(`${n}.agent 必须是非空字符串`);if("string"!=typeof e||0===e.trim().length)throw new Error(`${n}.tool 必须是非空字符串`);if(void 0!==i&&!E(i))throw new Error(`${n}.input 必须是 JSON object`);if(void 0!==s&&"string"!=typeof s)throw new Error(`${n}.label 必须是字符串`);return{agent:r,tool:e,input:i??{},...void 0!==s?{label:s}:{}}}function R(t){const o=[];for(const n of t){if(n.startsWith("--"))break;o.push(n)}const n=o.slice(2);if(0!==n.length){if(1===n.length&&n[0]?.trim().startsWith("{"))return n[0];throw new Error("roll run 只接受 agent/tool 两个位置参数;tool 输入请使用 --key value、--input-json、--input-file,或第三个位置参数 JSON object")}}function I(t){let o=0;for(const n of t){if(n.startsWith("--"))return o;o+=1}return o}export function resolveToolArgs(t){return{...parseExplicitToolInput(t)??{},...parseToolArgs(t)}}export function parseToolArgs(t){const o={};let n=0;for(;n<t.length&&!t[n]?.startsWith("--");)n++;for(;n<t.length;){const r=t[n];if(!r?.startsWith("--")){n++;continue}const e=r.slice(2),i=t[n+1];if(S.has(e)||$.has(e))n++;else if(v.has(e))n+=i&&!i.startsWith("--")?2:1;else if(!i||i.startsWith("--"))o[e]=!0,n++;else{const t=Number(i);o[e]=Number.isNaN(t)?i:t,n+=2}}return o}function M(t,o){if("string"==typeof t&&0!==t.trim().length)return t;w.error(`缺少必填位置参数:${o}`)}function P(t){const o=t.llm.defaultProvider,n=t.llm.providers[o];return n?c(o,t.llm.defaultModel,n.apiKey,"sampling",n.baseUrl,t.runtime.thinkingLevel):void 0}async function B(t){const o=t.agentConnections.get(t.item.agent);if(o)return o;const n=t.store.findByName(t.item.agent);if(!n)return`Agent "${t.item.agent}" 未注册。使用 \`roll agent list\` 查看已注册 Agent。`;w.info(`连接 Agent "${n.skill.name}"...`);const e=r(t.config,n.skill.name),s=a(n),l=await t.clientManager.connect(n.skill.name,s,n.installPath,{...u(t.samplingCall),...e?{env:e}:{}}),c=h((await l.listTools()).tools),f={agent:n,client:l,tools:c,envReport:i(n.skill.name,n.skill.env,t.config.agents.env)};return t.agentConnections.set(t.item.agent,f),f}async function F(t){const o=q(t.item,t.index);try{const n=await B(t);if("string"==typeof n)return{...o,ok:!1,error:n};const r=n.tools.find(o=>o.name===t.item.tool);if(!r)return{...o,ok:!1,error:g(n.agent.skill.name,t.item.tool,n.tools)};const i=j(r.name)?[]:e(n.envReport),s=p(r,t.item.input,{runtimeIssues:i});if(!s.ok)return{...o,ok:!1,error:f(n.agent.skill.name,t.item.tool,s.issues,s.runtimeIssues)};w.info(`调用 ${n.agent.skill.name}.${t.item.tool}`),w.debug(`调用参数: ${JSON.stringify(y(t.item.input))}`);const l=await n.client.callTool({name:t.item.tool,arguments:t.item.input});return b(l)?{...o,ok:!1,error:"tool 返回 isError=true",result:l}:{...o,ok:!0,result:l}}catch(t){const n=t instanceof Error?t.message:String(t),r=t instanceof Error&&t.cause?`\n cause: ${String(t.cause)}`:"";return{...o,ok:!1,error:`${n}${r}`}}}function q(t,o){return{index:o,agent:t.agent,tool:t.tool,...t.label?{label:t.label}:{}}}async function D(t){const o=[];for(const[n,r]of t.items.entries()){const e=await F({...t,item:r,index:n});if(o.push(e),!e.ok&&t.bail)break}return o}function K(t){if("object"==typeof t&&null!==t&&"content"in t)for(const o of m(t.content))console.log(o);else"string"!=typeof t?console.log(JSON.stringify(t,null,2)):console.log(t)}export function formatRunToolResultForJsonOutput(t){return t.ok||"result"in t?{...t,result:d(t.result)}:t}function L(t){for(const o of t){const t=o.label?`[${String(o.index+1)}] ${o.label} (${o.agent}.${o.tool})`:`[${String(o.index+1)}] ${o.agent}.${o.tool}`;console.log(t),o.ok?K(o.result):console.log(o.error)}}
@@ -1 +1 @@
1
- import t from"chalk";import{isCancel as e,select as s}from"@clack/prompts";import{createSpinner as n,log as o}from"./output.js";import{GLYPHS as a}from"./glyphs.js";import{computeUsageParts as i,formatUsageLine as r}from"./token-format.js";import{formatToolInput as p,formatApprovalDetails as c}from"./tool-format.js";export const clackConfirm=async t=>{const n=await s({message:t,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!e(n)&&"yes"===n};function l(t){const e=[`chat.${t.stage}`,t.message];return void 0!==t.elapsedMs&&e.push(`${String(t.elapsedMs)}ms`),void 0!==t.data&&e.push(JSON.stringify(t.data)),e.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;toolLabels=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(t,e){this.confirm=t,this.contextWindow=e}async handle(e,s){switch(e.type){case"debug":o.debug(l(e));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(e.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const s=`${e.agentName}.${e.toolName}`,o=n(`${t.cyan(s)} ${t.gray(p(e.input))}`);o.start(),this.spinners.set(e.toolCallId,o),this.toolLabels.set(e.toolCallId,s);break}case"tool-output-delta":{const s=this.spinners.get(e.toolCallId),n=this.toolLabels.get(e.toolCallId);if(s&&n){const o=e.delta.split("\n").map(t=>t.trim()).filter(t=>t.length>0).at(-1);if(o){const e=o.length>80?`${o.slice(0,79)}…`:o;s.text=`${t.cyan(n)} ${t.gray(e)}`}}break}case"tool-result":{const t=this.spinners.get(e.toolCallId);t&&(e.isError?t.fail():t.succeed(),this.spinners.delete(e.toolCallId)),this.toolLabels.delete(e.toolCallId);break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const t=e.reason?`(${e.reason})`:"",n=c(e.input),o=`执行 ${e.agentName}.${e.toolName}${t}?`;await this.confirm(n?`${o}\n${n}`:o)?s.approve(e.approvalId):s.reject(e.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=n(t.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const s="auto"===e.reason?"自动压缩":"手动压缩",n=e.truncatedTools?`,精简 ${String(e.truncatedTools)} 个工具结果`:"",o=0!==e.removed||e.truncatedTools?`${a.compact} ${s}(${e.strategy}):移除 ${String(e.removed)} 条 → 保留 ${String(e.kept)} 条${n}`:`${a.compact} ${s}:无需压缩`;process.stderr.write(`${t.gray(o)}\n`);break}case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),o.error(e.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===e.text.length&&(e.totalUsage?.outputTokens??0)>0&&o.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),e.stoppedAtStepLimit&&o.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const s=r(i(e.totalUsage,e.sessionUsage,this.contextWindow,e.contextInputTokens));s&&process.stderr.write(`${t.gray(s)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=n(t.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
1
+ import t from"chalk";import{isCancel as s,select as e}from"@clack/prompts";import{createSpinner as n,log as o}from"./output.js";import{GLYPHS as a}from"./glyphs.js";import{computeUsageParts as i,formatUsageLine as r}from"./token-format.js";import{formatToolInput as p,formatApprovalDetails as l}from"./tool-format.js";export const clackConfirm=async t=>{const n=await e({message:t,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!s(n)&&"yes"===n};function c(t){const s=[`chat.${t.stage}`,t.message];return void 0!==t.elapsedMs&&s.push(`${String(t.elapsedMs)}ms`),void 0!==t.data&&s.push(JSON.stringify(t.data)),s.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;toolLabels=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(t,s){this.confirm=t,this.contextWindow=s}async handle(s,e){switch(s.type){case"debug":o.debug(c(s));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(s.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const e=`${s.agentName}.${s.toolName}`,o=n(`${t.cyan(e)} ${t.gray(p(s.input))}`);o.start(),this.spinners.set(s.toolCallId,o),this.toolLabels.set(s.toolCallId,e);break}case"tool-output-delta":{const e=this.spinners.get(s.toolCallId),n=this.toolLabels.get(s.toolCallId);if(e&&n){const o=s.delta.split("\n").map(t=>t.trim()).filter(t=>t.length>0).at(-1);if(o){const s=o.length>80?`${o.slice(0,79)}…`:o;e.text=`${t.cyan(n)} ${t.gray(s)}`}}break}case"tool-result":{const t=this.spinners.get(s.toolCallId);t&&(s.isError?t.fail():t.succeed(),this.spinners.delete(s.toolCallId)),this.toolLabels.delete(s.toolCallId);break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const t=s.reason?`(${s.reason})`:"",n=l(s.input),o=`执行 ${s.agentName}.${s.toolName}${t}?`;await this.confirm(n?`${o}\n${n}`:o)?e.approve(s.approvalId):e.reject(s.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=n(t.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const e="auto"===s.reason?"自动压缩":"手动压缩",n=s.truncatedTools?`,精简 ${String(s.truncatedTools)} 个工具结果`:"",o=0!==s.removed||s.truncatedTools?`${a.compact} ${e}(${s.strategy}):移除 ${String(s.removed)} 条 → 保留 ${String(s.kept)} 条${n}`:`${a.compact} ${e}:无需压缩`;process.stderr.write(`${t.gray(o)}\n`);break}case"turn-cancelled":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine();for(const s of this.spinners.values())s.warn(`${s.text} ${t.yellow("已中断")}`);this.spinners.clear(),this.toolLabels.clear(),o.warn(s.message);break;case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),o.error(s.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===s.text.length&&(s.totalUsage?.outputTokens??0)>0&&o.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),s.stoppedAtStepLimit&&o.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const e=r(i(s.totalUsage,s.sessionUsage,this.contextWindow,s.contextInputTokens));e&&process.stderr.write(`${t.gray(e)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=n(t.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
@@ -15,10 +15,22 @@ export interface ResolvedLLMCall {
15
15
  readonly model: LanguageModelV4;
16
16
  readonly providerOptions?: SharedV4ProviderOptions;
17
17
  }
18
+ /**
19
+ * 把 resolveLLMCall(..., "sampling", ...) 的结果转换成
20
+ * McpClientManager.connect() 需要的 samplingModel/samplingProviderOptions 字段。
21
+ * 统一入口,避免每个调用方(ask/run/chat)各自拼一份、漏传 providerOptions。
22
+ */
23
+ export declare function toSamplingConnectOptions(call: ResolvedLLMCall | undefined): {
24
+ readonly samplingModel?: LanguageModelV4;
25
+ readonly samplingProviderOptions?: SharedV4ProviderOptions;
26
+ };
18
27
  /**
19
28
  * 按 provider + 调用目的解析 generateText 的完整调用上下文。
20
29
  *
21
30
  * structured-output 场景下,对 qwen provider 自动注入 enableThinking: false,
22
31
  * 因为阿里云 thinking mode 不支持 structured output。
32
+ *
33
+ * chat 与 sampling 场景下都是纯文本 generateText 调用,复用同一套
34
+ * thinkingProviderOptions 映射注入 reasoning/thinking effort。
23
35
  */
24
36
  export declare function resolveLLMCall(providerName: string, modelName: string, apiKey: string, purpose: LLMCallPurpose, baseURL?: string, thinkingLevel?: ThinkingLevel): ResolvedLLMCall;
@@ -1 +1 @@
1
- import{createAlibaba as e}from"@ai-sdk/alibaba";import{createAnthropic as i}from"@ai-sdk/anthropic";import{createOpenAI as n}from"@ai-sdk/openai";import{createDeepSeek as o}from"@ai-sdk/deepseek";import{runtimeThinkingLevels as t}from"../config/schema.js";const a={low:2048,medium:8192,high:16384},r=["gpt-5.1","gpt-5.2","gpt-5.3","gpt-5.4"];function p(e){return r.some(i=>e.startsWith(i))}function s(e){const i=/^claude-[a-z]+-(\d+)(?:-(\d+))?(?:\b|-)/.exec(e),n=i?.[1];if(!n)return!1;const o=Number(n),t=i?.[2],a=void 0!==t&&t.length<=2?Number(t):0;return o>4||4===o&&a>=6}export function thinkingProviderOptions(e,i,n){return"openai"===e?"off"===n?p(i)?{openai:{reasoningEffort:"none"}}:void 0:{openai:{reasoningEffort:n}}:"anthropic"===e?"off"===n?{anthropic:{thinking:{type:"disabled"}}}:s(i)?{anthropic:{thinking:{type:"adaptive"},effort:n}}:{anthropic:{thinking:{type:"enabled",budgetTokens:a[n]}}}:"qwen"===e?"off"===n?{alibaba:{enableThinking:!1}}:{alibaba:{enableThinking:!0,thinkingBudget:a[n]}}:"deepseek"===e?{deepseek:{thinking:{type:"off"===n?"disabled":"enabled"}}}:void 0}const d="https://dashscope.aliyuncs.com/compatible-mode/v1",c={anthropic:(e,{apiKey:n,baseURL:o})=>i({apiKey:n,...o?{baseURL:o}:{}})(e),openai:(e,{apiKey:i,baseURL:o})=>n({apiKey:i,...o?{baseURL:o}:{}})(e),deepseek:(e,{apiKey:i,baseURL:n})=>o({apiKey:i,...n?{baseURL:n}:{}})(e),qwen:(i,{apiKey:n,baseURL:o})=>e({apiKey:n,baseURL:o??d})(i)};export function createProviderModel(e,i,n,o){const t=c[e];if(!t){const i=Object.keys(c).join(", ");throw new Error(`Unknown LLM provider "${e}". Supported: ${i}`)}return t(i,{apiKey:n,baseURL:o})}export function resolveLLMCall(e,i,n,o,t,a="medium"){const r=createProviderModel(e,i,n,t);if("structured-output"===o&&"qwen"===e)return{model:r,providerOptions:{alibaba:{enableThinking:!1}}};if("chat"===o){const n=thinkingProviderOptions(e,i,a);return n?{model:r,providerOptions:n}:{model:r}}return{model:r}}
1
+ import{createAlibaba as e}from"@ai-sdk/alibaba";import{createAnthropic as i}from"@ai-sdk/anthropic";import{createOpenAI as n}from"@ai-sdk/openai";import{createDeepSeek as o}from"@ai-sdk/deepseek";import{runtimeThinkingLevels as t}from"../config/schema.js";const r={low:2048,medium:8192,high:16384},a=["gpt-5.1","gpt-5.2","gpt-5.3","gpt-5.4"];function p(e){return a.some(i=>e.startsWith(i))}function s(e){const i=/^claude-[a-z]+-(\d+)(?:-(\d+))?(?:\b|-)/.exec(e),n=i?.[1];if(!n)return!1;const o=Number(n),t=i?.[2],r=void 0!==t&&t.length<=2?Number(t):0;return o>4||4===o&&r>=6}export function thinkingProviderOptions(e,i,n){return"openai"===e?"off"===n?p(i)?{openai:{reasoningEffort:"none"}}:void 0:{openai:{reasoningEffort:n}}:"anthropic"===e?"off"===n?{anthropic:{thinking:{type:"disabled"}}}:s(i)?{anthropic:{thinking:{type:"adaptive"},effort:n}}:{anthropic:{thinking:{type:"enabled",budgetTokens:r[n]}}}:"qwen"===e?"off"===n?{alibaba:{enableThinking:!1}}:{alibaba:{enableThinking:!0,thinkingBudget:r[n]}}:"deepseek"===e?{deepseek:{thinking:{type:"off"===n?"disabled":"enabled"}}}:void 0}const d="https://dashscope.aliyuncs.com/compatible-mode/v1",c={anthropic:(e,{apiKey:n,baseURL:o})=>i({apiKey:n,...o?{baseURL:o}:{}})(e),openai:(e,{apiKey:i,baseURL:o})=>n({apiKey:i,...o?{baseURL:o}:{}})(e),deepseek:(e,{apiKey:i,baseURL:n})=>o({apiKey:i,...n?{baseURL:n}:{}})(e),qwen:(i,{apiKey:n,baseURL:o})=>e({apiKey:n,baseURL:o??d})(i)};export function createProviderModel(e,i,n,o){const t=c[e];if(!t){const i=Object.keys(c).join(", ");throw new Error(`Unknown LLM provider "${e}". Supported: ${i}`)}return t(i,{apiKey:n,baseURL:o})}export function toSamplingConnectOptions(e){return e?{samplingModel:e.model,...e.providerOptions?{samplingProviderOptions:e.providerOptions}:{}}:{}}export function resolveLLMCall(e,i,n,o,t,r="medium"){const a=createProviderModel(e,i,n,t);if("structured-output"===o&&"qwen"===e)return{model:a,providerOptions:{alibaba:{enableThinking:!1}}};if("chat"===o||"sampling"===o){const n=thinkingProviderOptions(e,i,r);return n?{model:a,providerOptions:n}:{model:a}}return{model:a}}
@@ -1,11 +1,13 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import type { LanguageModelV4 } from "@ai-sdk/provider";
2
+ import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
3
3
  import type { AgentTransport } from "../types/agent.ts";
4
4
  export interface ConnectOptions {
5
5
  /** 连接超时(毫秒),默认 30s */
6
6
  readonly timeoutMs?: number;
7
7
  /** 为子 Agent 提供的 LLM model(启用 Sampling 支持) */
8
8
  readonly samplingModel?: LanguageModelV4;
9
+ /** Sampling 调用的 provider 级参数(如 reasoning/thinking effort) */
10
+ readonly samplingProviderOptions?: SharedV4ProviderOptions;
9
11
  /** 注入到 stdio 子进程的环境变量(与 process.env 合并) */
10
12
  readonly env?: Readonly<Record<string, string>>;
11
13
  }
@@ -28,6 +30,8 @@ export declare class McpClientManager {
28
30
  * @param cwd 仅 stdio 模式使用,作为子进程工作目录
29
31
  */
30
32
  connect(agentName: string, transport: AgentTransport, cwd: string, options?: ConnectOptions): Promise<Client>;
33
+ /** 更新全部已连接 Agent 后续 Sampling 请求使用的 provider 参数。 */
34
+ setSamplingProviderOptions(providerOptions: SharedV4ProviderOptions | undefined): void;
31
35
  /** 断开指定 Agent 的连接 */
32
36
  disconnect(agentName: string): Promise<void>;
33
37
  /** 断开所有连接 */
@@ -1 +1 @@
1
- import{Client as n}from"@modelcontextprotocol/sdk/client/index.js";import{StdioClientTransport as t}from"@modelcontextprotocol/sdk/client/stdio.js";import{StreamableHTTPClientTransport as e}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{registerSamplingHandler as o}from"./sampling-handler.js";const s=3e4,i="--disable-warning=ExperimentalWarning";export function buildStdioChildEnv(n){const t={...process.env,...n??{}};return{...t,NODE_OPTIONS:r(t.NODE_OPTIONS,i),ROLL_AGENT_LOG_LEVEL:t.ROLL_AGENT_LOG_LEVEL??"warn",PYTHONUTF8:t.PYTHONUTF8??"1",PYTHONIOENCODING:t.PYTHONIOENCODING??"utf-8"}}function r(n,t){const e=n?.trim();if(!e)return t;const o=e.split(/\s+/);return o.includes("--no-warnings")||o.includes(t)?e:`${e} ${t}`}export function shouldSuppressStdioChildStderrLine(n){const t=n.trim();return t.includes("ExperimentalWarning:")||t.startsWith("(Use `node --trace-warnings")||/\[INFO\s*\]\s*\[[^\]]+\]\s*MCP Server running on stdio$/.test(t)}function c(n,e,o){const s=new t({command:n.command,args:[...n.args??[]],cwd:e,env:buildStdioChildEnv(o),stderr:"pipe"});return l(s),s}function l(n){const t=n.stderr;if(!t)return;let e="";const o=n=>{shouldSuppressStdioChildStderrLine(n)||process.stderr.write(`${n}\n`)};t.on("data",n=>{e+=n.toString();const t=e.split(/\r?\n/);e=t.pop()??"";for(const n of t)o(n)}),t.on("end",()=>{e.length>0&&(o(e),e="")})}export class McpClientManager{connections=new Map;async connect(t,i,r,l={}){const a=this.connections.get(t);if(a)return a.client;const d=l.timeoutMs??s,p=l.samplingModel?{sampling:{}}:{},m=new n({name:`roll-client-${t}`,version:"0.0.1"},{capabilities:p});l.samplingModel&&o(m,l.samplingModel);const u="streamable-http"===i.type?new e(new URL(i.endpoint)):c(i,r,l.env),h=m.connect(u);let O;const g=new Promise((n,e)=>{O=setTimeout(()=>e(new Error(`Connection to "${t}" timed out after ${d}ms`)),d)});try{await Promise.race([h,g])}catch(n){throw await m.close().catch(()=>{}),n}finally{clearTimeout(O)}return this.connections.set(t,{client:m,transportType:i.type}),m}async disconnect(n){const t=this.connections.get(n);t&&(await t.client.close(),this.connections.delete(n))}async disconnectAll(){const n=[...this.connections.keys()];await Promise.all(n.map(n=>this.disconnect(n)))}isConnected(n){return this.connections.has(n)}}
1
+ import{Client as n}from"@modelcontextprotocol/sdk/client/index.js";import{StdioClientTransport as t}from"@modelcontextprotocol/sdk/client/stdio.js";import{StreamableHTTPClientTransport as e}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{registerSamplingHandler as o}from"./sampling-handler.js";const s=3e4,i="--disable-warning=ExperimentalWarning";export function buildStdioChildEnv(n){const t={...process.env,...n??{}};return{...t,NODE_OPTIONS:r(t.NODE_OPTIONS,i),ROLL_AGENT_LOG_LEVEL:t.ROLL_AGENT_LOG_LEVEL??"warn",PYTHONUTF8:t.PYTHONUTF8??"1",PYTHONIOENCODING:t.PYTHONIOENCODING??"utf-8"}}function r(n,t){const e=n?.trim();if(!e)return t;const o=e.split(/\s+/);return o.includes("--no-warnings")||o.includes(t)?e:`${e} ${t}`}export function shouldSuppressStdioChildStderrLine(n){const t=n.trim();return t.includes("ExperimentalWarning:")||t.startsWith("(Use `node --trace-warnings")||/\[INFO\s*\]\s*\[[^\]]+\]\s*MCP Server running on stdio$/.test(t)}function c(n,e,o){const s=new t({command:n.command,args:[...n.args??[]],cwd:e,env:buildStdioChildEnv(o),stderr:"pipe"});return l(s),s}function l(n){const t=n.stderr;if(!t)return;let e="";const o=n=>{shouldSuppressStdioChildStderrLine(n)||process.stderr.write(`${n}\n`)};t.on("data",n=>{e+=n.toString();const t=e.split(/\r?\n/);e=t.pop()??"";for(const n of t)o(n)}),t.on("end",()=>{e.length>0&&(o(e),e="")})}export class McpClientManager{connections=new Map;async connect(t,i,r,l={}){const a=this.connections.get(t);if(a)return a.client;const d=l.timeoutMs??s,p=l.samplingModel?{sampling:{}}:{},m=new n({name:`roll-client-${t}`,version:"0.0.1"},{capabilities:p}),u=l.samplingModel?o(m,l.samplingModel,l.samplingProviderOptions):void 0,g="streamable-http"===i.type?new e(new URL(i.endpoint)):c(i,r,l.env),h=m.connect(g);let O;const f=new Promise((n,e)=>{O=setTimeout(()=>e(new Error(`Connection to "${t}" timed out after ${d}ms`)),d)});try{await Promise.race([h,f])}catch(n){throw await m.close().catch(()=>{}),n}finally{clearTimeout(O)}return this.connections.set(t,{client:m,transportType:i.type,...u?{samplingController:u}:{}}),m}setSamplingProviderOptions(n){for(const t of this.connections.values())t.samplingController?.setProviderOptions(n)}async disconnect(n){const t=this.connections.get(n);t&&(await t.client.close(),this.connections.delete(n))}async disconnectAll(){const n=[...this.connections.keys()];await Promise.all(n.map(n=>this.disconnect(n)))}isConnected(n){return this.connections.has(n)}}
@@ -1,5 +1,17 @@
1
1
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import type { LanguageModelV4 } from "@ai-sdk/provider";
2
+ import type { ModelMessage } from "ai";
3
+ import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
4
+ export interface SamplingHandlerController {
5
+ /** 更新后续 Sampling 请求使用的 provider 参数;不影响已经发出的请求。 */
6
+ setProviderOptions(providerOptions: SharedV4ProviderOptions | undefined): void;
7
+ }
8
+ /** 组装传给 AI SDK generateText() 的调用参数(供单测复用,不依赖真实网络请求) */
9
+ export declare function buildSamplingGenerateTextParams(model: LanguageModelV4, messages: ModelMessage[], maxTokens: number, providerOptions?: SharedV4ProviderOptions): {
10
+ providerOptions?: SharedV4ProviderOptions;
11
+ maxOutputTokens?: number;
12
+ model: LanguageModelV4;
13
+ messages: ModelMessage[];
14
+ };
3
15
  /**
4
16
  * 在 MCP Client 上注册 Sampling Handler。
5
17
  *
@@ -8,4 +20,4 @@ import type { LanguageModelV4 } from "@ai-sdk/provider";
8
20
  *
9
21
  * @see https://spec.modelcontextprotocol.io/specification/client/sampling/
10
22
  */
11
- export declare function registerSamplingHandler(client: Client, model: LanguageModelV4): void;
23
+ export declare function registerSamplingHandler(client: Client, model: LanguageModelV4, providerOptions?: SharedV4ProviderOptions): SamplingHandlerController;
@@ -1 +1 @@
1
- import{CreateMessageRequestSchema as t}from"@modelcontextprotocol/sdk/types.js";import{generateText as e}from"ai";export function registerSamplingHandler(o,s){o.setRequestHandler(t,async t=>{const o=n(t.params.messages),r=t.params.maxTokens;let a;try{a=await e({model:s,messages:o,...r>0?{maxOutputTokens:r}:{}})}catch(t){throw new Error("Sampling handler: LLM generation failed",{cause:t})}return{role:"assistant",content:{type:"text",text:a.text},model:a.response.modelId,..."length"===a.finishReason?{stopReason:"maxTokens"}:{stopReason:"endTurn"}}})}function n(t){return t.map(t=>{const e=o(t.content);return"assistant"===t.role?{role:"assistant",content:[{type:"text",text:e}]}:{role:"user",content:[{type:"text",text:e}]}})}function o(t){if("string"==typeof t)return t;if("object"==typeof t&&null!==t&&"type"in t){const e=t;if("text"===e.type&&"string"==typeof e.text)return e.text}return JSON.stringify(t)}
1
+ import{CreateMessageRequestSchema as t}from"@modelcontextprotocol/sdk/types.js";import{generateText as e}from"ai";export function buildSamplingGenerateTextParams(t,e,n,r){return{model:t,messages:e,...n>0?{maxOutputTokens:n}:{},...r?{providerOptions:r}:{}}}export function registerSamplingHandler(r,o,s){let a=s;return r.setRequestHandler(t,async t=>{const r=n(t.params.messages),s=t.params.maxTokens;let i;try{i=await e(buildSamplingGenerateTextParams(o,r,s,a))}catch(t){throw new Error("Sampling handler: LLM generation failed",{cause:t})}return{role:"assistant",content:{type:"text",text:i.text},model:i.response.modelId,..."length"===i.finishReason?{stopReason:"maxTokens"}:{stopReason:"endTurn"}}}),{setProviderOptions(t){a=t}}}function n(t){return t.map(t=>{const e=r(t.content);return"assistant"===t.role?{role:"assistant",content:[{type:"text",text:e}]}:{role:"user",content:[{type:"text",text:e}]}})}function r(t){if("string"==typeof t)return t;if("object"==typeof t&&null!==t&&"type"in t){const e=t;if("text"===e.type&&"string"==typeof e.text)return e.text}return JSON.stringify(t)}
@@ -1 +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")}}
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=p(n[0]);t&&e.add(t.split(a).join("/"))}return[...e].sort()}export function readReferencedSkillDocuments(t,e){const o=r(t),s=n(o),c=[];for(const t of collectReferencedSkillPaths(e)){const e=d(o,s,t);e&&c.push(e)}return c}export function readReferencedSkillDocument(t,e){const o=p(e.split("/").join(a));if(!o)return;const s=r(t);return d(s,n(s),o)}function p(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 d(r,s,i){const u=f(r,i);if(!t(u))return;const h=n(u),p=l(s,h);return p.startsWith("..")||c(p)||!o(h).isFile()?void 0:{relativePath:i.split(a).join("/"),path:h,content:e(h,"utf-8")}}
@@ -10,11 +10,17 @@ export interface LoadedSkill {
10
10
  readonly summary: SkillSummary;
11
11
  readonly content: string;
12
12
  readonly referencePaths: readonly string[];
13
+ readonly skillRoot?: string;
14
+ }
15
+ export interface LoadedSkillReference {
16
+ readonly content: string;
17
+ readonly skillRoot?: string;
13
18
  }
14
19
  export interface SkillLibrary {
15
20
  list(): readonly SkillSummary[];
16
21
  load(name: string): LoadedSkill | undefined;
17
22
  loadReference(name: string, referencePath: string): string | undefined;
23
+ loadReferenceDocument?(name: string, referencePath: string): LoadedSkillReference | undefined;
18
24
  }
19
25
  export interface CreateSkillLibraryOptions {
20
26
  readonly agents?: readonly RegisteredAgent[];
@@ -1 +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}}}
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 k(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 p(n,e,o){if(!d(n))return[];const s=c(n,"SKILL.md");if(t(s)){const t=k(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=k(i,r,e,o);l&&a.push(l)}return a}function y(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=[...y(t.agents??[])],m=findProjectSkillsDir(e);void 0!==m&&i.push(...p(m,"project",r)),i.push(...p(c(o,".agents","skills"),"user",r));for(const n of t.extraDirs??[])i.push(...p(n,"config",r));const d=new Map,k=new Set;for(const t of i){const n=void 0!==t.skillPath?h(t.skillPath):void 0;void 0!==n&&k.has(n)||(d.has(t.summary.name)?r?.(`skill "${t.summary.name}" 重名,保留 ${d.get(t.summary.name)?.summary.source} 来源,跳过 ${t.summary.source} 来源`):(d.set(t.summary.name,t),void 0!==n&&k.add(n)))}const g=(t,r)=>{const n=d.get(t);if(!n||void 0===n.skillPath)return;const e=f(n.skillPath,r);return void 0!==e?{content:e.content,skillRoot:a(h(n.skillPath)??l(n.skillPath))}:void 0};return{list:()=>[...d.values()].map(t=>t.summary),load:t=>{const e=d.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),skillRoot:a(h(e.skillPath)??l(e.skillPath))}}return void 0!==e.fallbackContent?{summary:e.summary,content:e.fallbackContent,referencePaths:[]}:{summary:e.summary,content:e.summary.description,referencePaths:[]}}},loadReference:(t,r)=>g(t,r)?.content,loadReferenceDocument:g}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/core",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -80,9 +80,10 @@
80
80
  "marked": "18.0.5",
81
81
  "ora": "^8.1.0",
82
82
  "react": "19.2.7",
83
+ "string-width": "8.2.1",
83
84
  "yaml": "^2.7.0",
84
85
  "zod": "^3.25.76",
85
- "@roll-agent/runtime": "0.5.0"
86
+ "@roll-agent/runtime": "0.7.0"
86
87
  },
87
88
  "devDependencies": {
88
89
  "@types/cross-spawn": "6.0.6",