@roll-agent/core 0.13.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.
@@ -1 +1 @@
1
- import{randomUUID as i}from"node:crypto";import{createElement as t,useState as n}from"react";import{Box as o,useInput as e}from"ink";import{useSession as m}from"./use-session.js";import{HistoryItemView as s}from"./history-item.js";import{LiveRegion as r}from"./live-region.js";import{StatusLine as a}from"./status-line.js";import{TextPrompt as l}from"./text-prompt.js";import{ConfirmSelect as f}from"./confirm-select.js";import{SlashPopup as h}from"./slash-popup.js";import{filterCommands as p,SLASH_COMMANDS as d}from"./commands.js";import{cycleThinking as c}from"./thinking.js";function u(){return d.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(g){const{session:k,model:v,contextWindow:x,onUserSubmit:j,onExit:C}=g,{state:y,submit:L,compact:T,resolveConfirm:M,setDraft:w,setThinking:S,commitHistory:b}=m(k,{model:v,contextWindow:x,...g.initialHistory?{initialHistory:g.initialHistory}:{},...g.initialThinkingLevel?{initialThinkingLevel:g.initialThinkingLevel}:{},...g.onThinkingChange?{onThinkingChange:g.onThinkingChange}:{}}),[D,H]=n(0),$="idle"===y.phase&&y.draft.startsWith("/"),W=$?p(y.draft):[],A=Math.max(W.length-1,0),E=Math.min(D,A);e((i,t)=>{t.meta&&"."===i?S(c(y.status.thinkingLevel,1)):t.meta&&","===i&&S(c(y.status.thinkingLevel,-1))});const R="confirm"===y.phase&&void 0!==y.pendingConfirm?t(f,{prompt:y.pendingConfirm.prompt,onDecide:M}):t(l,{value:y.draft,disabled:"idle"!==y.phase,slashActive:$,onChange:w,onSubmit:i=>{const t=i.trim();0!==t.length?(j(t),L(t)):w("")},onSlashMove:i=>{H(t=>Math.min(Math.max(Math.min(t,A)+i,0),A))},onSlashComplete:()=>{const i=W[E];i&&(w(`${i.name} `),H(0))},onSlashRun:()=>{const t=y.draft.trim().split(/\s+/,1)[0]??"";(t=>{w("");const n=t.trim().split(/\s+/),o=n[0]??"",e=(n[1]??"").toLowerCase(),m=y.status.thinkingLevel;"/compact"!==o?"/think"!==o?"/effort"!==o?"/help"!==o?"/exit"!==o?b({kind:"notice",id:i(),text:`未知命令 ${o}`}):C():b({kind:"notice",id:i(),text:u()}):"low"===e||"medium"===e||"high"===e?S(e):b({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}):S("off"===e?"off":"on"===e?"off"===m?"medium":m:"off"===m?"medium":"off"):T()})(d.some(i=>i.name===t)?y.draft:W[E]?.name??y.draft)}});return t(o,{flexDirection:"column"},t(o,{flexDirection:"column"},...y.history.map(i=>{const n="user"===i.kind||"assistant"===i.kind,e="tool"===i.kind||"denied"===i.kind;return t(o,{key:i.id,marginTop:n?1:0,marginLeft:e?2:0},t(s,{item:i}))})),t(r,{live:y.live}),t(a,{status:y.status}),$?t(h,{matches:W,selected:E}):null,R)}
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 e,useState as r}from"react";import{Box as o,Text as n,useInput as t}from"ink";export function ConfirmSelect({prompt:c,onDecide:l}){const[i,s]=r("no");return t((e,r)=>{r.leftArrow||r.rightArrow||r.upArrow||r.downArrow?s(e=>"yes"===e?"no":"yes"):r.return||e.includes("\r")||e.includes("\n")?l("yes"===i):r.escape||"n"===e?l(!1):"y"===e&&l(!0)}),e(o,{flexDirection:"column"},e(n,null,c),e(o,null,e(n,"yes"===i?{color:"green"}:{},("yes"===i?"❯ ":" ")+"Yes"),e(n,"no"===i?{color:"green"}:{},` ${"no"===i?"❯ ":" "}No`)))}
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 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+, 调推理)");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
+ 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 n}from"react";import{Text as e}from"ink";const o=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],c=80;export function Spinner(){const[a,l]=n(0);return t(()=>{const r=setInterval(()=>{l(r=>(r+1)%o.length)},c);return()=>{clearInterval(r)}},[]),r(e,{color:"cyan"},o[a]??o[0])}
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]??"-")}
@@ -53,11 +53,13 @@ export interface StatusState {
53
53
  readonly contextInputTokens: number | undefined;
54
54
  readonly outputTokensPerSecond: number | undefined;
55
55
  readonly thinkingLevel: ThinkingLevel;
56
+ readonly autoApprove: boolean;
56
57
  }
