@roll-agent/core 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/chat/ink/app.js +1 -1
- package/dist/cli/chat/ink/commands.js +1 -1
- package/dist/cli/chat/ink/confirm-select.d.ts +2 -1
- package/dist/cli/chat/ink/confirm-select.js +1 -1
- package/dist/cli/chat/ink/history-item.js +1 -1
- package/dist/cli/chat/ink/live-region.js +1 -1
- package/dist/cli/chat/ink/markdown.d.ts +1 -0
- package/dist/cli/chat/ink/markdown.js +1 -1
- package/dist/cli/chat/ink/run-ink-repl.js +1 -1
- package/dist/cli/chat/ink/spinner.js +1 -1
- package/dist/cli/chat/ink/state.d.ts +13 -0
- package/dist/cli/chat/ink/state.js +1 -1
- package/dist/cli/chat/ink/status-line.d.ts +11 -0
- package/dist/cli/chat/ink/status-line.js +1 -1
- package/dist/cli/chat/ink/text-prompt.d.ts +1 -0
- package/dist/cli/chat/ink/text-prompt.js +1 -1
- package/dist/cli/chat/ink/thinking-text.d.ts +1 -0
- package/dist/cli/chat/ink/thinking-text.js +1 -1
- package/dist/cli/chat/ink/use-session.d.ts +2 -0
- package/dist/cli/chat/ink/use-session.js +1 -1
- package/dist/cli/commands/agent-stop.js +1 -1
- package/dist/cli/utils/token-format.d.ts +5 -0
- package/dist/cli/utils/token-format.js +1 -1
- package/dist/config/loader.js +1 -1
- package/dist/llm/engine.d.ts +1 -1
- package/dist/llm/providers.d.ts +5 -5
- package/dist/llm/providers.js +1 -1
- package/dist/mcp/client-manager.d.ts +2 -2
- package/dist/mcp/client-manager.js +1 -1
- package/dist/mcp/sampling-handler.d.ts +2 -2
- package/dist/registry/discovery.js +1 -1
- package/dist/registry/json-file.d.ts +2 -0
- package/dist/registry/json-file.js +1 -0
- package/dist/registry/process-manager.js +1 -1
- package/dist/registry/source.js +1 -1
- package/dist/registry/store.js +1 -1
- package/dist/router/llm-router.d.ts +2 -2
- package/dist/tool-runtime/argument-extractor.d.ts +2 -2
- package/package.json +11 -8
package/dist/cli/chat/ink/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as t}from"node:crypto";import{createElement as i,useState as o}from"react";import{Box as n,useInput as e}from"ink";import{useSession as s}from"./use-session.js";import{HistoryItemView as m}from"./history-item.js";import{LiveRegion as r}from"./live-region.js";import{StatusLine as a}from"./status-line.js";import{TextPrompt as f}from"./text-prompt.js";import{ConfirmSelect as l}from"./confirm-select.js";import{SlashPopup as p}from"./slash-popup.js";import{filterCommands as d,SLASH_COMMANDS as h}from"./commands.js";import{cycleThinking as c}from"./thinking.js";function u(){return h.map(t=>`${t.name} — ${t.description}`).join("\n")}export function ChatApp(g){const{session:k,model:v,contextWindow:x,onUserSubmit:C,onExit:j}=g,{state:y,submit:L,compact:M,resolveConfirm:T,setDraft:A,setThinking:b,setAutoMode:w,toggleAutoMode:S,commitHistory:D}=s(k,{model:v,contextWindow:x,...g.initialHistory?{initialHistory:g.initialHistory}:{},...g.initialThinkingLevel?{initialThinkingLevel:g.initialThinkingLevel}:{},...g.onThinkingChange?{onThinkingChange:g.onThinkingChange}:{}}),[H,$]=o(0),W="idle"===y.phase&&y.draft.startsWith("/"),E=W?d(y.draft):[],R=Math.max(E.length-1,0),U=Math.min(H,R);e((t,i)=>{i.tab&&i.shift?S():i.meta&&"."===t?b(c(y.status.thinkingLevel,1)):i.meta&&","===t&&b(c(y.status.thinkingLevel,-1))});const q="confirm"===y.phase&&void 0!==y.pendingConfirm?i(l,{prompt:y.pendingConfirm.prompt,args:y.pendingConfirm.args,onDecide:T}):i(f,{value:y.draft,disabled:"idle"!==y.phase,slashActive:W,autoApprove:y.status.autoApprove,onChange:A,onSubmit:t=>{const i=t.trim();0!==i.length?(C(i),L(i)):A("")},onSlashMove:t=>{$(i=>Math.min(Math.max(Math.min(i,R)+t,0),R))},onSlashComplete:()=>{const t=E[U];t&&(A(`${t.name} `),$(0))},onSlashRun:()=>{const i=y.draft.trim().split(/\s+/,1)[0]??"";(i=>{A("");const o=i.trim().split(/\s+/),n=o[0]??"",e=(o[1]??"").toLowerCase(),s=y.status.thinkingLevel;"/compact"!==n?"/think"!==n?"/effort"!==n?"/auto"!==n?"/help"!==n?"/exit"!==n?D({kind:"notice",id:t(),text:`未知命令 ${n}`}):j():D({kind:"notice",id:t(),text:u()}):"on"===e?w(!0):"off"===e?w(!1):S():"low"===e||"medium"===e||"high"===e?b(e):D({kind:"notice",id:t(),text:"用法: /effort low | medium | high"}):b("off"===e?"off":"on"===e?"off"===s?"medium":s:"off"===s?"medium":"off"):M()})(h.some(t=>t.name===i)?y.draft:E[U]?.name??y.draft)}});return i(n,{flexDirection:"column"},i(n,{flexDirection:"column"},...y.history.map(t=>{const o="user"===t.kind||"assistant"===t.kind,e="tool"===t.kind||"denied"===t.kind;return i(n,{key:t.id,marginTop:o?1:0,marginLeft:e?2:0},i(m,{item:t}))})),i(r,{live:y.live}),i(a,{status:y.status}),W?i(p,{matches:E,selected:U}):null,q)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SLASH_COMMANDS=[{name:"/compact",description:"压缩上下文,释放 token"},{name:"/think",description:"开关 thinking/reasoning (on | off)"},{name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{name:"/help",description:"列出可用命令"},{name:"/exit",description:"退出对话"}];export function filterCommands(e){const t=(e.split(/\s+/,1)[0]??"").toLowerCase();return SLASH_COMMANDS.filter(e=>e.name.toLowerCase().startsWith(t))}
|
|
1
|
+
export const SLASH_COMMANDS=[{name:"/compact",description:"压缩上下文,释放 token"},{name:"/think",description:"开关 thinking/reasoning (on | off)"},{name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{name:"/help",description:"列出可用命令"},{name:"/exit",description:"退出对话"}];export function filterCommands(e){const t=(e.split(/\s+/,1)[0]??"").toLowerCase();return SLASH_COMMANDS.filter(e=>e.name.toLowerCase().startsWith(t))}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
export interface ConfirmSelectProps {
|
|
3
3
|
readonly prompt: string;
|
|
4
|
+
readonly args: string;
|
|
4
5
|
readonly onDecide: (approved: boolean) => void;
|
|
5
6
|
}
|
|
6
|
-
export declare function ConfirmSelect({ prompt, onDecide }: ConfirmSelectProps): ReactElement;
|
|
7
|
+
export declare function ConfirmSelect({ prompt, args, onDecide }: ConfirmSelectProps): ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as
|
|
1
|
+
import{createElement as o,useState as r}from"react";import{Box as e,Text as n,useInput as t,useStdout as i}from"ink";export function ConfirmSelect({prompt:l,args:c,onDecide:s}){const[d,u]=r("no"),{stdout:m}=i(),f=m.columns??80;t((o,r)=>{if(r.leftArrow||r.rightArrow||r.upArrow||r.downArrow)return void u(o=>"yes"===o?"no":"yes");if(r.return||o.includes("\r")||o.includes("\n"))return void s("yes"===d);const e=o.toLowerCase();r.escape||"n"===e?s(!1):"y"===e&&s(!0)});const a=c.length>0&&"{}"!==c;return o(e,{flexDirection:"column",width:f},o(e,{flexDirection:"column",borderStyle:"round",borderColor:"yellow",paddingX:1},o(n,null,l),a?o(n,{dimColor:!0},c):null,o(e,{marginTop:1},o(n,"yes"===d?{color:"green"}:{},("yes"===d?"❯ ":" ")+"Yes"),o(n,"no"===d?{color:"green"}:{},` ${"no"===d?"❯ ":" "}No`))),o(n,{dimColor:!0},"←→/y/n 选择 · Enter 确认 · Esc 取消 · Shift+Tab 自动批准本次及后续"))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as
|
|
1
|
+
import{createElement as t}from"react";import{Box as r,Text as o}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(t){const r=t.trimStart();return i.some(t=>r.startsWith(t))}export function HistoryItemView({item:i}){switch(i.kind){case"user":return t(r,null,t(o,{color:"cyan",bold:!0},"▌ "),t(o,{color:"cyan"},i.text));case"assistant":return c(i.text)?t(r,null,t(o,{dimColor:!0},"⊘ "),t(o,{dimColor:!0},i.text.trim())):t(r,{flexDirection:"column"},...e(i.text).map((r,e)=>r.thinking?t(o,{key:String(e),dimColor:!0},r.text):t(n,{key:String(e),text:r.text})));case"tool":{const r=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return t(o,null,t(o,i.ok?{color:"green"}:{color:"red"},i.ok?"✓ ":"✗ "),t(l,{name:i.name}),r.length>0?t(o,{dimColor:!0},r):null)}case"denied":return t(o,{dimColor:!0},`⊘ ${i.name} ${i.label}`);case"compaction":return t(o,{dimColor:!0},i.notice);case"notice":return t(r,null,t(o,{color:"yellow"},"⚠ "),t(o,{color:"yellow"},i.text));case"error":return t(r,null,t(o,{color:"red"},"✗ "),t(o,{color:"red"},i.message));default:return t(o,null,"")}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as
|
|
1
|
+
import{createElement as n}from"react";import{Box as l,Text as t}from"ink";import{Spinner as e}from"./spinner.js";import{ThinkingText as o}from"./thinking-text.js";import{ToolLabel as i}from"./tool-label.js";export function LiveRegion({live:r}){return n(l,{flexDirection:"column"},r.streamingText.length>0?n(o,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText}):null,r.thinking?n(l,{marginTop:1},n(e,null),n(t,{dimColor:!0}," 思考中…")):null,...r.activeTools.map(o=>n(l,{key:o.toolCallId},n(e,null),n(t,null," "),n(i,{name:o.name}),o.args.length>0?n(t,{dimColor:!0},` ${o.args}`):null)),r.compacting?n(l,null,n(e,null),n(t,{dimColor:!0}," 压缩上下文中…")):null)}
|
|
@@ -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={"&":"&","<":"<",">":">",""":'"',"'":"'","'":"'"};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
|
|
1
|
+
import{createElement as e}from"react";import{Box as t,Text as r}from"ink";import{marked as n}from"marked";const o={"&":"&","<":"<",">":">",""":'"',"'":"'","'":"'"};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 +1 @@
|
|
|
1
|
-
import{createElement as i}from"react";import{render as t}from"ink";import{ChatApp as e}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{log as a}from"../../utils/output.js";export async function runInkRepl(r,s,l,m){let g=!1,d=!l;a.info("多轮对话已就绪(/exit 退出 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+,
|
|
1
|
+
import{createElement as i}from"react";import{render as t}from"ink";import{ChatApp as e}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{log as a}from"../../utils/output.js";export async function runInkRepl(r,s,l,m){let g=!1,d=!l;a.info("多轮对话已就绪(/exit 退出 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准)");const p=t(i(e,{session:r,model:m.model,contextWindow:r.getContextWindow(),onUserSubmit:i=>{g=!0,d||(s.updateTitle(r.id,o(i)),d=!0)},onExit:()=>{p.unmount()},initialHistory:n(r.getMessages()),...m.initialThinkingLevel?{initialThinkingLevel:m.initialThinkingLevel}:{},...m.onThinkingChange?{onThinkingChange:m.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}});await p.waitUntilExit(),r.abort(),l&&!g&&0===s.countMessages(r.id)&&s.deleteThread(r.id)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as r,useEffect as t,useState as
|
|
1
|
+
import{createElement as r,useEffect as t,useState as e}from"react";import{Text as n}from"ink";import o from"is-unicode-supported";const c=o()?["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]:["-","\\","|","/"],i=80;export function Spinner(){const[o,a]=e(0);return t(()=>{const r=setInterval(()=>{a(r=>(r+1)%c.length)},i);return()=>{clearInterval(r)}},[]),r(n,{color:"cyan"},c[o]??c[0]??"-")}
|
|
@@ -19,6 +19,11 @@ export type HistoryItem = {
|
|
|
19
19
|
readonly name: string;
|
|
20
20
|
readonly args: string;
|
|
21
21
|
readonly ok: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "denied";
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly label: string;
|
|
22
27
|
} | {
|
|
23
28
|
readonly kind: "compaction";
|
|
24
29
|
readonly id: string;
|
|
@@ -35,6 +40,7 @@ export type HistoryItem = {
|
|
|
35
40
|
export interface LiveState {
|
|
36
41
|
readonly streamingText: string;
|
|
37
42
|
readonly thinking: boolean;
|
|
43
|
+
readonly thinkTagOpen: boolean;
|
|
38
44
|
readonly activeTools: readonly ToolRowState[];
|
|
39
45
|
readonly compacting: boolean;
|
|
40
46
|
readonly producedOutput: boolean;
|
|
@@ -45,12 +51,15 @@ export interface StatusState {
|
|
|
45
51
|
readonly turnUsage: SessionTokenUsage | undefined;
|
|
46
52
|
readonly sessionUsage: SessionTokenUsage | undefined;
|
|
47
53
|
readonly contextInputTokens: number | undefined;
|
|
54
|
+
readonly outputTokensPerSecond: number | undefined;
|
|
48
55
|
readonly thinkingLevel: ThinkingLevel;
|
|
56
|
+
readonly autoApprove: boolean;
|
|
49
57
|
}
|
|
50
58
|
export type ChatPhase = "idle" | "busy" | "confirm";
|
|
51
59
|
export interface PendingConfirm {
|
|
52
60
|
readonly approvalId: string;
|
|
53
61
|
readonly prompt: string;
|
|
62
|
+
readonly args: string;
|
|
54
63
|
}
|
|
55
64
|
export interface ChatUiState {
|
|
56
65
|
readonly history: readonly HistoryItem[];
|
|
@@ -70,6 +79,9 @@ export type ChatUiAction = {
|
|
|
70
79
|
} | {
|
|
71
80
|
readonly type: "set-thinking";
|
|
72
81
|
readonly level: ThinkingLevel;
|
|
82
|
+
} | {
|
|
83
|
+
readonly type: "set-auto";
|
|
84
|
+
readonly value: boolean;
|
|
73
85
|
} | {
|
|
74
86
|
readonly type: "commit-history";
|
|
75
87
|
readonly item: HistoryItem;
|
|
@@ -92,4 +104,5 @@ export declare function createInitialState(model: string, contextWindow: number
|
|
|
92
104
|
export declare function buildConfirmPrompt(event: Extract<SessionEvent, {
|
|
93
105
|
type: "confirmation-required";
|
|
94
106
|
}>): string;
|
|
107
|
+
export declare function denialLabel(output: unknown): string | undefined;
|
|
95
108
|
export declare function chatReducer(state: ChatUiState, action: ChatUiAction): ChatUiState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatToolInput as t}from"../../utils/tool-format.js";const
|
|
1
|
+
import{formatToolInput as t}from"../../utils/tool-format.js";import{endsInsideThink as e}from"./thinking-text.js";const i={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function n(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,n){return{history:n?.history??[],draft:"",live:i,status:{model:t,contextWindow:e,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,outputTokensPerSecond:void 0,thinkingLevel:n?.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 o(t){const e="auto"===t.reason?"自动压缩":"手动压缩",i=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`🗜 ${e}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${i}`:`🗜 ${e}:无需压缩`}const r=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function s(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=s(t);if(void 0!==e)return r.find(([t])=>e.startsWith(t))?.[1]}function a(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 u(r,s,u){switch(u.type){case"message-start":return{...r,live:{...r.live,thinking:!0}};case"text-delta":return{...r,live:{...r.live,thinking:!1,producedOutput:!0,streamingText:r.live.streamingText+u.delta}};case"tool-call":{const i=r.live.streamingText.length>0?[{kind:"assistant",id:s,text:n(r.live.streamingText,r.live.thinkTagOpen)}]:[];return{...r,history:[...r.history,...i],live:{...r.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:e(r.live.streamingText,r.live.thinkTagOpen),activeTools:[...r.live.activeTools,{toolCallId:u.toolCallId,name:`${u.agentName}.${u.toolName}`,args:t(u.input)}]}}}case"tool-result":return a(r,s,u);case"confirmation-required":return{...r,phase:"confirm",pendingConfirm:{approvalId:u.approvalId,prompt:buildConfirmPrompt(u),args:t(u.input)}};case"compaction-start":return{...r,live:{...r.live,compacting:!0}};case"context-compacted":return{...r,live:{...r.live,compacting:!1},history:[...r.history,{kind:"compaction",id:s,notice:o(u)}]};case"message-finish":{const t=[];return r.live.streamingText.length>0?t.push({kind:"assistant",id:s,text:n(r.live.streamingText,r.live.thinkTagOpen)}):!r.live.producedOutput&&(u.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:s,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),u.stoppedAtStepLimit&&t.push({kind:"notice",id:`${s}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...r,history:[...r.history,...t],live:{...i},status:{...r.status,turnUsage:u.totalUsage,sessionUsage:u.sessionUsage,contextInputTokens:u.contextInputTokens,outputTokensPerSecond:u.outputTokensPerSecond}}}case"error":return{...r,history:[...r.history,{kind:"error",id:s,message:u.message}],live:{...i}};default:return r}}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:{...i},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:{...i},phase:"busy",pendingConfirm:void 0};case"session-event":return u(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...i}};default:return t}}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import type { StatusState } from "./state.ts";
|
|
3
|
+
interface SegmentProps {
|
|
4
|
+
readonly color?: string;
|
|
5
|
+
readonly dimColor?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface StatusSegmentView {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly text: string;
|
|
10
|
+
readonly props: SegmentProps;
|
|
11
|
+
}
|
|
12
|
+
export declare function composeStatusSegments(status: StatusState, width: number): StatusSegmentView[];
|
|
3
13
|
export declare function StatusLine({ status }: {
|
|
4
14
|
status: StatusState;
|
|
5
15
|
}): ReactElement;
|
|
16
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as o}from"react";import{Box as
|
|
1
|
+
import{createElement as o}from"react";import{Box as t,Text as e,useStdout as s}from"ink";import{computeUsageParts as n,contextPressure as p,formatContextUsage as r,formatThroughput as i,formatTokens as c,formatTurnUsage as l}from"../../utils/token-format.js";import{displayWidth as u}from"./markdown.js";import{thinkingLabel as m}from"./thinking.js";const a={ok:{dimColor:!0},warn:{color:"yellow"},critical:{color:"red"}},k={off:"off",low:"low",medium:"med",high:"high"},d=["tps","session","turn","think"],f=" · ";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:m(o.thinkingLevel),compact:`🧠 ${k[o.thinkingLevel]}`,props:"off"===o.thinkingLevel?{color:"yellow"}:{dimColor:!0}}];o.autoApprove&&s.push({key:"auto",full:"⏵⏵ auto-approve",compact:"⏵⏵ auto",props:{color:"yellow"}});const h=r(e);void 0!==h&&void 0!==e.usedTokens&&void 0!==e.contextWindow&&s.push({key:"ctx",full:h,compact:`ctx ${c(e.usedTokens)}/${c(e.contextWindow)}`,props:a[p(e.percentLeft)]});const y=l(e);if(void 0!==y){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:y,compact:o.join(" "),props:{dimColor:!0}})}const v=i(o.outputTokensPerSecond);void 0!==v&&s.push({key:"tps",full:v,compact:v.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})),g=o=>u(o.map(o=>o.text).join(f))<=t,x=w(s,"full");if(g(x))return x;let T=s,$=w(T,"compact");for(const o of d){if(g($))break;T=T.filter(t=>t.key!==o),$=w(T,"compact")}return $}export function StatusLine({status:n}){const{stdout:p}=s(),r=p.columns??80,i=composeStatusSegments(n,r);return o(t,{width:r},o(e,{wrap:"truncate-end"},...i.flatMap((t,s)=>[...s>0?[o(e,{key:`${t.key}-sep`,dimColor:!0},f)]:[],o(e,{key:t.key,...t.props},t.text)])))}
|
|
@@ -3,6 +3,7 @@ export interface TextPromptProps {
|
|
|
3
3
|
readonly value: string;
|
|
4
4
|
readonly disabled: boolean;
|
|
5
5
|
readonly slashActive: boolean;
|
|
6
|
+
readonly autoApprove: boolean;
|
|
6
7
|
readonly onChange: (value: string) => void;
|
|
7
8
|
readonly onSubmit: (value: string) => void;
|
|
8
9
|
readonly onSlashMove: (direction: 1 | -1) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as r,useRef as t}from"react";import{Box as e,Text as n,useInput as o,useStdout as i}from"ink";function
|
|
1
|
+
import{createElement as r,useRef as t}from"react";import{Box as e,Text as n,useInput as o,useStdout as i}from"ink";function l(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function TextPrompt(u){const{value:c,disabled:a,slashActive:s,autoApprove:d,onChange:f,onSubmit:m}=u,{stdout:h}=i(),p=h.columns??80,v=t(c);v.current=c;const b=r=>{v.current=r,f(r)};o((r,t)=>{if(t.meta&&("."===r||","===r))return;if(l(r))return;if(t.backspace||t.delete)return void b(v.current.slice(0,-1));if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void b(`${v.current}\n`);if(t.return||r.includes("\r")){if(s)return void u.onSlashRun();const t=r.split("\r",1)[0]??"";return void m(v.current+t)}if(s){if(t.upArrow)return void u.onSlashMove(-1);if(t.downArrow)return void u.onSlashMove(1);if(t.tab&&!t.shift)return void u.onSlashComplete();if(t.escape)return void b("")}t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&b(v.current+r)},{isActive:!a});const S=c.split("\n"),w=r(e,{flexDirection:"column"},...S.map((t,o)=>{const i=o===S.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)})),A=s?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":d?"Shift+Tab 关闭 · Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",g=d?r(e,null,r(n,{color:"yellow"},"⏵⏵ auto"),r(n,{dimColor:!0},` · ${A}`)):r(n,{dimColor:!0},A);return r(e,{flexDirection:"column",width:p},r(e,{borderStyle:"round",borderColor:a?"gray":"cyan",paddingX:1},w),g)}
|
|
@@ -4,6 +4,7 @@ export interface TextSegment {
|
|
|
4
4
|
readonly thinking: boolean;
|
|
5
5
|
}
|
|
6
6
|
export declare function parseThinking(text: string): TextSegment[];
|
|
7
|
+
export declare function endsInsideThink(text: string, startInside: boolean): boolean;
|
|
7
8
|
export declare function ThinkingText({ text }: {
|
|
8
9
|
text: string;
|
|
9
10
|
}): ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createElement as t}from"react";import{Text as n}from"ink";export function parseThinking(t){const n=[],e=/<think>([\s\S]*?)(?:<\/think>|$)/gi;let
|
|
1
|
+
import{createElement as t}from"react";import{Text as n}from"ink";export function parseThinking(t){const n=[],e=t.toLowerCase(),i=e.indexOf("<think>"),r=e.indexOf("</think>");let l=t;r>=0&&(i<0||r<i)&&(n.push({text:t.slice(0,r),thinking:!0}),l=t.slice(r+8));const h=/<think>([\s\S]*?)(?:<\/think>|$)/gi;let s=0,o=h.exec(l);for(;null!==o;)o.index>s&&n.push({text:l.slice(s,o.index),thinking:!1}),n.push({text:o[1]??"",thinking:!0}),s=h.lastIndex,o=h.exec(l);return s<l.length&&n.push({text:l.slice(s),thinking:!1}),n.filter(t=>t.text.length>0)}export function endsInsideThink(t,n){let e=n;const i=/<\/?think>/gi;let r=i.exec(t);for(;null!==r;)e=!r[0].startsWith("</"),r=i.exec(t);return e}export function ThinkingText({text:e}){const i=parseThinking(e);return 0===i.length?t(n,null,""):t(n,null,...i.map((e,i)=>t(n,{key:String(i),...e.thinking?{dimColor:!0}:{}},e.text)))}
|
|
@@ -15,6 +15,8 @@ export interface UseSessionResult {
|
|
|
15
15
|
readonly resolveConfirm: (approved: boolean) => void;
|
|
16
16
|
readonly setDraft: (value: string) => void;
|
|
17
17
|
readonly setThinking: (level: ThinkingLevel) => void;
|
|
18
|
+
readonly setAutoMode: (value: boolean) => void;
|
|
19
|
+
readonly toggleAutoMode: () => void;
|
|
18
20
|
readonly commitHistory: (item: HistoryItem) => void;
|
|
19
21
|
}
|
|
20
22
|
export declare function useSession(session: AgentSession, options: UseSessionOptions): UseSessionResult;
|
|
@@ -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(
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{useCallback as t,useReducer as n,useRef as i}from"react";import{chatReducer as r,createInitialState as o}from"./state.js";import{log as s}from"../../utils/output.js";const a=32;function c(e){return e instanceof Error?e.message:String(e)}export function useSession(u,p){const[l,m]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),d=p.onThinkingChange,y=i(null),v=i(!1),f=i(!1),g=t(async t=>{let n,i="";const r=()=>{if(void 0!==n&&(clearTimeout(n),n=void 0),i.length>0){const t=i;i="",m({type:"session-event",id:e(),event:{type:"text-delta",delta:t}})}};try{for await(const o of t)if("debug"!==o.type)if("text-delta"!==o.type){if(r(),"confirmation-required"===o.type){if(v.current){u.approve(o.approvalId);continue}const t=new Promise(e=>{y.current=e});m({type:"session-event",id:e(),event:o});const n=await t;y.current=null,n?u.approve(o.approvalId):u.reject(o.approvalId,"用户取消"),m({type:"confirm-resolved"});continue}m({type:"session-event",id:e(),event:o})}else i+=o.delta,void 0===n&&(n=setTimeout(r,a));else s.debug(`chat.${o.stage} ${o.message}`)}catch(t){r(),m({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:c(t)}})}finally{r(),m({type:"turn-end"}),f.current=!1}},[u]),h=t(t=>{f.current||(f.current=!0,m({type:"submit-user",id:e(),text:t}),g(u.send(t)).catch(()=>{}))},[g,u]),k=t(()=>{f.current||(f.current=!0,m({type:"start-compaction"}),g(u.compact("manual")).catch(()=>{}))},[g,u]),x=t(e=>{y.current?.(e)},[]),T=t(e=>{m({type:"set-draft",value:e})},[]),b=t(e=>{m({type:"set-thinking",level:e}),d?.(e)},[d]),w=t(e=>{v.current=e,m({type:"set-auto",value:e}),e&&y.current?.(!0)},[]),j=t(()=>{w(!v.current)},[w]),H=t(e=>{m({type:"commit-history",item:e})},[]);return{state:l,submit:h,compact:k,resolveConfirm:x,setDraft:T,setThinking:b,setAutoMode:w,toggleAutoMode:j,commitHistory:H}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineCommand as e}from"citty";import{loadAgentsConfig as t}from"../../config/loader.js";import{stopAgentGracefully as r}from"../../registry/process-manager.js";import{AgentStore as n}from"../../registry/store.js";import{log as o}from"../utils/output.js";export default e({meta:{description:"停止由 Roll 托管的 core-managed Agent"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:e}){const{agentsConfig:a}=t(),s=new n(a.dataDir),i=s.findByName(e.name);if(!i)return o.error(`Agent "${e.name}" 未找到`),void(process.exitCode=1);switch(i.runtime.ownership){case"on-demand":return void o.success(`Agent "${e.name}" 为按需模式,无需手动停止。`);case"external-managed":return o.info(`Agent "${e.name}" 由外部服务管理,请在外部停止。`),void("streamable-http"===i.transport.type&&o.info(`端点: ${i.transport.endpoint}`))}let m=!1;try{m=await r(a.dataDir,i.skill.name)}catch(t){return o.error(`停止 Agent "${e.name}" 失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}s.updateStatus(i.skill.name,"stopped"),m?o.success(`Agent "${e.name}" 已停止`):o.info(`Agent "${e.name}" 当前未运行`)}});
|
|
1
|
+
import{defineCommand as e}from"citty";import{loadAgentsConfig as t}from"../../config/loader.js";import{stopAgentGracefully as r}from"../../registry/process-manager.js";import{AgentStore as n}from"../../registry/store.js";import{log as o}from"../utils/output.js";export default e({meta:{description:"停止由 Roll 托管的 core-managed Agent"},args:{name:{type:"positional",description:"Agent 名称",required:!0}},async run({args:e}){const{agentsConfig:a}=t(),s=new n(a.dataDir),i=s.findByName(e.name);if(!i)return o.error(`Agent "${e.name}" 未找到`),void(process.exitCode=1);switch(i.runtime.ownership){case"on-demand":return void o.success(`Agent "${e.name}" 为按需模式,无需手动停止。`);case"external-managed":return o.info(`Agent "${e.name}" 由外部服务管理,请在外部停止。`),void("streamable-http"===i.transport.type&&o.info(`端点: ${i.transport.endpoint}`))}let m=!1;try{"win32"===process.platform&&o.info("Windows 下停止为强制终止(无优雅退出信号),Agent 不会执行清理逻辑"),m=await r(a.dataDir,i.skill.name)}catch(t){return o.error(`停止 Agent "${e.name}" 失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}s.updateStatus(i.skill.name,"stopped"),m?o.success(`Agent "${e.name}" 已停止`):o.info(`Agent "${e.name}" 当前未运行`)}});
|
|
@@ -4,6 +4,7 @@ export interface UsageLineParts {
|
|
|
4
4
|
readonly outputTokens?: number;
|
|
5
5
|
readonly sessionTokens?: number;
|
|
6
6
|
readonly cachedInputTokens?: number;
|
|
7
|
+
readonly cacheWriteTokens?: number;
|
|
7
8
|
readonly reasoningTokens?: number;
|
|
8
9
|
readonly percentLeft?: number;
|
|
9
10
|
readonly usedTokens?: number;
|
|
@@ -15,4 +16,8 @@ export interface UsagePartsOptions {
|
|
|
15
16
|
export declare function formatTokens(value: number): string;
|
|
16
17
|
export declare function sessionTotal(usage: SessionTokenUsage): number;
|
|
17
18
|
export declare function computeUsageParts(turn: SessionTokenUsage | undefined, session: SessionTokenUsage | undefined, contextWindow: number | undefined, contextInputTokens: number | undefined, options?: UsagePartsOptions): UsageLineParts;
|
|
19
|
+
export declare function formatTurnUsage(parts: UsageLineParts): string | undefined;
|
|
20
|
+
export declare function formatContextUsage(parts: UsageLineParts): string | undefined;
|
|
21
|
+
export declare function formatThroughput(tokensPerSecond: number | undefined): string | undefined;
|
|
22
|
+
export declare function contextPressure(percentLeft: number | undefined): "ok" | "warn" | "critical";
|
|
18
23
|
export declare function formatUsageLine(parts: UsageLineParts): string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const o=12e3,n=.2;export function formatTokens(o){return o>=1e3?`${(o/1e3).toFixed(1)}k`:String(o)}export function sessionTotal(o){return void 0!==o.totalTokens&&o.totalTokens>0?o.totalTokens:(o.inputTokens??0)+(o.outputTokens??0)}function e(o,e
|
|
1
|
+
const o=12e3,n=.2;function t(o){return o.endsWith(".0")?o.slice(0,-2):o}export function formatTokens(o){return o>=1e6?`${t((o/1e6).toFixed(1))}M`:o>=1e3?`${t((o/1e3).toFixed(1))}k`:String(o)}export function sessionTotal(o){return void 0!==o.totalTokens&&o.totalTokens>0?o.totalTokens:(o.inputTokens??0)+(o.outputTokens??0)}function e(o,t,e){const s=Math.min(e,Math.floor(t*n)),i=Math.max(t-s,1),r=Math.max(o-s,0),u=Math.max(i-r,0);return Math.min(Math.max(Math.round(u/i*100),0),100)}export function computeUsageParts(n,t,s,i,r){const u=i??n?.inputTokens,a=r?.baselineHeadroom??o,c=n?.cachedInputTokens,T=n?.cacheWriteTokens,k=n?.reasoningTokens;return{...void 0!==n?.inputTokens?{inputTokens:n.inputTokens}:{},...void 0!==n?.outputTokens?{outputTokens:n.outputTokens}:{},...t?{sessionTokens:sessionTotal(t)}:{},...void 0!==c&&c>0?{cachedInputTokens:c}:{},...void 0!==T&&T>0?{cacheWriteTokens:T}:{},...void 0!==k&&k>0?{reasoningTokens:k}:{},...void 0!==s&&void 0!==u?{percentLeft:e(u,s,a),usedTokens:u,contextWindow:s}:{}}}export function formatTurnUsage(o){const n=[];if(void 0!==o.inputTokens){const t=[];void 0!==o.cachedInputTokens&&t.push(`+${formatTokens(o.cachedInputTokens)} cached`),void 0!==o.cacheWriteTokens&&t.push(`+${formatTokens(o.cacheWriteTokens)} cache-write`);const e=t.length>0?` (${t.join(", ")})`:"";n.push(`in ${formatTokens(o.inputTokens)}${e}`)}if(void 0!==o.outputTokens){const t=void 0!==o.reasoningTokens?` (+${formatTokens(o.reasoningTokens)} reasoning)`:"";n.push(`out ${formatTokens(o.outputTokens)}${t}`)}return n.length>0?`turn ${n.join(" ")}`:void 0}export function formatContextUsage(o){if(void 0!==o.percentLeft&&void 0!==o.usedTokens&&void 0!==o.contextWindow)return`ctx ${formatTokens(o.usedTokens)}/${formatTokens(o.contextWindow)} (${String(o.percentLeft)}% left)`}export function formatThroughput(o){if(void 0===o||!Number.isFinite(o)||o<=0)return;const n=o>=10?Math.round(o):o.toFixed(1);return`${String(n)} tok/s`}export function contextPressure(o){return void 0===o||o>25?"ok":o>10?"warn":"critical"}export function formatUsageLine(o){const n=[],t=formatTurnUsage(o);void 0!==t&&n.push(t),void 0!==o.sessionTokens&&n.push(`session ${formatTokens(o.sessionTokens)}`);const e=formatContextUsage(o);return void 0!==e&&n.push(e),n.length>0?`↳ ${n.join(" · ")}`:void 0}
|
package/dist/config/loader.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as n,existsSync as r}from"node:fs";import{
|
|
1
|
+
import{readFileSync as n,existsSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as o}from"node:path";import{parse as e}from"yaml";import{agentsConfigSchema as i,installConfigSchema as s,rollConfigSchema as a}from"./schema.js";import{DEFAULT_CONFIG as f,CONFIG_FILE_NAMES as c}from"./defaults.js";import{decodeFromYaml as u}from"./key-codec.js";import{detectKnownConfigMigrations as l,formatConfigMigrationError as g}from"./migration.js";function d(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(d);if(p(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=d(o);return r}return n}function p(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function h(n){return p(n)&&"number"==typeof n.line&&"number"==typeof n.col}function m(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(h)}function w(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(m(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function v(n){let t=o(n);const e=o("/");for(;t!==e;){for(const n of c){const e=o(t,n);if(r(e))return e}const n=o(t,"..");if(n===t)break;t=n}}function $(n){return"~"===n?t():n.startsWith("~/")||n.startsWith("~\\")?o(t(),n.slice(2)):n}function C(n){return{...n,agents:{...n.agents,dataDir:$(n.agents.dataDir)},runtime:{...n.runtime,threadsDir:$(n.runtime.threadsDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:$(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:$(r.sessionsDir)}:{}}]))}}}const P={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=e(n)}catch(n){throw new Error(w(r,n),{cause:n instanceof Error?n:void 0})}if(!p(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function y(n,r,t={}){const o=parseConfigDocument(n,r),e=l(o,t);if(e.needsMigration)throw new Error(g(r,e));return o}export function validateConfigText(n,r){const t=y(n,r),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=D(f,o),i=a.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return C(i.data)}function j(n,r){const t=y(n,r,{scope:"agents"}),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.agents)?o.agents:{},s=D(f.agents,e),a=i.safeParse(s);if(!a.success){const n=a.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...a.data,dataDir:$(a.data.dataDir)}}function E(n,r){const t=parseConfigDocument(n,r),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.install)?o.install:{},i=D(f.install,e),a=s.safeParse(i);if(!a.success){const n=a.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return a.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:f.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:E(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:C(f),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...f.agents,dataDir:$(f.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:j(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??v(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:P.notFound,configPath:void 0};if(!r(o))return{status:P.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:P.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=l(i);if(s.needsMigration)return{status:P.needsMigration,configPath:o,raw:e,report:s};try{return{status:P.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:P.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function D(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=D(r,e)}return t}
|
package/dist/llm/engine.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare class LLMEngine {
|
|
|
13
13
|
/**
|
|
14
14
|
* 生成文本(非流式)。
|
|
15
15
|
*
|
|
16
|
-
* 使用 AI SDK
|
|
16
|
+
* 使用 AI SDK v7 的 generateText,支持 Anthropic provider。
|
|
17
17
|
*/
|
|
18
18
|
generateText(prompt: string, options?: LLMEngineOptions): Promise<string>;
|
|
19
19
|
/** 解析 provider + model,创建 AI SDK LanguageModel 实例 */
|
package/dist/llm/providers.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
|
|
2
2
|
import { runtimeThinkingLevels } from "../config/schema.ts";
|
|
3
3
|
export type ThinkingLevel = (typeof runtimeThinkingLevels)[number];
|
|
4
|
-
export declare function thinkingProviderOptions(providerName: string, modelName: string, level: ThinkingLevel):
|
|
4
|
+
export declare function thinkingProviderOptions(providerName: string, modelName: string, level: ThinkingLevel): SharedV4ProviderOptions | undefined;
|
|
5
5
|
/**
|
|
6
6
|
* 根据 provider 名称创建 AI SDK LanguageModel 实例。
|
|
7
7
|
*
|
|
8
8
|
* 支持: anthropic, openai, deepseek, qwen
|
|
9
9
|
*/
|
|
10
|
-
export declare function createProviderModel(providerName: string, modelName: string, apiKey: string, baseURL?: string):
|
|
10
|
+
export declare function createProviderModel(providerName: string, modelName: string, apiKey: string, baseURL?: string): LanguageModelV4;
|
|
11
11
|
/** generateText 调用目的 */
|
|
12
12
|
export type LLMCallPurpose = "structured-output" | "text" | "sampling" | "chat";
|
|
13
13
|
/** resolveLLMCall 的返回值 */
|
|
14
14
|
export interface ResolvedLLMCall {
|
|
15
|
-
readonly model:
|
|
16
|
-
readonly providerOptions?:
|
|
15
|
+
readonly model: LanguageModelV4;
|
|
16
|
+
readonly providerOptions?: SharedV4ProviderOptions;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* 按 provider + 调用目的解析 generateText 的完整调用上下文。
|
package/dist/llm/providers.js
CHANGED
|
@@ -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
|
|
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,11 +1,11 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LanguageModelV4 } 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
|
-
readonly samplingModel?:
|
|
8
|
+
readonly samplingModel?: LanguageModelV4;
|
|
9
9
|
/** 注入到 stdio 子进程的环境变量(与 process.env 合并) */
|
|
10
10
|
readonly env?: Readonly<Record<string, string>>;
|
|
11
11
|
}
|
|
@@ -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=n?{...process.env,...n}:{};return{...t,NODE_OPTIONS:r(t.NODE_OPTIONS,i),ROLL_AGENT_LOG_LEVEL:t.ROLL_AGENT_LOG_LEVEL??"warn"}}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
|
|
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=n?{...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,5 +1,5 @@
|
|
|
1
1
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LanguageModelV4 } from "@ai-sdk/provider";
|
|
3
3
|
/**
|
|
4
4
|
* 在 MCP Client 上注册 Sampling Handler。
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@ import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
|
8
8
|
*
|
|
9
9
|
* @see https://spec.modelcontextprotocol.io/specification/client/sampling/
|
|
10
10
|
*/
|
|
11
|
-
export declare function registerSamplingHandler(client: Client, model:
|
|
11
|
+
export declare function registerSamplingHandler(client: Client, model: LanguageModelV4): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as t,existsSync as r,realpathSync as n}from"node:fs";import{resolve as
|
|
1
|
+
import{readFileSync as t,existsSync as r,realpathSync as n}from"node:fs";import{readJsonFile as e}from"./json-file.js";import{resolve as o,sep as i}from"node:path";import a from"gray-matter";import{z as s}from"zod";import{parse as p}from"yaml";import{createDefaultRuntimeForTransport as d}from"../types/agent.js";const l="SKILL.md",m="package.json",c=s.object({name:s.string().min(1),purpose:s.string().optional(),example:s.string().optional(),default:s.string().optional()}),u=s.object({required:s.array(c).optional(),optional:s.array(c).optional()});export function discoverAgent(n){const e=o(n),i=o(e,l);if(!r(i))throw new Error(`SKILL.md not found in ${e}`);const s=t(i,"utf-8"),{data:p,content:m}=a(s),c=b(e),u=p,f=u.name,h=u.description;if("string"!=typeof f||0===f.length)throw new Error(`SKILL.md missing required field "name" in ${i}`);if("string"!=typeof h||0===h.length)throw new Error(`SKILL.md missing required field "description" in ${i}`);const w=u.metadata??{},$={};for(const[t,r]of Object.entries(w))$[t]=String(r);const L="string"==typeof u.license?u.license:void 0,S="string"==typeof u.compatibility?u.compatibility:void 0,I=g(e,$,i),k={name:f,description:h,...L?{license:L}:{},...S?{compatibility:S}:{},metadata:$,...I?{env:I}:{}},q=c?v(c):void 0;if(q&&j($)){if(!E(y($),q.transport))throw new Error(`Conflicting runtime metadata in ${i}: package.json#rollAgent and SKILL.md metadata disagree`)}const A=q?.transport??y($);return{skill:k,transport:A,runtime:q?.runtime??d(A),skillPath:i,skillBody:m.trim()}}function f(t,r){if(void 0===t)return;const n=u.safeParse(t);if(!n.success){const t=n.error.issues.map(t=>`${t.path.length>0?`env.${t.path.join(".")}`:"env"}: ${t.message}`).join("; ");throw new Error(`SKILL.md has invalid "env" declaration in ${r}: ${t}`)}const e=n.data;return e.required||e.optional?{...e.required?{required:e.required.map(w)}:{},...e.optional?{optional:e.optional.map(w)}:{}}:void 0}function g(e,i,a){const s=i["roll-env-file"];if(!s)return;const d=o(e,s);if(!h(e,d))throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${a}`);if(!r(d))throw new Error(`SKILL.md roll-env-file not found: ${d}`);const l=n(e),m=n(d);if(!h(l,m))throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${a}`);let c;try{c=p(t(m,"utf-8"))}catch(t){throw new Error(`Failed to parse roll-env-file for ${a}: ${t instanceof Error?t.message:String(t)}`)}return f(c,m)}function h(t,r){return r===t||r.startsWith(`${t}${i}`)}function w(t){return{name:t.name,...t.purpose?{purpose:t.purpose}:{},...t.example?{example:t.example}:{},...t.default?{default:t.default}:{}}}function y(t){if("streamable-http"===("streamable-http"===t["roll-transport"]?"streamable-http":"stdio")){const r=t["roll-endpoint"];if(!r)throw new Error('SKILL.md declares streamable-http transport but missing "roll-endpoint"');return{type:"streamable-http",endpoint:r}}const r=(t["roll-command"]??"node --experimental-strip-types src/index.ts").split(/\s+/),n=r[0],e=r.slice(1);if(!n)throw new Error("Invalid roll-command in SKILL.md");return e.length>0?{type:"stdio",command:n,args:e}:{type:"stdio",command:n}}function b(t){const n=o(t,m);if(!r(n))return;let i;try{i=e(n)}catch{throw new Error(`Invalid package.json in ${t}`)}if(!k(i)||!k(i.rollAgent))return;const a=i.rollAgent,s=k(a.runtime)?a.runtime:void 0;if(!s)throw new Error(`package.json#rollAgent.runtime is required in ${n}`);const p={runtime:{ownership:S(s.ownership)??"",transport:S(s.transport)??""}};if(k(a.start)){const t=S(a.start.command),r=I(a.start.args);(t||r)&&(p.start={...t?{command:t}:{},...r?{args:r}:{}})}if(k(a.endpoint)){const t=S(a.endpoint.url),r=S(a.endpoint.path),n="number"==typeof a.endpoint.port?a.endpoint.port:void 0;(t||r||void 0!==n)&&(p.endpoint={...t?{url:t}:{},...r?{path:r}:{},...void 0!==n?{port:n}:{}})}if(k(a.setup)&&k(a.setup.playwright)){const t=I(a.setup.playwright.browsers);t&&t.length>0&&(p.setup={playwright:{browsers:t}})}return p}function v(t){const{ownership:r,transport:n}=t.runtime;if("on-demand"===r&&"stdio"===n){const r=t.start?.command;if(!r)throw new Error("package.json#rollAgent.start.command is required for stdio runtime");const n=t.start?.args;return{transport:n?{type:"stdio",command:r,args:n}:{type:"stdio",command:r},runtime:{ownership:"on-demand"}}}if("core-managed"===r&&"streamable-http"===n){const r=t.start?.command,n=$(t),e=t.endpoint?.path,o=t.endpoint?.port;if(!r||!e||void 0===o)throw new Error("package.json#rollAgent for core-managed streamable-http requires start.command, endpoint.path and endpoint.port");const i=t.start?.args,a=t.setup?.playwright?.browsers;return{transport:{type:"streamable-http",endpoint:n},runtime:{ownership:"core-managed",start:i?{command:r,args:i}:{command:r},endpoint:{path:e,port:o},...a&&a.length>0?{setup:{playwright:{browsers:a}}}:{}}}}if("external-managed"===r&&"streamable-http"===n)return{transport:{type:"streamable-http",endpoint:$(t)},runtime:{ownership:"external-managed"}};throw new Error(`Unsupported package.json#rollAgent runtime combination: ${r}/${n}`)}function $(t){const r=t.endpoint?.url;if(r)return r;const n=t.endpoint?.path,e=t.endpoint?.port;if(!n||void 0===e)throw new Error("package.json#rollAgent.streamable-http requires endpoint.url or endpoint.path + endpoint.port");return L(n,e)}function L(t,r){const n=t.startsWith("/")?t:`/${t}`;return`http://127.0.0.1:${String(r)}${n}`}function j(t){return"string"==typeof t["roll-transport"]||"string"==typeof t["roll-endpoint"]||"string"==typeof t["roll-command"]}function E(t,r){if(t.type!==r.type)return!1;if("streamable-http"===t.type&&"streamable-http"===r.type)return t.endpoint===r.endpoint;if("stdio"!==t.type||"stdio"!==r.type)return!1;const n=t.args??[],e=r.args??[];return t.command===r.command&&n.join("\0")===e.join("\0")}function S(t){return"string"==typeof t&&t.length>0?t:void 0}function I(t){if(!Array.isArray(t))return;const r=t.filter(t=>"string"==typeof t);return r.length>0?r:void 0}function k(t){return"object"==typeof t&&null!==t}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as e}from"node:fs";export function readJsonFile(o){const r=e(o,"utf-8"),t=65279===r.charCodeAt(0)?r.slice(1):r;return JSON.parse(t)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import t from"cross-spawn";import{closeSync as e,existsSync as n,mkdirSync as r,openSync as i,readFileSync as o,unlinkSync as s,writeFileSync as a}from"node:fs";import{resolve as c,dirname as l}from"node:path";import{McpClientManager as m}from"../mcp/client-manager.js";import{readJsonFile as d}from"./json-file.js";import{resolveDevSpawnSpec as p}from"./dev-spawn.js";import{inferAgentSourceType as u}from"./source.js";const g=1,f="unknown",h=15e3,w=2e3,A=500;function $(t,e){return c(t,"pids",`${e}.pid`)}function k(t,e){return c(t,"pids",`${e}.runtime.json`)}function v(t,e){for(const r of[$(t,e),k(t,e)])n(r)&&s(r)}export function cleanupOrphanAgentRuntimeMetadata(t,e){return void 0===getAgentPid(t,e)&&(v(t,e),!0)}export function getAgentLogPath(t,e){return c(t,"logs",`${e}.log`)}function y(t){try{return process.kill(t,0),!0}catch{return!1}}export function getAgentPid(t,e){const r=$(t,e);if(!n(r))return;const i=Number(o(r,"utf-8").trim());if(!Number.isNaN(i)&&y(i))return i;v(t,e)}export function getRollCoreVersion(){try{const t=c(import.meta.dirname,"../../package.json"),e=d(t);return S(e)&&"string"==typeof e.version?e.version:f}catch{return f}}export function writeAgentRuntimeSidecar(t,e,i){const o=k(e,t.skill.name),s=l(o);n(s)||r(s,{recursive:!0});const c={schemaVersion:1,agentName:t.skill.name,pid:i,coreVersion:getRollCoreVersion(),startedAt:(new Date).toISOString(),..."streamable-http"===t.transport.type?{endpoint:t.transport.endpoint}:{}};a(o,`${JSON.stringify(c,null,2)}\n`,"utf-8")}export function inspectManagedAgentRuntime(t,e){const n=getAgentPid(e,t.skill.name),r=getRollCoreVersion(),i="streamable-http"===t.transport.type?t.transport.endpoint:void 0;if(void 0===n){const n=R(e,t.skill.name);return void 0!==n?{..."invalid"!==n?{sidecar:n}:{},expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:[{code:"orphan-sidecar",message:"invalid"===n?"runtime sidecar 存在但没有活动 PID,且无法解析":`runtime sidecar 记录 PID ${String(n.pid)},但没有活动 PID`,fix:`运行 \`roll doctor --fix\` 清理 ${t.skill.name} 的过期 runtime 元数据`}]}:{expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:[]}}const o=[],s=R(e,t.skill.name);return"invalid"===s?(o.push({code:"invalid-sidecar",message:"runtime sidecar 无法解析",fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o}):s?(s.pid!==n&&o.push({code:"pid-mismatch",message:`runtime sidecar PID ${String(s.pid)} 与活动 PID ${String(n)} 不一致`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),s.coreVersion!==r&&o.push({code:"version-mismatch",message:`runtime sidecar 来自 core ${s.coreVersion},当前 core 是 ${r}`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),i&&s.endpoint!==i&&o.push({code:"endpoint-mismatch",message:`runtime sidecar endpoint 是 ${s.endpoint??"n/a"},当前配置是 ${i}`,fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,sidecar:s,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o}):(o.push({code:"missing-sidecar",message:"进程存在但缺少 runtime sidecar",fix:`运行 \`roll agent stop ${t.skill.name}\` 后重新 \`roll agent start ${t.skill.name}\``}),{pid:n,expectedCoreVersion:r,...i?{expectedEndpoint:i}:{},issues:o})}export async function probeAgentEndpoint(t,e={}){const n=new m;try{const r=await n.connect(t.skill.name,t.transport,t.installPath,void 0!==e.timeoutMs?{timeoutMs:e.timeoutMs}:{});await r.listTools()}finally{await n.disconnectAll()}}function x(t){const e=process.env[t];if(void 0===e||0===e.trim().length)return;const n=Number.parseInt(e,10);return!Number.isInteger(n)||n<=0||String(n)!==e.trim()?void 0:n}export async function waitForAgentReady(t,e={}){const n=x("ROLL_AGENT_READY_STARTUP_TIMEOUT_MS")??e.startupTimeoutMs??h,r=x("ROLL_AGENT_READY_PROBE_TIMEOUT_MS")??e.probeTimeoutMs??w,i=x("ROLL_AGENT_READY_INTERVAL_MS")??e.intervalMs??A,o=Date.now()+n;let s;for(;Date.now()<o;)try{return void await probeAgentEndpoint(t,{timeoutMs:r})}catch(t){s=t,await P(i)}throw new Error(`Agent "${t.skill.name}" did not become ready within ${n}ms${s?`: ${s instanceof Error?s.message:String(s)}`:""}`)}export function startAgent(o,s,c){if("streamable-http"===o.transport.type&&"core-managed"!==o.runtime.ownership)throw new Error(`Agent "${o.skill.name}" 使用 streamable-http 传输且非 core-managed,请手动启动服务。\n 端点: ${o.transport.endpoint}`);const m=getAgentPid(s,o.skill.name);if(void 0!==m)throw new Error(`Agent "${o.skill.name}" 已在运行 (PID: ${String(m)})`);const d=E(o),p=getAgentLogPath(s,o.skill.name),u=l(p);n(u)||r(u,{recursive:!0});const g=i(p,"a"),f=t(d.command,[...d.args??[]],{cwd:o.installPath,detached:!0,windowsHide:!0,stdio:["ignore",g,g],...c?{env:{...process.env,...c}}:{}});if(e(g),!f.pid)throw new Error(`Failed to start agent "${o.skill.name}"`);const h=$(s,o.skill.name),w=l(h);n(w)||r(w,{recursive:!0});try{a(h,String(f.pid),"utf-8"),writeAgentRuntimeSidecar(o,s,f.pid)}catch(t){try{process.kill(f.pid,"SIGTERM")}catch{}throw v(s,o.skill.name),new Error(`Failed to persist runtime metadata for agent "${o.skill.name}"`,{cause:t})}return f.unref(),f.pid}export function stopAgent(t,e){const n=getAgentPid(t,e);if(void 0===n)return!1;try{process.kill(n,"SIGTERM")}catch{}return v(t,e),!0}export async function stopAgentGracefully(t,e,n={}){const r=getAgentPid(t,e);if(void 0===r)return!1;try{process.kill(r,"SIGTERM")}catch{return v(t,e),!0}const i=n.timeoutMs??15e3,o=n.intervalMs??200,s=Date.now()+i;for(;Date.now()<s;){if(!y(r))return v(t,e),!0;await P(o)}throw new Error(`Agent "${e}" did not stop within ${i}ms`)}function E(t){if("stdio"===t.transport.type){const e=p(t.transport.command,t.transport.args,t.installPath,u(t));return e||{command:t.transport.command,...t.transport.args?{args:t.transport.args}:{}}}if("core-managed"===t.runtime.ownership){const e=p(t.runtime.start.command,t.runtime.start.args,t.installPath,u(t));return e||{command:t.runtime.start.command,...t.runtime.start.args?{args:t.runtime.start.args}:{}}}throw new Error(`Agent "${t.skill.name}" does not have a managed runtime start command`)}function P(t){return new Promise(e=>{setTimeout(e,t)})}function R(t,e){const r=k(t,e);if(!n(r))return;let i;try{i=d(r)}catch{return"invalid"}return M(i)?i:"invalid"}function M(t){return!!S(t)&&(1===t.schemaVersion&&(!("string"!=typeof t.agentName||"number"!=typeof t.pid||!Number.isInteger(t.pid)||"string"!=typeof t.coreVersion||"string"!=typeof t.startedAt)&&(void 0===t.endpoint||"string"==typeof t.endpoint)))}function S(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
|
package/dist/registry/source.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as t,readFileSync as e,readdirSync as n}from"node:fs";import{
|
|
1
|
+
import{existsSync as t,readFileSync as e,readdirSync as n}from"node:fs";import{readJsonFile as r}from"./json-file.js";import{resolve as o}from"node:path";const i="SKILL.md";export function inferAgentSourceType(t){return t.source?t.source.type:inferAgentSourceFromInstallPath(t.installPath,t.transport)?.type??"local-path"}export function formatAgentSourceType(t){switch(t){case"git":return"git";case"installed-package":return"installed";case"remote-manifest":return"remote";case"local-path":return"local-path"}}export function getAgentLocation(t){return"streamable-http"===t.transport.type?t.transport.endpoint:t.installPath}export function inferAgentSourceFromInstallPath(e,n){if(t(o(e,".git"))){const t=s(e);return t?{type:"git",url:t}:{type:"git"}}return t(o(e,i))?{type:"local-path",path:e}:"streamable-http"===n.type?{type:"remote-manifest",endpoint:n.endpoint}:void 0}function s(n){const r=o(n,".git","config");if(t(r))try{const t=e(r,"utf-8").match(/\[remote "origin"\]([\s\S]*?)(?:\n\[|$)/),n=t?.[1]?.match(/^\s*url\s*=\s*(.+)$/m);return n?.[1]?.trim()}catch{return}}export function sanitizeInstallId(t){const e=t.trim().replace(/^@/,"").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase();return e.length>0?e:"agent"}export function parsePackageName(t){if(t.startsWith("@")){const e=t.indexOf("/");if(-1===e)return t;const n=t.indexOf("@",e+1);return-1===n?t:t.slice(0,n)}const e=t.indexOf("@");return-1===e?t:t.slice(0,e)}export function resolveInstalledPackageRoot(e,n){const r=o(e,"node_modules",...n.split("/"));if(t(r))return r;const i=a(e);if(i){const n=o(e,"node_modules",...i.split("/"));if(t(n))return n}return c(e)??r}export function readInstalledPackageManifest(e){const n=o(e,"package.json");if(t(n))try{const t=r(n),e="string"==typeof t.name&&t.name.length>0?t.name:void 0,o="string"==typeof t.version&&t.version.length>0?t.version:void 0;if(!e&&!o)return;return{...e?{name:e}:{},...o?{version:o}:{}}}catch{return}}function a(e){const n=o(e,"package.json");if(t(n))try{const t=r(n),e=Object.keys(t.dependencies??{});return 1===e.length?e[0]:void 0}catch{return}}function c(e){const n=o(e,"node_modules");if(!t(n))return;const r=p(n).filter(e=>t(o(e,"package.json"))&&t(o(e,i)));return 1===r.length?r[0]:void 0}function p(t){const e=[];for(const r of n(t,{withFileTypes:!0})){if(!r.isDirectory()||".bin"===r.name)continue;const i=o(t,r.name);if(r.name.startsWith("@"))for(const t of n(i,{withFileTypes:!0}))t.isDirectory()&&e.push(o(i,t.name));else e.push(i)}return e}
|
package/dist/registry/store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{writeFileSync as t,existsSync as e,mkdirSync as n,renameSync as r}from"node:fs";import{resolve as i,dirname as s}from"node:path";import{readJsonFile as a}from"./json-file.js";import{inferAgentSourceFromInstallPath as o}from"./source.js";import{AGENT_STATUSES as u,AGENT_STORE_SCHEMA_VERSION as c,createDefaultRuntimeForTransport as l}from"../types/agent.js";const p="agents.json";export class AgentStore{storePath;constructor(t){this.storePath=i(t,p)}list(){return this.load().agents}findByName(t){return this.list().find(e=>e.skill.name===t)}add(t){const e=[...this.list()];if(-1!==e.findIndex(e=>e.skill.name===t.skill.name))throw new Error(`Agent "${t.skill.name}" is already registered`);e.push(t),this.save(e)}remove(t){const e=this.list(),n=e.filter(e=>e.skill.name!==t);return n.length!==e.length&&(this.save([...n]),!0)}replace(t,e){const n=[...this.list()],r=n.findIndex(e=>e.skill.name===t);if(-1===r)return!1;if(-1!==n.findIndex((t,n)=>n!==r&&t.skill.name===e.skill.name))throw new Error(`Agent "${e.skill.name}" is already registered`);return n[r]=e,this.save(n),!0}updateStatus(t,e){const n=this.list().map(n=>n.skill.name===t?{...n,status:e}:n);this.save([...n])}save(i){const a=s(this.storePath);e(a)||n(a,{recursive:!0});const o={schemaVersion:c,agents:i},u=`${this.storePath}.tmp`;t(u,JSON.stringify(o,null,2),"utf-8"),r(u,this.storePath)}load(){if(!e(this.storePath))return f();let t;try{t=a(this.storePath)}catch{return f()}return d(t)}}function f(){return{schemaVersion:c,agents:[]}}function d(t){if(Array.isArray(t))return{schemaVersion:c,agents:t.flatMap(t=>{const e=m(t);return e?[e]:[]})};if(!D(t))return f();const e=t.agents;return Array.isArray(e)?{schemaVersion:t.schemaVersion===c?c:2,agents:e.flatMap(t=>{const e=m(t);return e?[e]:[]})}:f()}function m(t){if(!D(t))return;const e=h(t.skill),n=v(t.transport),r=N(t.installPath);if(!e||!n||!r)return;const i=w(t.runtime,n),s=P(t.source,r,n),a=N(t.registeredAt)??new Date(0).toISOString(),o=j(t.status),u=N(t.skillBody);return{skill:e,transport:n,runtime:i,installPath:r,registeredAt:a,status:o,...s?{source:s}:{},...u?{skillBody:u}:{}}}function h(t){if(!D(t))return;const e=N(t.name),n=N(t.description);if(!e||!n)return;const r=N(t.license),i=N(t.compatibility),s=y(t.env);return{name:e,description:n,...r?{license:r}:{},...i?{compatibility:i}:{},metadata:g(t.metadata),...s?{env:s}:{}}}function g(t){if(!D(t))return{};const e={};for(const[n,r]of Object.entries(t))e[n]=String(r);return e}function y(t){if(!D(t))return;const e=k(t.required),n=k(t.optional);return e||n?{...e?{required:e}:{},...n?{optional:n}:{}}:void 0}function k(t){if(!Array.isArray(t))return;const e=t.flatMap(t=>{if(!D(t))return[];const e=N(t.name);if(!e)return[];const n=N(t.purpose),r=N(t.example),i=N(t.default);return[{name:e,...n?{purpose:n}:{},...r?{example:r}:{},...i?{default:i}:{}}]});return e.length>0?e:void 0}function v(t){if(!D(t))return;const e=N(t.type);if("stdio"===e){const n=N(t.command);if(!n)return;const r=x(t.args);return r?{type:e,command:n,args:r}:{type:e,command:n}}if("streamable-http"===e){const n=N(t.endpoint);if(!n)return;return{type:e,endpoint:n}}}function w(t,e){if(!D(t))return l(e);const n=N(t.ownership);if("on-demand"===n)return{ownership:n};if("external-managed"===n)return{ownership:n};if("core-managed"===n&&"streamable-http"===e.type){const e=A(t.start),r=b(t.endpoint);if(e&&r){const i=S(t.setup);return i?{ownership:n,start:e,endpoint:r,setup:i}:{ownership:n,start:e,endpoint:r}}}return l(e)}function A(t){if(!D(t))return;const e=N(t.command);if(!e)return;const n=x(t.args);return n?{command:e,args:n}:{command:e}}function b(t){if(!D(t))return;const e=N(t.path),n="number"==typeof t.port?t.port:void 0;return e&&void 0!==n&&Number.isInteger(n)?{path:e,port:n}:void 0}function S(t){if(!D(t))return;const e=t.playwright;if(!D(e))return;const n=x(e.browsers);return n&&0!==n.length?{playwright:{browsers:n}}:void 0}function P(t,e,n){if(!D(t))return o(e,n);const r=N(t.type);if(!r)return o(e,n);switch(r){case"git":return I(N(t.url));case"local-path":return{type:r,path:N(t.path)??e};case"installed-package":{const e=N(t.packageName),n=N(t.packageSpec),i=N(t.installDir),s=N(t.installedVersion);if(!e||!n||!i)return;return{type:r,packageName:e,packageSpec:n,installDir:i,...s?{installedVersion:s}:{}}}case"remote-manifest":{const e=N(t.endpoint)??("streamable-http"===n.type?n.endpoint:void 0);return e?{type:r,endpoint:e}:void 0}default:return V(r,t,e,n)}}function V(t,e,n,r){switch(t){case"git":return I(N(e.url));case"local":return{type:"local-path",path:N(e.path)??n};case"installed":{const t=N(e.packageName),n=N(e.packageSpec),r=N(e.installDir),i=N(e.installedVersion);if(!t||!n||!r)return;return{type:"installed-package",packageName:t,packageSpec:n,installDir:r,...i?{installedVersion:i}:{}}}case"remote":{const t=o(n,r);if(t&&"remote-manifest"!==t.type)return t;const i=N(e.endpoint)??("streamable-http"===r.type?r.endpoint:void 0);return i?{type:"remote-manifest",endpoint:i}:void 0}}}function j(t){return"string"==typeof t&&u.includes(t)?t:"idle"}function x(t){if(!Array.isArray(t))return;return t.filter(t=>"string"==typeof t)}function N(t){return"string"==typeof t&&t.length>0?t:void 0}function D(t){return"object"==typeof t&&null!==t}function I(t){return t?{type:"git",url:t}:{type:"git"}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
|
|
2
2
|
import type { RegisteredAgent } from "../types/agent.ts";
|
|
3
3
|
import type { RouteSelection } from "../types/router.ts";
|
|
4
4
|
/**
|
|
@@ -8,4 +8,4 @@ import type { RouteSelection } from "../types/router.ts";
|
|
|
8
8
|
* 1. 优先尝试 structured output
|
|
9
9
|
* 2. 仅当模型未遵循 schema 时,降级为纯文本 JSON fallback
|
|
10
10
|
*/
|
|
11
|
-
export declare function routeWithLLM(message: string, agents: ReadonlyArray<RegisteredAgent>, model:
|
|
11
|
+
export declare function routeWithLLM(message: string, agents: ReadonlyArray<RegisteredAgent>, model: LanguageModelV4, structuredOutputProviderOptions?: SharedV4ProviderOptions): Promise<RouteSelection>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
|
|
2
2
|
import type { AgentTool } from "../types/agent.ts";
|
|
3
|
-
export declare function extractToolInput(message: string, tool: Pick<AgentTool, "name" | "description" | "inputSchema">, model:
|
|
3
|
+
export declare function extractToolInput(message: string, tool: Pick<AgentTool, "name" | "description" | "inputSchema">, model: LanguageModelV4, structuredOutputProviderOptions?: SharedV4ProviderOptions): Promise<Readonly<Record<string, unknown>>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -58,27 +58,30 @@
|
|
|
58
58
|
"node": ">=22.6.0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@ai-sdk/alibaba": "^
|
|
62
|
-
"@ai-sdk/anthropic": "^
|
|
63
|
-
"@ai-sdk/deepseek": "^
|
|
64
|
-
"@ai-sdk/openai": "^
|
|
65
|
-
"@ai-sdk/provider": "^
|
|
61
|
+
"@ai-sdk/alibaba": "^2.0.3",
|
|
62
|
+
"@ai-sdk/anthropic": "^4.0.4",
|
|
63
|
+
"@ai-sdk/deepseek": "^3.0.2",
|
|
64
|
+
"@ai-sdk/openai": "^4.0.4",
|
|
65
|
+
"@ai-sdk/provider": "^4.0.1",
|
|
66
66
|
"@clack/prompts": "1.4.0",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
68
|
-
"ai": "^
|
|
68
|
+
"ai": "^7.0.9",
|
|
69
69
|
"chalk": "^5.4.0",
|
|
70
70
|
"citty": "^0.1.6",
|
|
71
71
|
"cli-table3": "^0.6.5",
|
|
72
|
+
"cross-spawn": "7.0.6",
|
|
72
73
|
"gray-matter": "^4.0.3",
|
|
73
74
|
"ink": "7.0.1",
|
|
75
|
+
"is-unicode-supported": "2.1.0",
|
|
74
76
|
"marked": "18.0.5",
|
|
75
77
|
"ora": "^8.1.0",
|
|
76
78
|
"react": "19.2.7",
|
|
77
79
|
"yaml": "^2.7.0",
|
|
78
80
|
"zod": "^3.25.76",
|
|
79
|
-
"@roll-agent/runtime": "0.
|
|
81
|
+
"@roll-agent/runtime": "0.3.0"
|
|
80
82
|
},
|
|
81
83
|
"devDependencies": {
|
|
84
|
+
"@types/cross-spawn": "6.0.6",
|
|
82
85
|
"@types/node": "^22.0.0",
|
|
83
86
|
"@types/react": "19.2.16",
|
|
84
87
|
"ink-testing-library": "4.0.0"
|