57
58
  export type ChatPhase = "idle" | "busy" | "confirm";
58
59
  export interface PendingConfirm {
59
60
  readonly approvalId: string;
60
61
  readonly prompt: string;
62
+ readonly args: string;
61
63
  }
62
64
  export interface ChatUiState {
63
65
  readonly history: readonly HistoryItem[];
@@ -77,6 +79,9 @@ export type ChatUiAction = {
77
79
  } | {
78
80
  readonly type: "set-thinking";
79
81
  readonly level: ThinkingLevel;
82
+ } | {
83
+ readonly type: "set-auto";
84
+ readonly value: boolean;
80
85
  } | {
81
86
  readonly type: "commit-history";
82
87
  readonly item: HistoryItem;
@@ -1 +1 @@
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"},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 l(r,s,l){switch(l.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+l.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:l.toolCallId,name:`${l.agentName}.${l.toolName}`,args:t(l.input)}]}}}case"tool-result":return a(r,s,l);case"confirmation-required":return{...r,phase:"confirm",pendingConfirm:{approvalId:l.approvalId,prompt:buildConfirmPrompt(l)}};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(l)}]};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&&(l.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:s,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),l.stoppedAtStepLimit&&t.push({kind:"notice",id:`${s}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...r,history:[...r.history,...t],live:{...i},status:{...r.status,turnUsage:l.totalUsage,sessionUsage:l.sessionUsage,contextInputTokens:l.contextInputTokens,outputTokensPerSecond:l.outputTokensPerSecond}}}case"error":return{...r,history:[...r.history,{kind:"error",id:s,message:l.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"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 l(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
+ 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 +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 i,formatContextUsage as p,formatThroughput as r,formatTokens as c,formatTurnUsage as l}from"../../utils/token-format.js";import{displayWidth as m}from"./markdown.js";import{thinkingLabel as u}from"./thinking.js";const k={ok:{dimColor:!0},warn:{color:"yellow"},critical:{color:"red"}},a={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:u(o.thinkingLevel),compact:`🧠 ${a[o.thinkingLevel]}`,props:"off"===o.thinkingLevel?{color:"yellow"}:{dimColor:!0}}],h=p(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:k[i(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 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 v=(o,t)=>o.map(o=>({key:o.key,text:o[t],props:o.props})),w=o=>m(o.map(o=>o.text).join(f))<=t,x=v(s,"full");if(w(x))return x;let T=s,$=v(T,"compact");for(const o of d){if(w($))break;T=T.filter(t=>t.key!==o),$=v(T,"compact")}return $}export function StatusLine({status:n}){const{stdout:i}=s(),p=i.columns??80,r=composeStatusSegments(n,p);return o(t,{width:p},o(e,{wrap:"truncate-end"},...r.flatMap((t,s)=>[...s>0?[o(e,{key:`${t.key}-sep`,dimColor:!0},f)]:[],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 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 u(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function TextPrompt(l){const{value:c,disabled:s,slashActive:d,onChange:a,onSubmit:f}=l,{stdout:m}=i(),p=m.columns??80,v=t(c);v.current=c;const h=r=>{v.current=r,a(r)};o((r,t)=>{if(t.meta&&("."===r||","===r))return;if(u(r))return;if(t.backspace||t.delete)return void h(v.current.slice(0,-1));if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void h(`${v.current}\n`);if(t.return||r.includes("\r")){if(d)return void l.onSlashRun();const t=r.split("\r",1)[0]??"";return void f(v.current+t)}if(d){if(t.upArrow)return void l.onSlashMove(-1);if(t.downArrow)return void l.onSlashMove(1);if(t.tab)return void l.onSlashComplete();if(t.escape)return void h("")}t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&h(v.current+r)},{isActive:!s});const b=c.split("\n"),w=r(e,{flexDirection:"column"},...b.map((t,o)=>{const i=o===b.length-1,u=0===o?r(n,{color:"green"},"› "):r(n,null," ");return r(e,{key:String(o)},u,r(n,s?{dimColor:!0}:{},t),i&&!s?r(n,null,"▏"):null)})),g=d?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令";return r(e,{flexDirection:"column",width:p},r(e,{borderStyle:"round",borderColor:s?"gray":"cyan",paddingX:1},w),r(n,{dimColor:!0},g))}
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)}
@@ -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(l,p){const[u,m]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),y=p.onThinkingChange,d=i(null),v=i(!1),f=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){const t=new Promise(e=>{d.current=e});m({type:"session-event",id:e(),event:o});const n=await t;d.current=null,n?l.approve(o.approvalId):l.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"}),v.current=!1}},[l]);return{state:u,submit:t(t=>{v.current||(v.current=!0,m({type:"submit-user",id:e(),text:t}),f(l.send(t)).catch(()=>{}))},[f,l]),compact:t(()=>{v.current||(v.current=!0,m({type:"start-compaction"}),f(l.compact("manual")).catch(()=>{}))},[f,l]),resolveConfirm:t(e=>{d.current?.(e)},[]),setDraft:t(e=>{m({type:"set-draft",value:e})},[]),setThinking:t(e=>{m({type:"set-thinking",level:e}),y?.(e)},[y]),commitHistory:t(e=>{m({type:"commit-history",item:e})},[])}}
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}" 当前未运行`)}});
@@ -1 +1 @@
1
- import{readFileSync as n,existsSync as r}from"node:fs";import{resolve as t}from"node:path";import{parse as o}from"yaml";import{agentsConfigSchema as e,installConfigSchema as i,rollConfigSchema as s}from"./schema.js";import{DEFAULT_CONFIG as a,CONFIG_FILE_NAMES as f}from"./defaults.js";import{decodeFromYaml as c}from"./key-codec.js";import{detectKnownConfigMigrations as u,formatConfigMigrationError as l}from"./migration.js";function g(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(g);if(d(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=g(o);return r}return n}function d(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function p(n){return d(n)&&"number"==typeof n.line&&"number"==typeof n.col}function h(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(p)}function m(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(h(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function w(n){let o=t(n);const e=t("/");for(;o!==e;){for(const n of f){const e=t(o,n);if(r(e))return e}const n=t(o,"..");if(n===o)break;o=n}}function v(n){if(n.startsWith("~/")){const r=process.env.HOME??process.env.USERPROFILE??"";return t(r,n.slice(2))}return n}function $(n){return{...n,agents:{...n.agents,dataDir:v(n.agents.dataDir)},runtime:{...n.runtime,threadsDir:v(n.runtime.threadsDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:v(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:v(r.sessionsDir)}:{}}]))}}}const C={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=o(n)}catch(n){throw new Error(m(r,n),{cause:n instanceof Error?n:void 0})}if(!d(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function P(n,r,t={}){const o=parseConfigDocument(n,r),e=u(o,t);if(e.needsMigration)throw new Error(l(r,e));return o}export function validateConfigText(n,r){const t=P(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=j(a,o),i=s.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return $(i.data)}function E(n,r){const t=P(n,r,{scope:"agents"}),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const i=d(o.agents)?o.agents:{},s=j(a.agents,i),f=e.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...f.data,dataDir:v(f.data.dataDir)}}function y(n,r){const t=parseConfigDocument(n,r),o=g(c(t));if(!d(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=d(o.install)?o.install:{},s=j(a.install,e),f=i.safeParse(s);if(!f.success){const n=f.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return f.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:a.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:y(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:$(a),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...a.agents,dataDir:v(a.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:E(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??w(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:C.notFound,configPath:void 0};if(!r(o))return{status:C.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=u(i);if(s.needsMigration)return{status:C.needsMigration,configPath:o,raw:e,report:s};try{return{status:C.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:C.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function j(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=j(r,e)}return t}
1
+ import{readFileSync as n,existsSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as o}from"node:path";import{parse as e}from"yaml";import{agentsConfigSchema as i,installConfigSchema as s,rollConfigSchema as a}from"./schema.js";import{DEFAULT_CONFIG as f,CONFIG_FILE_NAMES as c}from"./defaults.js";import{decodeFromYaml as u}from"./key-codec.js";import{detectKnownConfigMigrations as l,formatConfigMigrationError as g}from"./migration.js";function d(n){if("string"==typeof n)return n.replace(/\$\{([^}]+)\}/g,(n,r)=>process.env[r]??n);if(Array.isArray(n))return n.map(d);if(p(n)){const r={};for(const[t,o]of Object.entries(n))r[t]=d(o);return r}return n}function p(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}function h(n){return p(n)&&"number"==typeof n.line&&"number"==typeof n.col}function m(n){return n instanceof Error&&"linePos"in n&&Array.isArray(n.linePos)&&n.linePos.length>0&&n.linePos.every(h)}function w(n,r){const t=`Invalid YAML syntax in config file: ${n}`;if(!(r instanceof Error))return`${t}\n${String(r)}`;if(m(r)){const[n]=r.linePos;if(n)return`${t} at line ${n.line}, column ${n.col}\n${r.message}`}return`${t}\n${r.message}`}function v(n){let t=o(n);const e=o("/");for(;t!==e;){for(const n of c){const e=o(t,n);if(r(e))return e}const n=o(t,"..");if(n===t)break;t=n}}function $(n){return"~"===n?t():n.startsWith("~/")||n.startsWith("~\\")?o(t(),n.slice(2)):n}function C(n){return{...n,agents:{...n.agents,dataDir:$(n.agents.dataDir)},runtime:{...n.runtime,threadsDir:$(n.runtime.threadsDir)},browser:{...n.browser,instances:Object.fromEntries(Object.entries(n.browser.instances).map(([n,r])=>[n,{...r,userDataDir:$(r.userDataDir),...void 0!==r.sessionsDir?{sessionsDir:$(r.sessionsDir)}:{}}]))}}}const P={notFound:"not-found",valid:"valid",needsMigration:"needs-migration",invalid:"invalid"};export function parseConfigDocument(n,r){let t;try{t=e(n)}catch(n){throw new Error(w(r,n),{cause:n instanceof Error?n:void 0})}if(!p(t))throw new Error(`Invalid config file: ${r} (expected YAML object)`);return t}function y(n,r,t={}){const o=parseConfigDocument(n,r),e=l(o,t);if(e.needsMigration)throw new Error(g(r,e));return o}export function validateConfigText(n,r){const t=y(n,r),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=D(f,o),i=a.safeParse(e);if(!i.success){const n=i.error.issues.map(n=>` - ${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return C(i.data)}function j(n,r){const t=y(n,r,{scope:"agents"}),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.agents)?o.agents:{},s=D(f.agents,e),a=i.safeParse(s);if(!a.success){const n=a.error.issues.map(n=>` - agents.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return{...a.data,dataDir:$(a.data.dataDir)}}function E(n,r){const t=parseConfigDocument(n,r),o=d(u(t));if(!p(o))throw new Error(`Invalid config file: ${r} (expected YAML object)`);const e=p(o.install)?o.install:{},i=D(f.install,e),a=s.safeParse(i);if(!a.success){const n=a.error.issues.map(n=>` - install.${n.path.join(".")}: ${n.message}`).join("\n");throw new Error(`Config validation failed (${r}):\n${n}`)}return a.data}export function loadInstallConfig(t={}){const o=resolveConfigPath(t);if(!o)return{installConfig:f.install,configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{installConfig:E(n(o,"utf-8"),o),configPath:o}}export function loadConfig(t={}){const o=resolveConfigPath(t);if(!o)return{config:C(f),configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{config:validateConfigText(n(o,"utf-8"),o),configPath:o}}export function loadAgentsConfig(t={}){const o=resolveConfigPath(t);if(!o)return{agentsConfig:{...f.agents,dataDir:$(f.agents.dataDir)},configPath:void 0};if(!r(o))throw new Error(`Config file not found: ${o}`);return{agentsConfig:j(n(o,"utf-8"),o),configPath:o}}export function resolveConfigPath(n={}){const{configPath:r,cwd:t=process.cwd()}=n;return r??v(t)}export function inspectConfigFile(t={}){const o=resolveConfigPath(t);if(!o)return{status:P.notFound,configPath:void 0};if(!r(o))return{status:P.invalid,configPath:o,raw:"",error:new Error(`Config file not found: ${o}`)};const e=n(o,"utf-8");let i;try{i=parseConfigDocument(e,o)}catch(n){return{status:P.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}const s=l(i);if(s.needsMigration)return{status:P.needsMigration,configPath:o,raw:e,report:s};try{return{status:P.valid,configPath:o,config:validateConfigText(e,o)}}catch(n){return{status:P.invalid,configPath:o,raw:e,error:n instanceof Error?n:new Error(String(n))}}}function D(n,r){const t={...n};for(const[o,e]of Object.entries(r)){const r=n[o];"object"!=typeof e||null===e||Array.isArray(e)||"object"!=typeof r||null===r||Array.isArray(r)?t[o]=e:t[o]=D(r,e)}return t}
@@ -1 +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 g;const w=new Promise((n,e)=>{g=setTimeout(()=>e(new Error(`Connection to "${t}" timed out after ${d}ms`)),d)});try{await Promise.race([h,w])}catch(n){throw await m.close().catch(()=>{}),n}finally{clearTimeout(g)}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=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 +1 @@
1
- import{readFileSync as t,existsSync as r,realpathSync as n}from"node:fs";import{resolve as e,sep as o}from"node:path";import i from"gray-matter";import{z as a}from"zod";import{parse as s}from"yaml";import{createDefaultRuntimeForTransport as p}from"../types/agent.js";const d="SKILL.md",l="package.json",m=a.object({name:a.string().min(1),purpose:a.string().optional(),example:a.string().optional(),default:a.string().optional()}),c=a.object({required:a.array(m).optional(),optional:a.array(m).optional()});export function discoverAgent(n){const o=e(n),a=e(o,d);if(!r(a))throw new Error(`SKILL.md not found in ${o}`);const s=t(a,"utf-8"),{data:l,content:m}=i(s),c=y(o),u=l,g=u.name,h=u.description;if("string"!=typeof g||0===g.length)throw new Error(`SKILL.md missing required field "name" in ${a}`);if("string"!=typeof h||0===h.length)throw new Error(`SKILL.md missing required field "description" in ${a}`);const v=u.metadata??{},$={};for(const[t,r]of Object.entries(v))$[t]=String(r);const j="string"==typeof u.license?u.license:void 0,S="string"==typeof u.compatibility?u.compatibility:void 0,I=f(o,$,a),k={name:g,description:h,...j?{license:j}:{},...S?{compatibility:S}:{},metadata:$,...I?{env:I}:{}},q=c?b(c):void 0;if(q&&L($)){if(!E(w($),q.transport))throw new Error(`Conflicting runtime metadata in ${a}: package.json#rollAgent and SKILL.md metadata disagree`)}const A=q?.transport??w($);return{skill:k,transport:A,runtime:q?.runtime??p(A),skillPath:a,skillBody:m.trim()}}function u(t,r){if(void 0===t)return;const n=c.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(h)}:{},...e.optional?{optional:e.optional.map(h)}:{}}:void 0}function f(o,i,a){const p=i["roll-env-file"];if(!p)return;const d=e(o,p);if(!g(o,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(o),m=n(d);if(!g(l,m))throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${a}`);let c;try{c=s(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 u(c,m)}function g(t,r){return r===t||r.startsWith(`${t}${o}`)}function h(t){return{name:t.name,...t.purpose?{purpose:t.purpose}:{},...t.example?{example:t.example}:{},...t.default?{default:t.default}:{}}}function w(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 y(n){const o=e(n,l);if(!r(o))return;let i;try{i=JSON.parse(t(o,"utf-8"))}catch{throw new Error(`Invalid package.json in ${n}`)}if(!I(i)||!I(i.rollAgent))return;const a=i.rollAgent,s=I(a.runtime)?a.runtime:void 0;if(!s)throw new Error(`package.json#rollAgent.runtime is required in ${o}`);const p={runtime:{ownership:j(s.ownership)??"",transport:j(s.transport)??""}};if(I(a.start)){const t=j(a.start.command),r=S(a.start.args);(t||r)&&(p.start={...t?{command:t}:{},...r?{args:r}:{}})}if(I(a.endpoint)){const t=j(a.endpoint.url),r=j(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(I(a.setup)&&I(a.setup.playwright)){const t=S(a.setup.playwright.browsers);t&&t.length>0&&(p.setup={playwright:{browsers:t}})}return p}function b(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=v(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:v(t)},runtime:{ownership:"external-managed"}};throw new Error(`Unsupported package.json#rollAgent runtime combination: ${r}/${n}`)}function v(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 $(n,e)}function $(t,r){const n=t.startsWith("/")?t:`/${t}`;return`http://127.0.0.1:${String(r)}${n}`}function L(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 j(t){return"string"==typeof t&&t.length>0?t:void 0}function S(t){if(!Array.isArray(t))return;const r=t.filter(t=>"string"==typeof t);return r.length>0?r:void 0}function I(t){return"object"==typeof t&&null!==t}
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,2 @@
1
+ /** 读取并解析 JSON 文件,容忍 Windows 工具写入的 UTF-8 BOM */
2
+ export declare function readJsonFile(filePath: string): unknown;
@@ -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{spawn as t}from"node:child_process";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{resolveDevSpawnSpec as p}from"./dev-spawn.js";import{inferAgentSourceType as d}from"./source.js";const u=1,g="unknown",f=15e3,h=2e3,w=500;function A(t,e){return c(t,"pids",`${e}.pid`)}function $(t,e){return c(t,"pids",`${e}.runtime.json`)}function k(t,e){for(const r of[A(t,e),$(t,e)])n(r)&&s(r)}export function cleanupOrphanAgentRuntimeMetadata(t,e){return void 0===getAgentPid(t,e)&&(k(t,e),!0)}export function getAgentLogPath(t,e){return c(t,"logs",`${e}.log`)}function v(t){try{return process.kill(t,0),!0}catch{return!1}}export function getAgentPid(t,e){const r=A(t,e);if(!n(r))return;const i=Number(o(r,"utf-8").trim());if(!Number.isNaN(i)&&v(i))return i;k(t,e)}export function getRollCoreVersion(){try{const t=c(import.meta.dirname,"../../package.json"),e=JSON.parse(o(t,"utf-8"));return R(e)&&"string"==typeof e.version?e.version:g}catch{return g}}export function writeAgentRuntimeSidecar(t,e,i){const o=$(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=P(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=P(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 y(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=y("ROLL_AGENT_READY_STARTUP_TIMEOUT_MS")??e.startupTimeoutMs??f,r=y("ROLL_AGENT_READY_PROBE_TIMEOUT_MS")??e.probeTimeoutMs??h,i=y("ROLL_AGENT_READY_INTERVAL_MS")??e.intervalMs??w,o=Date.now()+n;let s;for(;Date.now()<o;)try{return void await probeAgentEndpoint(t,{timeoutMs:r})}catch(t){s=t,await E(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 p=x(o),d=getAgentLogPath(s,o.skill.name),u=l(d);n(u)||r(u,{recursive:!0});const g=i(d,"a"),f=t(p.command,[...p.args??[]],{cwd:o.installPath,detached:!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=A(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 k(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 k(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 k(t,e),!0}const i=n.timeoutMs??15e3,o=n.intervalMs??200,s=Date.now()+i;for(;Date.now()<s;){if(!v(r))return k(t,e),!0;await E(o)}throw new Error(`Agent "${e}" did not stop within ${i}ms`)}function x(t){if("stdio"===t.transport.type){const e=p(t.transport.command,t.transport.args,t.installPath,d(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,d(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 E(t){return new Promise(e=>{setTimeout(e,t)})}function P(t,e){const r=$(t,e);if(!n(r))return;let i;try{i=JSON.parse(o(r,"utf-8"))}catch{return"invalid"}return S(i)?i:"invalid"}function S(t){return!!R(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 R(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
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)}
@@ -1 +1 @@
1
- import{existsSync as t,readFileSync as e,readdirSync as n}from"node:fs";import{resolve as r}from"node:path";const o="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(r(e,".git"))){const t=i(e);return t?{type:"git",url:t}:{type:"git"}}return t(r(e,o))?{type:"local-path",path:e}:"streamable-http"===n.type?{type:"remote-manifest",endpoint:n.endpoint}:void 0}function i(n){const o=r(n,".git","config");if(t(o))try{const t=e(o,"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 o=r(e,"node_modules",...n.split("/"));if(t(o))return o;const i=s(e);if(i){const n=r(e,"node_modules",...i.split("/"));if(t(n))return n}return a(e)??o}export function readInstalledPackageManifest(n){const o=r(n,"package.json");if(t(o))try{const t=JSON.parse(e(o,"utf-8")),n="string"==typeof t.name&&t.name.length>0?t.name:void 0,r="string"==typeof t.version&&t.version.length>0?t.version:void 0;if(!n&&!r)return;return{...n?{name:n}:{},...r?{version:r}:{}}}catch{return}}function s(n){const o=r(n,"package.json");if(t(o))try{const t=JSON.parse(e(o,"utf-8")),n=Object.keys(t.dependencies??{});return 1===n.length?n[0]:void 0}catch{return}}function a(e){const n=r(e,"node_modules");if(!t(n))return;const i=c(n).filter(e=>t(r(e,"package.json"))&&t(r(e,o)));return 1===i.length?i[0]:void 0}function c(t){const e=[];for(const o of n(t,{withFileTypes:!0})){if(!o.isDirectory()||".bin"===o.name)continue;const i=r(t,o.name);if(o.name.startsWith("@"))for(const t of n(i,{withFileTypes:!0}))t.isDirectory()&&e.push(r(i,t.name));else e.push(i)}return e}
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}
@@ -1 +1 @@
1
- import{readFileSync as t,writeFileSync as e,existsSync as n,mkdirSync as r}from"node:fs";import{resolve as i,dirname as s}from"node:path";import{inferAgentSourceFromInstallPath as a}from"./source.js";import{AGENT_STATUSES as o,AGENT_STORE_SCHEMA_VERSION as u,createDefaultRuntimeForTransport as c}from"../types/agent.js";const l="agents.json";export class AgentStore{storePath;constructor(t){this.storePath=i(t,l)}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(t){const i=s(this.storePath);n(i)||r(i,{recursive:!0});const a={schemaVersion:u,agents:t};e(this.storePath,JSON.stringify(a,null,2),"utf-8")}load(){if(!n(this.storePath))return p();let e;try{const n=t(this.storePath,"utf-8");e=JSON.parse(n)}catch{return p()}return f(e)}}function p(){return{schemaVersion:u,agents:[]}}function f(t){if(Array.isArray(t))return{schemaVersion:u,agents:t.flatMap(t=>{const e=d(t);return e?[e]:[]})};if(!j(t))return p();const e=t.agents;return Array.isArray(e)?{schemaVersion:t.schemaVersion===u?u:2,agents:e.flatMap(t=>{const e=d(t);return e?[e]:[]})}:p()}function d(t){if(!j(t))return;const e=m(t.skill),n=k(t.transport),r=x(t.installPath);if(!e||!n||!r)return;const i=v(t.runtime,n),s=S(t.source,r,n),a=x(t.registeredAt)??new Date(0).toISOString(),o=N(t.status),u=x(t.skillBody);return{skill:e,transport:n,runtime:i,installPath:r,registeredAt:a,status:o,...s?{source:s}:{},...u?{skillBody:u}:{}}}function m(t){if(!j(t))return;const e=x(t.name),n=x(t.description);if(!e||!n)return;const r=x(t.license),i=x(t.compatibility),s=g(t.env);return{name:e,description:n,...r?{license:r}:{},...i?{compatibility:i}:{},metadata:h(t.metadata),...s?{env:s}:{}}}function h(t){if(!j(t))return{};const e={};for(const[n,r]of Object.entries(t))e[n]=String(r);return e}function g(t){if(!j(t))return;const e=y(t.required),n=y(t.optional);return e||n?{...e?{required:e}:{},...n?{optional:n}:{}}:void 0}function y(t){if(!Array.isArray(t))return;const e=t.flatMap(t=>{if(!j(t))return[];const e=x(t.name);if(!e)return[];const n=x(t.purpose),r=x(t.example),i=x(t.default);return[{name:e,...n?{purpose:n}:{},...r?{example:r}:{},...i?{default:i}:{}}]});return e.length>0?e:void 0}function k(t){if(!j(t))return;const e=x(t.type);if("stdio"===e){const n=x(t.command);if(!n)return;const r=P(t.args);return r?{type:e,command:n,args:r}:{type:e,command:n}}if("streamable-http"===e){const n=x(t.endpoint);if(!n)return;return{type:e,endpoint:n}}}function v(t,e){if(!j(t))return c(e);const n=x(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=w(t.start),r=A(t.endpoint);if(e&&r){const i=b(t.setup);return i?{ownership:n,start:e,endpoint:r,setup:i}:{ownership:n,start:e,endpoint:r}}}return c(e)}function w(t){if(!j(t))return;const e=x(t.command);if(!e)return;const n=P(t.args);return n?{command:e,args:n}:{command:e}}function A(t){if(!j(t))return;const e=x(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 b(t){if(!j(t))return;const e=t.playwright;if(!j(e))return;const n=P(e.browsers);return n&&0!==n.length?{playwright:{browsers:n}}:void 0}function S(t,e,n){if(!j(t))return a(e,n);const r=x(t.type);if(!r)return a(e,n);switch(r){case"git":return D(x(t.url));case"local-path":return{type:r,path:x(t.path)??e};case"installed-package":{const e=x(t.packageName),n=x(t.packageSpec),i=x(t.installDir),s=x(t.installedVersion);if(!e||!n||!i)return;return{type:r,packageName:e,packageSpec:n,installDir:i,...s?{installedVersion:s}:{}}}case"remote-manifest":{const e=x(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 D(x(e.url));case"local":return{type:"local-path",path:x(e.path)??n};case"installed":{const t=x(e.packageName),n=x(e.packageSpec),r=x(e.installDir),i=x(e.installedVersion);if(!t||!n||!r)return;return{type:"installed-package",packageName:t,packageSpec:n,installDir:r,...i?{installedVersion:i}:{}}}case"remote":{const t=a(n,r);if(t&&"remote-manifest"!==t.type)return t;const i=x(e.endpoint)??("streamable-http"===r.type?r.endpoint:void 0);return i?{type:"remote-manifest",endpoint:i}:void 0}}}function N(t){return"string"==typeof t&&o.includes(t)?t:"idle"}function P(t){if(!Array.isArray(t))return;return t.filter(t=>"string"==typeof t)}function x(t){return"string"==typeof t&&t.length>0?t:void 0}function j(t){return"object"==typeof t&&null!==t}function D(t){return t?{type:"git",url:t}:{type:"git"}}
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"}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/core",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -69,8 +69,10 @@
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",
@@ -79,6 +81,7 @@
79
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"