@roll-agent/core 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli/chat/ink/app.d.ts +4 -4
  2. package/dist/cli/chat/ink/app.js +1 -1
  3. package/dist/cli/chat/ink/banner-view.d.ts +3 -4
  4. package/dist/cli/chat/ink/commands.d.ts +4 -12
  5. package/dist/cli/chat/ink/commands.js +1 -1
  6. package/dist/cli/chat/ink/confirm-select.d.ts +3 -1
  7. package/dist/cli/chat/ink/confirm-select.js +1 -1
  8. package/dist/cli/chat/ink/history-from-messages.js +1 -1
  9. package/dist/cli/chat/ink/history-item.js +1 -1
  10. package/dist/cli/chat/ink/input-history.d.ts +2 -0
  11. package/dist/cli/chat/ink/input-history.js +1 -0
  12. package/dist/cli/chat/ink/layout.d.ts +13 -0
  13. package/dist/cli/chat/ink/layout.js +1 -0
  14. package/dist/cli/chat/ink/line-buffer.d.ts +8 -0
  15. package/dist/cli/chat/ink/line-buffer.js +1 -1
  16. package/dist/cli/chat/ink/live-region.d.ts +2 -0
  17. package/dist/cli/chat/ink/live-region.js +1 -1
  18. package/dist/cli/chat/ink/mouse-input.d.ts +9 -0
  19. package/dist/cli/chat/ink/mouse-input.js +1 -0
  20. package/dist/cli/chat/ink/reasoning-block.d.ts +4 -0
  21. package/dist/cli/chat/ink/reasoning-block.js +1 -0
  22. package/dist/cli/chat/ink/run-ink-repl.d.ts +2 -2
  23. package/dist/cli/chat/ink/run-ink-repl.js +1 -1
  24. package/dist/cli/chat/ink/slash-popup.d.ts +3 -1
  25. package/dist/cli/chat/ink/slash-popup.js +1 -1
  26. package/dist/cli/chat/ink/spinner.d.ts +3 -1
  27. package/dist/cli/chat/ink/spinner.js +1 -1
  28. package/dist/cli/chat/ink/state.d.ts +26 -3
  29. package/dist/cli/chat/ink/state.js +1 -1
  30. package/dist/cli/chat/ink/status-line.d.ts +3 -2
  31. package/dist/cli/chat/ink/status-line.js +1 -1
  32. package/dist/cli/chat/ink/terminal-output.d.ts +6 -0
  33. package/dist/cli/chat/ink/terminal-output.js +1 -0
  34. package/dist/cli/chat/ink/text-prompt.d.ts +7 -1
  35. package/dist/cli/chat/ink/text-prompt.js +1 -1
  36. package/dist/cli/chat/ink/transcript-viewport.d.ts +22 -0
  37. package/dist/cli/chat/ink/transcript-viewport.js +1 -0
  38. package/dist/cli/chat/ink/turn-activity.d.ts +19 -0
  39. package/dist/cli/chat/ink/turn-activity.js +1 -0
  40. package/dist/cli/chat/ink/turn-status-line.d.ts +15 -0
  41. package/dist/cli/chat/ink/turn-status-line.js +1 -0
  42. package/dist/cli/chat/ink/use-session.js +1 -1
  43. package/dist/cli/chat/screen-mode.d.ts +66 -0
  44. package/dist/cli/chat/screen-mode.js +1 -0
  45. package/dist/cli/commands/browser-stop.d.ts +4 -4
  46. package/dist/cli/commands/chat.d.ts +33 -1
  47. package/dist/cli/commands/chat.js +1 -1
  48. package/dist/cli/commands/config.js +1 -1
  49. package/dist/cli/utils/chat-renderer.js +1 -1
  50. package/dist/cli/utils/current-cli-shim.d.ts +14 -0
  51. package/dist/cli/utils/current-cli-shim.js +1 -0
  52. package/dist/cli/utils/debug-format.d.ts +4 -0
  53. package/dist/cli/utils/debug-format.js +1 -0
  54. package/dist/config/application-service.js +1 -1
  55. package/dist/config/defaults.d.ts +2 -1
  56. package/dist/config/defaults.js +1 -1
  57. package/dist/config/guidance.d.ts +38 -4
  58. package/dist/config/guidance.js +1 -1
  59. package/dist/config/schema.d.ts +136 -67
  60. package/dist/config/schema.js +1 -1
  61. package/dist/llm/providers.d.ts +6 -4
  62. package/dist/llm/providers.js +1 -1
  63. package/dist/registry/install.js +1 -1
  64. package/dist/skills/invocation.d.ts +27 -0
  65. package/dist/skills/invocation.js +1 -0
  66. package/dist/types/chat.d.ts +3 -0
  67. package/package.json +3 -2
@@ -0,0 +1,22 @@
1
+ import { type ReactElement } from "react";
2
+ import type { BannerLine } from "../banner.ts";
3
+ import type { HistoryItem, LiveState } from "./state.ts";
4
+ export interface TranscriptViewportProps {
5
+ readonly width: number;
6
+ readonly history: readonly HistoryItem[];
7
+ readonly live: LiveState;
8
+ readonly banner?: readonly BannerLine[];
9
+ readonly animateBanner: boolean;
10
+ readonly onBannerSettled: () => void;
11
+ readonly navigationBlocked: boolean;
12
+ }
13
+ interface VisibleWindow {
14
+ readonly startIndex: number;
15
+ readonly endIndex: number;
16
+ readonly topSpacer: number;
17
+ readonly bottomSpacer: number;
18
+ readonly totalHeight: number;
19
+ }
20
+ export declare function computeVisibleWindow(heights: readonly number[], viewportHeight: number, scrollOffset: number): VisibleWindow;
21
+ export declare function TranscriptViewport(props: TranscriptViewportProps): ReactElement;
22
+ export {};
@@ -0,0 +1 @@
1
+ import{createElement as e,useCallback as t,useEffect as n,useMemo as i,useRef as o,useState as r}from"react";import{Box as a,Text as l,useBoxMetrics as c,useInput as h,useStdout as s}from"ink";import{BannerHistoryView as m,BannerLinesView as d}from"./banner-view.js";import{HistoryItemView as p}from"./history-item.js";import{LiveRegion as g}from"./live-region.js";import{DISABLE_MOUSE_TRACKING as u,ENABLE_MOUSE_TRACKING as f,parseMouseWheelInput as v}from"./mouse-input.js";const x=20,y=3;function k(e){return e.streamingText.length>0||e.reasoningText.length>0||e.activeTools.length>0||e.compacting}function M(t,n){const i="user"===t.kind||"assistant"===t.kind||"reasoning"===t.kind||"reasoning"===n?.kind,o="tool"===t.kind||"denied"===t.kind||"cancelled"===t.kind;return{key:`history:${t.id}`,element:e(p,{item:t}),paddingTop:i?1:0,marginLeft:o?3:1}}export function computeVisibleWindow(e,t,n){const i=Math.max(1,Math.floor(t)),o=e.reduce((e,t)=>e+t,0),r=Math.max(0,o-i),a=Math.min(Math.max(0,Math.floor(n)),r),l=Math.max(0,o-i-a),c=Math.max(0,l-i),h=Math.min(o,l+2*i);let s=0,m=0;for(;m<e.length&&s+(e[m]??0)<=c;)s+=e[m]??0,m+=1;const d=s;let p=m;for(;p<e.length&&s<h;)s+=e[p]??0,p+=1;return{startIndex:m,endIndex:p,topSpacer:d,bottomSpacer:Math.max(0,o-s),totalHeight:o}}function w({entry:t,onMeasure:i}){const r=o(null);return n(()=>{const e=r.current;if(null!==e){const n=e.yogaNode?.getComputedLayout();void 0!==n&&i(t.key,n.height)}}),e(a,{ref:r,flexDirection:"column",flexShrink:0,paddingTop:t.paddingTop,marginLeft:t.marginLeft},t.element)}export function TranscriptViewport(p){const{stdout:y}=s(),S=o(null),b=c(S),[T,H]=r(()=>new Map),[L,$]=r(0),[j,B]=r(0),[C,D]=r(!1),E=o(0);E.current=L;const I=i(()=>{const t=p.history.map((e,t)=>M(e,p.history[t-1]));return void 0!==p.banner&&t.unshift({key:"animated-banner",element:p.animateBanner?e(m,{lines:p.banner,onSettled:p.onBannerSettled}):e(d,{lines:p.banner}),paddingTop:0,marginLeft:1}),k(p.live)&&t.push({key:"live",element:e(g,{live:p.live}),paddingTop:0,marginLeft:1}),t},[p.animateBanner,p.banner,p.history,p.live,p.onBannerSettled]);n(()=>{H(new Map)},[p.width]);const V=t((e,t)=>{H(n=>{if(n.get(e)===t)return n;const i=new Map(n);return i.set(e,t),i})},[]);n(()=>{const e=new Set(I.map(e=>e.key));H(t=>[...t.keys()].every(t=>e.has(t))?t:new Map([...t].filter(([t])=>e.has(t))))},[I]);const G=I.map(e=>T.get(e.key)),W=G.every(e=>void 0!==e),A=W?G.map(e=>e??0):[],N=A.reduce((e,t)=>e+t,0),U=Math.max(1,b.height),Y=Math.max(0,N-U),q=Math.min(L,Y),z=o(void 0);n(()=>{if(!W)return;const e=z.current;if(z.current={totalHeight:N,viewportHeight:U},void 0===e||0===E.current)return;const t=N-e.totalHeight-(U-e.viewportHeight);0!==t&&$(e=>Math.min(Math.max(0,e+t),Y))},[W,Y,N,U]);const F=o(p.history.length);n(()=>{const e=F.current;F.current=p.history.length,E.current>0&&p.history.length>e&&B(t=>t+p.history.length-e)},[p.history.length]);const J=`${p.live.streamingText.length}:${p.live.reasoningText.length}:${p.live.activeTools.length}:${p.live.compacting?"1":"0"}`,K=o(J);n(()=>{E.current>0&&K.current!==J&&D(!0),K.current=J},[J]);const O=t(()=>{B(0),D(!1)},[]),P=t(e=>{const t=Math.min(Math.max(0,e),Y);$(t),0===t&&O()},[O,Y]),Q=t(e=>{P(E.current+e)},[P]),R=t(()=>{H(new Map),P(0)},[P]);h((e,t)=>{const n=v(e);if(void 0!==n){const e=n.row-1;return void(b.hasMeasured&&e>=b.top&&e<b.top+b.height&&Q("up"===n.direction?3:-3))}t.pageUp?Q(Math.max(1,U-2)):t.pageDown?Q(-Math.max(1,U-2)):t.ctrl&&t.home?P(Y):t.ctrl&&t.end&&R()},{isActive:!p.navigationBlocked}),n(()=>(y.write(f),()=>{y.write(u)}),[y]);const X=W&&I.length>x?computeVisibleWindow(A,U,q):void 0,Z=void 0===X?I:I.slice(X.startIndex,X.endIndex),_=X?.totalHeight??(W?N:void 0),ee=[];void 0!==X&&X.topSpacer>0&&ee.push(e(a,{key:"top-spacer",height:X.topSpacer,flexShrink:0}));for(const t of Z)ee.push(e(w,{key:t.key,entry:t,onMeasure:V}));void 0!==X&&X.bottomSpacer>0&&ee.push(e(a,{key:"bottom-spacer",height:X.bottomSpacer,flexShrink:0}));const te=q>0&&(j>0||C)?j>0?`↓ ${String(j)} 条新内容 · Ctrl+End 返回底部`:"↓ 有新内容 · Ctrl+End 返回底部":q>0?"↓ Ctrl+End 返回底部":void 0;return e(a,{flexDirection:"column",flexGrow:1,flexShrink:1,minHeight:1},e(a,{ref:S,flexDirection:"column",flexGrow:1,flexShrink:1,minHeight:1,overflowY:"hidden",justifyContent:"flex-end"},e(a,{flexDirection:"column",flexShrink:0,position:"relative",top:q,...void 0===_?{}:{height:_}},...ee)),void 0===te?null:e(a,{flexShrink:0,paddingLeft:1},e(l,{color:"cyan"},te)))}
@@ -0,0 +1,19 @@
1
+ import { type ChatUiState } from "./state.ts";
2
+ export declare const TURN_ACTIVITY_KINDS: {
3
+ readonly waitingModel: "waiting-model";
4
+ readonly reasoning: "reasoning";
5
+ readonly replying: "replying";
6
+ readonly tool: "tool";
7
+ readonly compacting: "compacting";
8
+ readonly waitingUser: "waiting-user";
9
+ readonly cancelling: "cancelling";
10
+ };
11
+ export type TurnActivityKind = (typeof TURN_ACTIVITY_KINDS)[keyof typeof TURN_ACTIVITY_KINDS];
12
+ export interface TurnActivity {
13
+ readonly kind: TurnActivityKind;
14
+ readonly key: string;
15
+ readonly label: string;
16
+ readonly animated: boolean;
17
+ readonly showPhaseElapsed: boolean;
18
+ }
19
+ export declare function resolveTurnActivity(state: ChatUiState): TurnActivity | undefined;
@@ -0,0 +1 @@
1
+ import{CHAT_PHASES as e}from"./state.js";import{endsInsideThink as i}from"./thinking-text.js";export const TURN_ACTIVITY_KINDS={waitingModel:"waiting-model",reasoning:"reasoning",replying:"replying",tool:"tool",compacting:"compacting",waitingUser:"waiting-user",cancelling:"cancelling"};function n(e,i,n){return{kind:e,key:n?.key??e,label:i,animated:n?.animated??!0,showPhaseElapsed:n?.timed??!0}}export function resolveTurnActivity(t){if(t.phase===e.idle)return;if(t.phase===e.cancelling)return n(TURN_ACTIVITY_KINDS.cancelling,"正在中断…");if(t.phase===e.confirm)return n(TURN_ACTIVITY_KINDS.waitingUser,"等待你确认…",{key:`waiting-user:${t.pendingConfirm?.approvalId??"unknown"}`,animated:!1,timed:!1});if(t.live.compacting)return n(TURN_ACTIVITY_KINDS.compacting,"压缩上下文中…");const o=t.live.activeTools[0];if(void 0!==o){const e=t.live.activeTools.length-1>0?` 等 ${String(t.live.activeTools.length)} 项`:"";return n(TURN_ACTIVITY_KINDS.tool,`执行 ${o.name}${e}…`,{key:`tool:${t.live.activeTools.map(e=>e.toolCallId).join(",")}`})}return t.live.reasoningActive?n(TURN_ACTIVITY_KINDS.reasoning,"思考中…"):t.live.streamingText.length>0&&i(t.live.streamingText,t.live.thinkTagOpen)?n(TURN_ACTIVITY_KINDS.reasoning,"思考中…",{key:"inline-reasoning"}):t.live.streamingText.length>0?n(TURN_ACTIVITY_KINDS.replying,"回复中…"):n(TURN_ACTIVITY_KINDS.waitingModel,"等待模型响应…")}
@@ -0,0 +1,15 @@
1
+ import type { ReactElement } from "react";
2
+ import { type TurnActivity } from "./turn-activity.ts";
3
+ export declare function formatElapsed(elapsedMs: number): string;
4
+ export interface TurnStatusLayout {
5
+ readonly label: string;
6
+ readonly phaseTime: string | undefined;
7
+ readonly spacer: string;
8
+ readonly turnTime: string;
9
+ }
10
+ export declare function composeTurnStatusLayout(activity: TurnActivity, phaseElapsedMs: number, turnElapsedMs: number, width: number): TurnStatusLayout;
11
+ export declare function composeTurnStatusLine(activity: TurnActivity, phaseElapsedMs: number, turnElapsedMs: number, width: number): string;
12
+ export declare function TurnStatusLine({ activity, width, }: {
13
+ readonly activity: TurnActivity;
14
+ readonly width: number;
15
+ }): ReactElement;
@@ -0,0 +1 @@
1
+ import{createElement as t,useEffect as e,useRef as n,useState as r}from"react";import{Box as o,Text as a}from"ink";import{displayWidth as i}from"./display-width.js";import{Spinner as l}from"./spinner.js";import{TURN_ACTIVITY_KINDS as s}from"./turn-activity.js";const m=250,u=2,c=2,p=2,d=4,h=1e3,f="…",g=new Intl.Segmenter(void 0,{granularity:"grapheme"}),y={[s.waitingModel]:"gray",[s.reasoning]:"magenta",[s.replying]:"green",[s.tool]:"cyan",[s.compacting]:"yellow",[s.waitingUser]:"yellow",[s.cancelling]:"yellow"};function T(t,e){if(e<=0)return"";if(i(t)<=e)return t;const n=e-i(f);let r="",o=0;for(const{segment:e}of g.segment(t)){const t=i(e);if(o+t>n)break;r+=e,o+=t}return`${r}${f}`}export function formatElapsed(t){const e=Math.max(0,Math.floor(t/1e3)),n=Math.floor(e/60),r=e%60;return n>0?`${String(n)}m${String(r)}s`:`${String(r)}s`}export function composeTurnStatusLayout(t,e,n,r){if(r<=0)return{label:"",phaseTime:void 0,spacer:"",turnTime:""};const o=`本轮 ${formatElapsed(n)}`,a=i(o);if(a>=r)return{label:"",phaseTime:void 0,spacer:"",turnTime:T(o,r)};const l=Math.max(0,r-a-2);let s=t.showPhaseElapsed&&e>=h?formatElapsed(e):void 0;let m=l-(void 0===s?0:i(` ${s}`));void 0!==s&&m<4&&(s=void 0,m=l);const u=T(t.label,Math.max(0,m)),c=void 0===s?"":` ${s}`;return{label:u,phaseTime:s,spacer:" ".repeat(Math.max(1,r-i(u)-i(c)-a)),turnTime:o}}export function composeTurnStatusLine(t,e,n,r){const o=composeTurnStatusLayout(t,e,n,r),a=void 0===o.phaseTime?"":` ${o.phaseTime}`;return`${o.label}${a}${o.spacer}${o.turnTime}`}export function TurnStatusLine({activity:i,width:s}){const u=Date.now(),c=n(u),p=n({key:i.key,startedAt:u});p.current.key!==i.key&&(p.current={key:i.key,startedAt:u});const[d,h]=r(u);e(()=>{const t=setInterval(()=>{h(Date.now())},m);return()=>{clearInterval(t)}},[]);const f=Math.max(d,u),g=composeTurnStatusLayout(i,f-p.current.startedAt,f-c.current,Math.max(0,s-2-2));return t(o,{width:s,paddingX:1},i.animated?t(l,{color:y[i.kind]}):t(a,{color:y[i.kind]},"◆"),s>3?t(a,null," "):null,g.label.length>0?t(a,{color:y[i.kind]},g.label):null,void 0===g.phaseTime?null:t(a,{dimColor:!0},` ${g.phaseTime}`),g.spacer.length>0?t(a,null,g.spacer):null,g.turnTime.length>0?t(a,{dimColor:!0},g.turnTime):null)}
@@ -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 c=32;function a(e){return e instanceof Error?e.message:String(e)}export function useSession(u,l){const[p,m]=n(r,o(l.model,l.contextWindow,{...l.initialHistory?{history:l.initialHistory}:{},...l.initialThinkingLevel?{thinkingLevel:l.initialThinkingLevel}:{}})),d=l.onThinkingChange,y=i(null),v=i(!1),f=i(!1),g=t(async t=>{let n,i="";const r=()=>{if(void 0!==n&&(clearTimeout(n),n=void 0),i.length>0){const t=i;i="",m({type:"session-event",id:e(),event:{type:"text-delta",delta:t}})}};try{for await(const o of t)if("debug"!==o.type)if("text-delta"!==o.type){if(r(),"confirmation-required"===o.type){if(v.current){u.approve(o.approvalId);continue}const t=new Promise(e=>{y.current=e});m({type:"session-event",id:e(),event:o});const n=await t;y.current=null,n?u.approve(o.approvalId):u.reject(o.approvalId,"用户取消"),m({type:"confirm-resolved"});continue}m({type:"session-event",id:e(),event:o})}else i+=o.delta,void 0===n&&(n=setTimeout(r,c));else s.debug(`chat.${o.stage} ${o.message}`)}catch(t){r(),m({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:a(t)}})}finally{r(),m({type:"turn-end"}),f.current=!1}},[u]),h=t((t,n)=>{f.current||(f.current=!0,m({type:"submit-user",id:e(),text:t}),g(u.send(n??t)).catch(()=>{}))},[g,u]),k=t(()=>{f.current||(f.current=!0,m({type:"start-compaction"}),g(u.compact("manual")).catch(()=>{}))},[g,u]),x=t(()=>{f.current&&u.cancel()},[u]),T=t(e=>{y.current?.(e)},[]),b=t(e=>{m({type:"set-draft",value:e})},[]),w=t(e=>{m({type:"set-thinking",level:e}),d?.(e)},[d]),j=t(e=>{v.current=e,m({type:"set-auto",value:e}),e&&y.current?.(!0)},[]),H=t(()=>{j(!v.current)},[j]),I=t(e=>{m({type:"commit-history",item:e})},[]);return{state:p,submit:h,compact:k,cancel:x,resolveConfirm:T,setDraft:b,setThinking:w,setAutoMode:j,toggleAutoMode:H,commitHistory:I}}
1
+ 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 a}from"../../utils/output.js";import{formatDebugEvent as s}from"../../utils/debug-format.js";const c=32;function l(e){return e instanceof Error?e.message:String(e)}export function useSession(d,p){const[u,y]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),m=p.onThinkingChange,v=i(null),g=i(!1),f=i(!1),h=t(async t=>{let n,i;const r=()=>{if(void 0!==i&&(clearTimeout(i),i=void 0),void 0!==n){const t=n;n=void 0,y({type:"session-event",id:e(),event:t})}},o=e=>{void 0===n?n=e:"text-delta"===n.type&&"text-delta"===e.type?n={type:"text-delta",delta:n.delta+e.delta}:"reasoning-delta"===n.type&&"reasoning-delta"===e.type&&n.reasoningId===e.reasoningId?n={type:"reasoning-delta",reasoningId:e.reasoningId,delta:n.delta+e.delta}:(r(),n=e),void 0===i&&(i=setTimeout(r,c))};try{for await(const n of t)if("debug"!==n.type)if("text-delta"!==n.type&&"reasoning-delta"!==n.type){if(r(),"confirmation-required"===n.type){if(g.current){d.approve(n.approvalId);continue}const t=new Promise(e=>{v.current=e});y({type:"session-event",id:e(),event:n});const i=await t;v.current=null,i?d.approve(n.approvalId):d.reject(n.approvalId,"用户取消"),y({type:"confirm-resolved"});continue}y({type:"session-event",id:e(),event:n})}else o(n);else a.debug(s(n))}catch(t){r(),y({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:l(t)}})}finally{r(),y({type:"turn-end"}),f.current=!1}},[d]),x=t(t=>{f.current||(f.current=!0,y({type:"submit-user",id:e(),text:t}),h(d.send(t)).catch(()=>{}))},[h,d]),I=t(()=>{f.current||(f.current=!0,y({type:"start-compaction"}),h(d.compact("manual")).catch(()=>{}))},[h,d]),k=t(()=>{f.current&&d.cancel()&&y({type:"cancel-requested"})},[d]),T=t(e=>{v.current?.(e)},[]),b=t(e=>{y({type:"set-draft",value:e})},[]),j=t(e=>{y({type:"set-thinking",level:e}),m?.(e)},[m]),w=t(e=>{g.current=e,y({type:"set-auto",value:e}),e&&v.current?.(!0)},[]),H=t(()=>{w(!g.current)},[w]),L=t(e=>{y({type:"commit-history",item:e})},[]);return{state:u,submit:x,compact:I,cancel:k,resolveConfirm:T,setDraft:b,setThinking:j,setAutoMode:w,toggleAutoMode:H,commitHistory:L}}
@@ -0,0 +1,66 @@
1
+ import { type ChatScreenMode } from "../../config/schema.ts";
2
+ export declare const CHAT_PRESENTATIONS: {
3
+ readonly fullscreen: "fullscreen";
4
+ readonly inline: "inline";
5
+ };
6
+ export type ChatPresentation = (typeof CHAT_PRESENTATIONS)[keyof typeof CHAT_PRESENTATIONS];
7
+ export declare const CHAT_MULTIPLEXERS: {
8
+ readonly none: "none";
9
+ readonly tmux: "tmux";
10
+ readonly tmuxControl: "tmux-control";
11
+ readonly tmuxUnknown: "tmux-unknown";
12
+ readonly zellij: "zellij";
13
+ };
14
+ export type ChatMultiplexer = (typeof CHAT_MULTIPLEXERS)[keyof typeof CHAT_MULTIPLEXERS];
15
+ export type ChatScreenModeSource = "cli" | "config";
16
+ export interface ChatTerminalCapabilities {
17
+ readonly stdinIsTty: boolean;
18
+ readonly stdoutIsTty: boolean;
19
+ readonly rawModeSupported: boolean;
20
+ readonly interactive: boolean;
21
+ readonly ci: boolean;
22
+ readonly dumbTerminal: boolean;
23
+ readonly screenReader: boolean;
24
+ readonly multiplexer: ChatMultiplexer;
25
+ }
26
+ export interface DetectChatTerminalCapabilitiesInput {
27
+ readonly stdinIsTty: boolean;
28
+ readonly stdoutIsTty: boolean;
29
+ readonly rawModeSupported: boolean;
30
+ readonly env: NodeJS.ProcessEnv;
31
+ readonly readTmuxClientFlags?: () => Promise<string | undefined>;
32
+ }
33
+ export interface ResolveChatScreenModeRequestInput {
34
+ readonly configMode: ChatScreenMode;
35
+ readonly cliValue?: string;
36
+ readonly messagePresent: boolean;
37
+ readonly json: boolean;
38
+ readonly server: boolean;
39
+ readonly list: boolean;
40
+ }
41
+ export type ChatScreenModeRequest = {
42
+ readonly ok: true;
43
+ readonly mode: ChatScreenMode;
44
+ readonly source: ChatScreenModeSource;
45
+ } | {
46
+ readonly ok: false;
47
+ readonly error: string;
48
+ };
49
+ export type ChatPresentationDecision = {
50
+ readonly ok: true;
51
+ readonly presentation: ChatPresentation;
52
+ readonly reason: "requested" | "auto" | "non-interactive" | "ci" | "dumb-terminal" | "screen-reader" | "tmux-control" | "tmux-unknown" | "zellij";
53
+ readonly warning?: string;
54
+ } | {
55
+ readonly ok: false;
56
+ readonly error: string;
57
+ };
58
+ export declare function isCiEnvironment(env: NodeJS.ProcessEnv): boolean;
59
+ export declare function readCurrentTmuxClientFlags(): Promise<string | undefined>;
60
+ export declare function detectChatTerminalCapabilities(input: DetectChatTerminalCapabilitiesInput): Promise<ChatTerminalCapabilities>;
61
+ export declare function resolveChatScreenModeRequest(input: ResolveChatScreenModeRequestInput): ChatScreenModeRequest;
62
+ export declare function resolveChatPresentation(input: {
63
+ readonly mode: ChatScreenMode;
64
+ readonly source: ChatScreenModeSource;
65
+ readonly capabilities: ChatTerminalCapabilities;
66
+ }): ChatPresentationDecision;
@@ -0,0 +1 @@
1
+ import{execFile as e}from"node:child_process";import{CHAT_SCREEN_MODES as n,chatScreenModeSchema as t}from"../../config/schema.js";export const CHAT_PRESENTATIONS={fullscreen:"fullscreen",inline:"inline"};export const CHAT_MULTIPLEXERS={none:"none",tmux:"tmux",tmuxControl:"tmux-control",tmuxUnknown:"tmux-unknown",zellij:"zellij"};function i(e,n){const t=e[n];return void 0!==t&&"0"!==t&&"false"!==t}export function isCiEnvironment(e){return i(e,"CI")||i(e,"CONTINUOUS_INTEGRATION")}function o(e){if(void 0===e)return!1;const n=e.split(",").map(e=>e.trim());return n.includes("control")||n.includes("control-mode")}export function readCurrentTmuxClientFlags(){return new Promise(n=>{e("tmux",["display-message","-p","#{client_flags}"],{encoding:"utf8",timeout:250,maxBuffer:4096,windowsHide:!0},(e,t)=>{n(e?void 0:t.trim())})})}export async function detectChatTerminalCapabilities(e){let n=CHAT_MULTIPLEXERS.none;if(e.env.TMUX){const t=await(e.readTmuxClientFlags??readCurrentTmuxClientFlags)();n=void 0===t?CHAT_MULTIPLEXERS.tmuxUnknown:o(t)?CHAT_MULTIPLEXERS.tmuxControl:CHAT_MULTIPLEXERS.tmux}else e.env.ZELLIJ&&(n=CHAT_MULTIPLEXERS.zellij);return{stdinIsTty:e.stdinIsTty,stdoutIsTty:e.stdoutIsTty,rawModeSupported:e.rawModeSupported,interactive:e.stdinIsTty&&e.stdoutIsTty&&e.rawModeSupported,ci:isCiEnvironment(e.env),dumbTerminal:"dumb"===e.env.TERM?.toLowerCase(),screenReader:"true"===e.env.INK_SCREEN_READER,multiplexer:n}}export function resolveChatScreenModeRequest(e){if(void 0===e.cliValue)return{ok:!0,mode:e.configMode,source:"config"};const i=t.safeParse(e.cliValue);if(!i.success)return{ok:!1,error:`--screen-mode 仅支持 ${n.join("、")}`};const o=[e.messagePresent?"message":void 0,e.json?"--json":void 0,e.server?"--server":void 0,e.list?"--list":void 0].filter(e=>void 0!==e);return o.length>0?{ok:!1,error:`--screen-mode 仅适用于无起始消息的交互式 roll chat,不能与 ${o.join("、")} 同用`}:{ok:!0,mode:i.data,source:"cli"}}function r(e){return[e.stdinIsTty?void 0:"stdin TTY",e.stdoutIsTty?void 0:"stdout TTY",e.rawModeSupported?void 0:"raw mode"].filter(e=>void 0!==e).join("、")}export function resolveChatPresentation(e){if("inline"===e.mode)return{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"requested"};if("fullscreen"===e.mode){if(e.capabilities.interactive)return{ok:!0,presentation:CHAT_PRESENTATIONS.fullscreen,reason:"requested"};const n=r(e.capabilities);return"cli"===e.source?{ok:!1,error:`--screen-mode fullscreen 需要交互式终端,当前缺少:${n}`}:{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"non-interactive",warning:`chat.screen-mode=fullscreen 需要交互式终端,当前缺少 ${n};已回退基础 REPL`}}return e.capabilities.interactive?e.capabilities.ci?{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"ci"}:e.capabilities.dumbTerminal?{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"dumb-terminal"}:e.capabilities.screenReader?{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"screen-reader"}:e.capabilities.multiplexer===CHAT_MULTIPLEXERS.tmuxControl?{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"tmux-control"}:e.capabilities.multiplexer===CHAT_MULTIPLEXERS.tmuxUnknown?{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"tmux-unknown"}:e.capabilities.multiplexer===CHAT_MULTIPLEXERS.zellij?{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"zellij"}:{ok:!0,presentation:CHAT_PRESENTATIONS.fullscreen,reason:"auto"}:{ok:!0,presentation:CHAT_PRESENTATIONS.inline,reason:"non-interactive"}}
@@ -15,13 +15,13 @@ declare const BrowserStopToolResultSchema: z.ZodObject<{
15
15
  }, "strip", z.ZodTypeAny, {
16
16
  status: "stopped" | "failed" | "not_running" | "not_found";
17
17
  browserInstance: string;
18
- mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
19
18
  message?: string | undefined;
19
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
20
20
  }, {
21
21
  status: "stopped" | "failed" | "not_running" | "not_found";
22
22
  browserInstance: string;
23
- mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
24
23
  message?: string | undefined;
24
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
25
25
  }>, "many">;
26
26
  }, "strip", z.ZodTypeAny, {
27
27
  stopped: number;
@@ -29,8 +29,8 @@ declare const BrowserStopToolResultSchema: z.ZodObject<{
29
29
  results: {
30
30
  status: "stopped" | "failed" | "not_running" | "not_found";
31
31
  browserInstance: string;
32
- mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
33
32
  message?: string | undefined;
33
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
34
34
  }[];
35
35
  }, {
36
36
  stopped: number;
@@ -38,8 +38,8 @@ declare const BrowserStopToolResultSchema: z.ZodObject<{
38
38
  results: {
39
39
  status: "stopped" | "failed" | "not_running" | "not_found";
40
40
  browserInstance: string;
41
- mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
42
41
  message?: string | undefined;
42
+ mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
43
43
  }[];
44
44
  }>;
45
45
  type BrowserStopToolResult = z.infer<typeof BrowserStopToolResultSchema>;
@@ -1,16 +1,44 @@
1
1
  import type { AgentSession } from "@roll-agent/runtime";
2
2
  import { type ChatConfirm } from "../utils/chat-renderer.ts";
3
3
  import type { ChatCommandResult } from "../../types/chat.ts";
4
- import type { RollConfig } from "../../config/schema.ts";
4
+ import { type RollConfig } from "../../config/schema.ts";
5
5
  import type { LlmConfigReadiness } from "../../config/helpers.ts";
6
6
  type RuntimeModule = typeof import("@roll-agent/runtime");
7
7
  type ThreadStoreInstance = InstanceType<RuntimeModule["ThreadStore"]>;
8
+ type ChatEngineOptions = ConstructorParameters<RuntimeModule["ConversationEngine"]>[0];
9
+ export declare const CHAT_ENGINE_SURFACES: {
10
+ readonly ink: "ink";
11
+ readonly basicRepl: "basic-repl";
12
+ readonly oneShot: "one-shot";
13
+ readonly json: "json";
14
+ readonly server: "server";
15
+ };
16
+ export type ChatEngineSurface = (typeof CHAT_ENGINE_SURFACES)[keyof typeof CHAT_ENGINE_SURFACES];
17
+ export declare function chatHostModeForSurface(surface: ChatEngineSurface): NonNullable<ChatEngineOptions["hostMode"]>;
18
+ interface CreateChatEngineInput {
19
+ readonly runtime: RuntimeModule;
20
+ readonly config: RollConfig;
21
+ readonly model: NonNullable<ChatEngineOptions["model"]>;
22
+ readonly store: ThreadStoreInstance;
23
+ readonly surface: ChatEngineSurface;
24
+ readonly providerOptions?: NonNullable<ChatEngineOptions["providerOptions"]>;
25
+ readonly structuredOutputProviderOptions?: NonNullable<ChatEngineOptions["structuredOutputProviderOptions"]>;
26
+ readonly structuredOutputReasoning?: NonNullable<ChatEngineOptions["structuredOutputReasoning"]>;
27
+ readonly shellEnv?: NodeJS.ProcessEnv;
28
+ }
8
29
  interface ReplIo {
9
30
  readonly input: NodeJS.ReadableStream;
10
31
  readonly output: NodeJS.WritableStream;
11
32
  readonly confirm?: ChatConfirm;
12
33
  }
34
+ export declare function createChatEngine(input: CreateChatEngineInput): import("@roll-agent/runtime").ConversationEngine;
13
35
  export declare function resolveChatLlmReadiness(config: RollConfig): LlmConfigReadiness;
36
+ export declare function resolveChatLlmCalls(provider: string, modelName: string, apiKey: string, baseUrl: string | undefined, thinkingLevel: RollConfig["runtime"]["thinkingLevel"], compactionThinkingLevel?: RollConfig["runtime"]["compaction"]["thinkingLevel"], compactionUsesStructuredOutput?: boolean): {
37
+ readonly model: NonNullable<ChatEngineOptions["model"]>;
38
+ readonly providerOptions?: NonNullable<ChatEngineOptions["providerOptions"]>;
39
+ readonly structuredOutputProviderOptions?: NonNullable<ChatEngineOptions["structuredOutputProviderOptions"]>;
40
+ readonly structuredOutputReasoning?: NonNullable<ChatEngineOptions["structuredOutputReasoning"]>;
41
+ };
14
42
  export declare function runJsonTurn(session: AgentSession, message: string): Promise<ChatCommandResult>;
15
43
  export declare function runRepl(session: AgentSession, store: ThreadStoreInstance, isNewSession: boolean, io?: ReplIo): Promise<void>;
16
44
  declare const _default: import("citty").CommandDef<{
@@ -43,5 +71,9 @@ declare const _default: import("citty").CommandDef<{
43
71
  description: string;
44
72
  default: false;
45
73
  };
74
+ "screen-mode": {
75
+ type: "string";
76
+ description: string;
77
+ };
46
78
  }>;
47
79
  export default _default;
@@ -1 +1 @@
1
- var e=this&&this.__rewriteRelativeImportExtension||function(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,o,s,n,i){return o?t?".jsx":".js":!s||n&&i?s+n+"."+i.toLowerCase()+"js":e}):e};import{defineCommand as t}from"citty";import{inspectLlmConfigReadiness as o}from"../../config/helpers.js";import{loadConfig as s}from"../../config/loader.js";import{resolveLLMCall as n,thinkingProviderOptions as i}from"../../llm/providers.js";import{createInterface as r}from"node:readline/promises";import a from"chalk";import l from"cli-table3";import{isDebugLogEnabled as c,log as d}from"../utils/output.js";import{ChatRenderer as u,clackConfirm as p}from"../utils/chat-renderer.js";import{titleFromMessage as m}from"../chat/title.js";import{buildBannerLines as g,renderBannerText as f}from"../chat/banner.js";import{getCurrentVersion as h}from"../utils/update-checker.js";import{buildSkillInvocationPrompt as v,formatSkillList as w,parseSkillInvocation as y}from"../chat/ink/commands.js";const b=import.meta.url.endsWith(".ts")?"ts":"js";function k(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}function C(e){console.log(JSON.stringify(e,null,2))}function x(e){d.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function S(e){d.warn(`skill 目录加载警告:${e}`)}function T(e,t){const o=y(t,e.getSkillSummaries());return o&&o.prompt.length>0?v(o):t}async function I(e,t,o){d.debug(`chat.repl input waiting · ${o}`),e.resume();try{const s=await e.question(t);return d.debug(`chat.repl input received · ${o} · chars=${String(s.length)}`),s}catch(e){const t=e instanceof Error?e.message:String(e);return void d.debug(`chat.repl input closed · ${o} · ${t}`)}finally{e.pause()}}async function j(){return import("@roll-agent/runtime")}async function $(t,o){const s=new URL(`./setup.${b}`,import.meta.url).href;return(await import(e(s))).runChatOnboarding({},{provider:t,model:o})}export function resolveChatLlmReadiness(e){return o(e,{provider:e.runtime.provider??e.llm.defaultProvider,model:e.runtime.model??e.llm.defaultModel})}async function N(e){const t=resolveChatLlmReadiness(e);if(!t.configured||!t.providerConfig)return d.error(t.message),void(process.exitCode=1);const o=t.providerConfig,s=t.provider,i=t.model,r=await j(),{ConversationEngine:a,ThreadStore:l,RuntimeServer:u,createStdioConnection:p}=r,{model:m,providerOptions:g}=n(s,i,o.apiKey,"chat",o.baseUrl,e.runtime.thinkingLevel),f=new l(e.runtime.threadsDir),h=new a({config:e,model:m,store:f,policy:k(r,e),maxSteps:e.runtime.maxSteps,...g?{providerOptions:g}:{},debugEvents:c(),onAgentBootstrapIssue:x,onSkillLibraryIssue:S}),v=p(process.stdin,process.stdout),w=new u(h,v);v.onClose(()=>{w.abortAll().then(()=>h.dispose()).catch(()=>{}).finally(()=>{f.close(),process.exit(0)})}),d.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function R(e,t){const{ThreadStore:o}=await j(),s=new o(e.runtime.threadsDir);try{const e=s.listThreads();if(t){const t=e.map(e=>({...e,messageCount:s.countMessages(e.id)}));return void console.log(JSON.stringify(t,null,2))}if(0===e.length)return void console.log('暂无会话。用 `roll chat "<message>"` 开始一个。');const o=new l({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const t of e)o.push([t.id,t.title??"-",String(s.countMessages(t.id)),t.updatedAt]);console.log(o.toString())}finally{s.close()}}export async function runJsonTurn(e,t){const o=[],s=[],n=[],i=[];let r,a,l,c,d="";for await(const u of e.send(t))switch(u.type){case"text-delta":d+=u.delta;break;case"tool-call":o.push({summary:`${u.agentName}.${u.toolName}`,agentName:u.agentName,toolName:u.toolName});break;case"confirmation-required":i.push({summary:`${u.agentName}.${u.toolName}`,agentName:u.agentName,toolName:u.toolName}),e.reject(u.approvalId,"json 模式不支持交互确认");break;case"step-finish":s.push({finishReason:u.finishReason,...u.usage?{usage:u.usage}:{}});break;case"message-finish":a=u.totalUsage,l=u.sessionUsage,c=u.contextInputTokens;break;case"context-compacted":n.push({reason:u.reason,strategy:u.strategy,removed:u.removed,kept:u.kept,...void 0!==u.truncatedTools?{truncatedTools:u.truncatedTools}:{},...void 0!==u.beforeInputTokens?{beforeInputTokens:u.beforeInputTokens}:{}});break;case"turn-cancelled":case"error":r=u.message}const u=e.getContextWindow();return void 0!==r?{status:"failed",stage:"execute",message:r,sessionId:e.id}:i.length>0?{status:"needs_confirmation",sessionId:e.id,message:"存在需要确认的工具调用,请在交互模式下执行或显式批准",pendingActions:i}:{status:"completed",sessionId:e.id,output:d,steps:o,...s.length>0?{stepUsages:s}:{},...a?{totalUsage:a}:{},...l?{sessionUsage:l}:{},...void 0!==u?{contextWindow:u}:{},...void 0!==c?{contextInputTokens:c}:{},...n.length>0?{compactions:n}:{}}}export async function runRepl(e,t,o,s={input:process.stdin,output:process.stdout}){const n=r({input:s.input,output:s.output});n.on("SIGINT",()=>n.close());const i=s.confirm??(async e=>{d.debug("chat.repl input waiting · confirm"),n.pause();const t=await p(e);return d.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),l=new u(i,e.getContextWindow()),c=e.getSkillSummaries();d.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let g=!o,f=!1;try{for(;;){const o=await I(n,a.green("› "),"prompt");if(void 0===o)break;const s=o.trim();if(0===s.length)continue;if("exit"===s||"quit"===s)break;if("/compact"===s){d.debug("chat.repl manual compact requested");for await(const t of e.compact("manual"))await l.handle(t,e);d.debug("chat.repl manual compact completed");continue}if("/skills"===s){d.info(w(c,(process.stdout.columns||96)-2));continue}const i=y(s,c);if(i&&0===i.prompt.length){d.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");continue}const r=i?v(i):s;g||(t.updateTitle(e.id,m(s)),g=!0),f=!0,d.debug(`chat.repl send start · chars=${String(s.length)}`);for await(const t of e.send(r))await l.handle(t,e);d.debug("chat.repl send completed")}}finally{n.close(),o&&!f&&0===t.countMessages(e.id)&&t.deleteThread(e.id)}}export default t({meta:{description:"会话式 AI 助手(多轮对话 + 自动工具调用)"},args:{message:{type:"positional",description:"起始消息",required:!1},session:{type:"string",description:"继续已有会话的 session ID"},last:{type:"boolean",description:"继续最近一个会话",default:!1},list:{type:"boolean",description:"列出已有会话",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},server:{type:"boolean",description:"以 JSON-RPC daemon 模式运行(stdio,供 GUI/前端接入)",default:!1}},async run({args:e}){let{config:t}=s();if(e.server)return void await N(t);if(e.list)return void await R(t,e.json);let o=resolveChatLlmReadiness(t);if(!o.configured){!0===process.stdin.isTTY&&!0===process.stderr.isTTY&&!e.json&&await $(o.provider,o.model)&&(t=s().config,o=resolveChatLlmReadiness(t))}if(!o.configured||!o.providerConfig)return d.error(o.message),void(process.exitCode=1);const r=o.providerConfig,a=o.provider,l=o.model;if(e.json&&!e.message)return d.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const v=await j(),{ConversationEngine:w,ThreadStore:y}=v,{model:I,providerOptions:L}=n(a,l,r.apiKey,"chat",r.baseUrl,t.runtime.thinkingLevel),O=new y(t.runtime.threadsDir),U=new w({config:t,model:I,store:O,policy:k(v,t),maxSteps:t.runtime.maxSteps,...L?{providerOptions:L}:{},debugEvents:c(),onAgentBootstrapIssue:x,onSkillLibraryIssue:S,sessionExecEnabled:void 0===e.message});let E;try{let o;if(e.session)o=await U.resumeSession(e.session);else if(e.last){const e=O.listThreads()[0];if(!e)return d.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);o=await U.resumeSession(e.id)}else o=await U.createSession(e.message?{title:m(e.message)}:{});if(E=o,e.json&&e.message){const t=await runJsonTurn(o,T(o,e.message));return C(t),void("completed"!==t.status&&(process.exitCode=1))}if(d.info(`会话 ${o.id}`),t.runtime.compaction.enabled&&void 0===o.getContextWindow()&&d.warn(`未知模型 "${l}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const t=new u(p,o.getContextWindow());for await(const s of o.send(T(o,e.message)))await t.handle(s,o)}else{const s=!e.session&&!e.last,n=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode),r=await U.getContextSummary(),c={version:h(),model:l,agentCount:r.agentCount,skillCount:r.skillCount};let u=!1;if(n)try{const e=new URL(`../chat/ink/run-ink-repl.${b}`,import.meta.url).href,{runInkRepl:n}=await import(e);await n(o,O,s,{model:l,banner:c,initialThinkingLevel:t.runtime.thinkingLevel,onThinkingChange:e=>o.setProviderOptions(i(a,l,e))}),u=!0}catch(e){d.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}u||(process.stderr.write(`${f(g(c,process.stdout.columns||80))}\n`),await runRepl(o,O,s))}}catch(e){d.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await(E?.close()),await U.dispose(),O.close()}}});
1
+ var e=this&&this.__rewriteRelativeImportExtension||function(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,o,s,n,r){return o?t?".jsx":".js":!s||n&&r?s+n+"."+r.toLowerCase()+"js":e}):e};import{defineCommand as t}from"citty";import{inspectLlmConfigReadiness as o}from"../../config/helpers.js";import{loadConfig as s}from"../../config/loader.js";import{resolveLLMCall as n,thinkingProviderOptions as r}from"../../llm/providers.js";import{createInterface as i}from"node:readline/promises";import a from"chalk";import c from"cli-table3";import{isDebugLogEnabled as u,log as d}from"../utils/output.js";import{ChatRenderer as l,clackConfirm as p}from"../utils/chat-renderer.js";import{CHAT_SCREEN_MODES as m}from"../../config/schema.js";import{titleFromMessage as g}from"../chat/title.js";import{buildBannerLines as f,renderBannerText as h}from"../chat/banner.js";import{CHAT_PRESENTATIONS as v,detectChatTerminalCapabilities as C,resolveChatPresentation as w,resolveChatScreenModeRequest as S}from"../chat/screen-mode.js";import{getCurrentVersion as y}from"../utils/update-checker.js";import{formatSkillList as k,parseSkillInvocation as E}from"../chat/ink/commands.js";import{installCurrentCliShim as T}from"../utils/current-cli-shim.js";const O=import.meta.url.endsWith(".ts")?"ts":"js";function b(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}export const CHAT_ENGINE_SURFACES={ink:"ink",basicRepl:"basic-repl",oneShot:"one-shot",json:"json",server:"server"};const N={[CHAT_ENGINE_SURFACES.ink]:"interactive",[CHAT_ENGINE_SURFACES.basicRepl]:"interactive",[CHAT_ENGINE_SURFACES.oneShot]:"one-shot",[CHAT_ENGINE_SURFACES.json]:"one-shot",[CHAT_ENGINE_SURFACES.server]:"server"};export function chatHostModeForSurface(e){return N[e]}function R(e){console.log(JSON.stringify(e,null,2))}function I(e){d.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function x(e){d.warn(`skill 目录加载警告:${e}`)}function j(){const e={...process.env};try{const t=T({env:e});return{env:e,dispose:()=>t.dispose()}}catch(t){return d.warn(`无法锁定当前 Roll CLI,子任务将继续按 PATH 查找 roll:${t instanceof Error?t.message:String(t)}`),{env:e,dispose:()=>{}}}}export function createChatEngine(e){return new e.runtime.ConversationEngine({config:e.config,model:e.model,store:e.store,hostMode:chatHostModeForSurface(e.surface),policy:b(e.runtime,e.config),maxSteps:e.config.runtime.maxSteps,...e.providerOptions?{providerOptions:e.providerOptions}:{},...e.structuredOutputProviderOptions?{structuredOutputProviderOptions:e.structuredOutputProviderOptions}:{},...e.structuredOutputReasoning?{structuredOutputReasoning:e.structuredOutputReasoning}:{},debugEvents:u(),onAgentBootstrapIssue:I,onSkillLibraryIssue:x,...e.shellEnv?{shellEnv:e.shellEnv}:{}})}async function A(e,t,o){d.debug(`chat.repl input waiting · ${o}`),e.resume();try{const s=await e.question(t);return d.debug(`chat.repl input received · ${o} · chars=${String(s.length)}`),s}catch(e){const t=e instanceof Error?e.message:String(e);return void d.debug(`chat.repl input closed · ${o} · ${t}`)}finally{e.pause()}}async function $(){return import("@roll-agent/runtime")}async function _(t,o){const s=new URL(`./setup.${O}`,import.meta.url).href;return(await import(e(s))).runChatOnboarding({},{provider:t,model:o})}export function resolveChatLlmReadiness(e){return o(e,{provider:e.runtime.provider??e.llm.defaultProvider,model:e.runtime.model??e.llm.defaultModel})}export function resolveChatLlmCalls(e,t,o,s,r,i=void 0,a=!0){const c=n(e,t,o,"chat",s,r),u=a?n(e,t,o,"structured-output",s,i??r):void 0;return{model:c.model,...c.providerOptions?{providerOptions:c.providerOptions}:{},...u?.providerOptions?{structuredOutputProviderOptions:u.providerOptions}:{},...u?.reasoning?{structuredOutputReasoning:u.reasoning}:{}}}async function U(e){const t=resolveChatLlmReadiness(e);if(!t.configured||!t.providerConfig)return d.error(t.message),void(process.exitCode=1);const o=t.providerConfig,s=t.provider,n=t.model,r=await $(),{ThreadStore:i,RuntimeServer:a,createStdioConnection:c}=r,{model:u,providerOptions:l,structuredOutputProviderOptions:p,structuredOutputReasoning:m}=resolveChatLlmCalls(s,n,o.apiKey,o.baseUrl,e.runtime.thinkingLevel,e.runtime.compaction.thinkingLevel,"summarize"===e.runtime.compaction.strategy),g=new i(e.runtime.threadsDir),f=j();let h;try{h=createChatEngine({runtime:r,config:e,model:u,store:g,surface:CHAT_ENGINE_SURFACES.server,shellEnv:f.env,...l?{providerOptions:l}:{},...p?{structuredOutputProviderOptions:p}:{},...m?{structuredOutputReasoning:m}:{}});const t=h,o=c(process.stdin,process.stdout),s=new a(t,o);o.onClose(()=>{s.abortAll().then(()=>t.dispose()).catch(()=>{}).finally(()=>{f.dispose(),g.close(),process.exit(0)})})}catch(e){throw await(h?.dispose().catch(()=>{})),f.dispose(),g.close(),e}d.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function L(e,t){const{ThreadStore:o}=await $(),s=new o(e.runtime.threadsDir);try{const e=s.listThreads();if(t){const t=e.map(e=>({...e,messageCount:s.countMessages(e.id)}));return void console.log(JSON.stringify(t,null,2))}if(0===e.length)return void console.log('暂无会话。用 `roll chat "<message>"` 开始一个。');const o=new c({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const t of e)o.push([t.id,t.title??"-",String(s.countMessages(t.id)),t.updatedAt]);console.log(o.toString())}finally{s.close()}}export async function runJsonTurn(e,t){const o=[],s=[],n=[],r=[];let i,a,c,u,d="";for await(const l of e.send(t))switch(l.type){case"text-delta":d+=l.delta;break;case"tool-call":o.push({summary:`${l.agentName}.${l.toolName}`,agentName:l.agentName,toolName:l.toolName});break;case"confirmation-required":r.push({summary:`${l.agentName}.${l.toolName}`,agentName:l.agentName,toolName:l.toolName}),e.reject(l.approvalId,"json 模式不支持交互确认");break;case"step-finish":s.push({finishReason:l.finishReason,...l.usage?{usage:l.usage}:{}});break;case"message-finish":a=l.totalUsage,c=l.sessionUsage,u=l.contextInputTokens;break;case"context-compacted":n.push({reason:l.reason,strategy:l.strategy,removed:l.removed,kept:l.kept,...void 0!==l.truncatedTools?{truncatedTools:l.truncatedTools}:{},...void 0!==l.beforeInputTokens?{beforeInputTokens:l.beforeInputTokens}:{},...void 0!==l.checkpointId?{checkpointId:l.checkpointId}:{},...void 0!==l.checkpointGeneration?{checkpointGeneration:l.checkpointGeneration}:{},...void 0!==l.checkpointSummaryStatus?{checkpointSummaryStatus:l.checkpointSummaryStatus}:{}});break;case"turn-cancelled":case"error":i=l.message}const l=e.getContextWindow();return void 0!==i?{status:"failed",stage:"execute",message:i,sessionId:e.id}:r.length>0?{status:"needs_confirmation",sessionId:e.id,message:"存在需要确认的工具调用,请在交互模式下执行或显式批准",pendingActions:r}:{status:"completed",sessionId:e.id,output:d,steps:o,...s.length>0?{stepUsages:s}:{},...a?{totalUsage:a}:{},...c?{sessionUsage:c}:{},...void 0!==l?{contextWindow:l}:{},...void 0!==u?{contextInputTokens:u}:{},...n.length>0?{compactions:n}:{}}}export async function runRepl(e,t,o,s={input:process.stdin,output:process.stdout}){const n=i({input:s.input,output:s.output});n.on("SIGINT",()=>n.close());const r=s.confirm??(async e=>{d.debug("chat.repl input waiting · confirm"),n.pause();const t=await p(e);return d.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),c=new l(r,e.getContextWindow()),u=e.getSkillSummaries();d.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let m=!o,f=!1;try{for(;;){const o=await A(n,a.green("› "),"prompt");if(void 0===o)break;const s=o.trim();if(0===s.length)continue;if("exit"===s||"quit"===s)break;if("/compact"===s){d.debug("chat.repl manual compact requested");for await(const t of e.compact("manual"))await c.handle(t,e);d.debug("chat.repl manual compact completed");continue}if("/skills"===s){d.info(k(u,(process.stdout.columns||96)-2));continue}const r=E(s,u);if(r&&0===r.prompt.length)d.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");else{m||(t.updateTitle(e.id,g(s)),m=!0),f=!0,d.debug(`chat.repl send start · chars=${String(s.length)}`);for await(const t of e.send(s))await c.handle(t,e);d.debug("chat.repl send completed")}}}finally{n.close(),o&&!f&&0===t.countMessages(e.id)&&t.deleteThread(e.id)}}export default t({meta:{description:"会话式 AI 助手(多轮对话 + 自动工具调用)"},args:{message:{type:"positional",description:"起始消息",required:!1},session:{type:"string",description:"继续已有会话的 session ID"},last:{type:"boolean",description:"继续最近一个会话",default:!1},list:{type:"boolean",description:"列出已有会话",default:!1},json:{type:"boolean",description:"JSON 格式输出",default:!1},server:{type:"boolean",description:"以 JSON-RPC daemon 模式运行(stdio,供 GUI/前端接入)",default:!1},"screen-mode":{type:"string",description:`交互界面模式(${m.join("|")})`}},async run({args:e}){let{config:t}=s();const o=S({configMode:t.chat.screenMode,...void 0!==e["screen-mode"]?{cliValue:e["screen-mode"]}:{},messagePresent:void 0!==e.message,json:e.json,server:e.server,list:e.list});if(!o.ok)return d.error(o.error),void(process.exitCode=1);if(e.server)return void await U(t);if(e.list)return void await L(t,e.json);let n=resolveChatLlmReadiness(t);if(!n.configured){!0===process.stdin.isTTY&&!0===process.stderr.isTTY&&!e.json&&await _(n.provider,n.model)&&(t=s().config,n=resolveChatLlmReadiness(t))}if(!n.configured||!n.providerConfig)return d.error(n.message),void(process.exitCode=1);const i=n.providerConfig,a=n.provider,c=n.model;if(e.json&&!e.message)return d.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const u=e.message?void 0:w({mode:o.mode,source:o.source,capabilities:await C({stdinIsTty:!0===process.stdin.isTTY,stdoutIsTty:!0===process.stdout.isTTY,rawModeSupported:"function"==typeof process.stdin.setRawMode,env:process.env})});if(void 0!==u&&!u.ok)return d.error(u.error),void(process.exitCode=1);u?.warning?d.warn(u.warning):u?.presentation===v.inline&&"requested"!==u.reason&&d.debug(`chat 自动选择基础 REPL:${u.reason}`);const m=await $(),{ThreadStore:k}=m,{model:E,providerOptions:T,structuredOutputProviderOptions:b,structuredOutputReasoning:N}=resolveChatLlmCalls(a,c,i.apiKey,i.baseUrl,t.runtime.thinkingLevel,t.runtime.compaction.thinkingLevel,"summarize"===t.runtime.compaction.strategy),I=new k(t.runtime.threadsDir),x=e.message?e.json?CHAT_ENGINE_SURFACES.json:CHAT_ENGINE_SURFACES.oneShot:u?.presentation===v.fullscreen?CHAT_ENGINE_SURFACES.ink:CHAT_ENGINE_SURFACES.basicRepl,A=j();let G,P;try{let o;if(G=createChatEngine({runtime:m,config:t,model:E,store:I,surface:x,shellEnv:A.env,...T?{providerOptions:T}:{},...b?{structuredOutputProviderOptions:b}:{},...N?{structuredOutputReasoning:N}:{}}),e.session)o=await G.resumeSession(e.session);else if(e.last){const e=I.listThreads()[0];if(!e)return d.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);o=await G.resumeSession(e.id)}else o=await G.createSession(e.message?{title:g(e.message)}:{});if(P=o,e.json&&e.message){const t=await runJsonTurn(o,e.message);return R(t),void("completed"!==t.status&&(process.exitCode=1))}if(u?.presentation!==v.fullscreen&&d.info(`会话 ${o.id}`),t.runtime.compaction.enabled&&void 0===o.getContextWindow()&&d.warn(`未知模型 "${c}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const t=new l(p,o.getContextWindow());for await(const s of o.send(e.message))await t.handle(s,o)}else{const s=!e.session&&!e.last,n=await G.getContextSummary(),i={version:y(),model:c,agentCount:n.agentCount,skillCount:n.skillCount};let l=!1;if(u?.presentation===v.fullscreen)try{const e=new URL(`../chat/ink/run-ink-repl.${O}`,import.meta.url).href,{runInkRepl:n}=await import(e);await n(o,I,s,{model:c,banner:i,initialThinkingLevel:t.runtime.thinkingLevel,onStarted:()=>{l=!0},onThinkingChange:e=>o.setProviderOptions(r(a,c,e))})}catch(e){if(l)throw e;d.info(`会话 ${o.id}`),d.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}l||(process.stderr.write(`${h(f(i,process.stdout.columns||80))}\n`),await runRepl(o,I,s))}}catch(e){d.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await(P?.close()),await(G?.dispose()),I.close(),A.dispose()}}});
@@ -1 +1 @@
1
- import{defineCommand as o}from"citty";import{existsSync as e,readFileSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as n}from"node:path";import{createInterface as i}from"node:readline/promises";import{ConfigApplicationService as s}from"../../config/application-service.js";import{DEFAULT_CONFIG as c,DEFAULT_LLM_MODELS as a,LLM_PROVIDER_OPTIONS as l}from"../../config/defaults.js";import{inspectConfigFile as f,loadConfig as d,parseConfigDocument as u,resolveConfigPath as p}from"../../config/loader.js";import{createConfigRevision as g}from"../../config/document-store.js";import{decodeFromYaml as m,normalizeUserPath as v}from"../../config/key-codec.js";import{applyKnownConfigMigrations as h}from"../../config/migration.js";import{explainConfig as y}from"./config-explain.js";import{runConfigSetup as $}from"./config-setup.js";export default o({meta:{description:"管理全局配置"},args:{action:{type:"positional",description:"操作(init/get/set/migrate/setup/explain)",required:!0},key:{type:"positional",description:"配置键(get/set 时使用,用英文句点 `.` 分隔,如 ask.confirm-threshold)",required:!1},value:{type:"positional",description:"配置值(set 时使用)",required:!1}},async run({args:o}){try{if("init"===o.action)return void await Y();if("get"===o.action)return void K(o.key);if("set"===o.action)return void S(o.key,o.value);if("migrate"===o.action)return void migrateConfig();if("setup"===o.action)return process.stdin.isTTY?void await $(o.key,o.value):(console.error("✗ `roll config setup` 需要交互式终端。"),console.error(" 非交互环境请改用 `roll config set <key> <value>` 或直接编辑 roll.config.yaml。"),console.error(" 查看配置说明:`roll config explain <key>`。"),void(process.exitCode=1));if("explain"===o.action)return void y(o.key);console.error(`✗ 未知操作: ${o.action}。可用: init, get, set, migrate, setup, explain`),process.exitCode=1}catch(o){const e=o instanceof Error?o.message:String(o);console.error(`✗ ${e}`),process.exitCode=1}}});const P={anthropic:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY",qwen:"DASHSCOPE_API_KEY",deepseek:"DEEPSEEK_API_KEY"};function w(o,e){const r=o?.trim();return r||e}function k(o){return l.some(e=>e===o)}function E(o){return k(o)?a[o]:c.llm.defaultModel}function x(o){return k(o)?P[o]:P.anthropic}function C({provider:o,model:e,apiKeyEnv:r}){return`llm:\n default-provider: ${o}\n default-model: ${e}\n providers:\n ${o}:\n api-key: \${${r}}\n\nask:\n confirm-threshold: 0.5\n\nagents:\n data-dir: ~/.roll-agent/agents\n`}async function j(){if(!process.stdin.isTTY){const[o,e,t]=r(0,"utf-8").split(/\r?\n/u),n=w(o,c.llm.defaultProvider);return{provider:n,model:w(e,E(n)),apiKeyEnv:w(t,x(n))}}const o=i({input:process.stdin,output:process.stderr}),e=w(await o.question(`默认 LLM provider (${l.join("/")}) [${c.llm.defaultProvider}]: `),c.llm.defaultProvider),t=E(e),n=w(await o.question(`默认 model [${t}]: `),t),s=x(e),a=w(await o.question(`API Key 环境变量名 [${s}]: `),s);return o.close(),{provider:e,model:n,apiKeyEnv:a}}async function Y(){const o=p()??n(t(),"roll.config.yaml");let c,a=!1;if(e(o)){c=g(r(o,"utf-8"));const e=f({configPath:o});switch(e.status){case"needs-migration":console.error(`⚠ 现有配置文件需要迁移: ${o}`),console.error(" 建议先运行 `roll config migrate`,再决定是否重新初始化。");break;case"invalid":a=!0,console.error(`⚠ 现有配置文件存在问题:\n${e.error.message}`)}if(console.error(`⚠ 配置文件已存在: ${o}`),!process.stdin.isTTY)throw new Error("非交互模式下不会覆盖现有配置文件,请手动删除后重试。");const t=i({input:process.stdin,output:process.stderr}),n=await t.question("是否覆盖?(y/N) ");if(t.close(),"y"!==n.toLowerCase())return void console.error("已取消。")}const l=C(await j()),d=new s({configPath:o}),u=void 0===c?d.saveYaml(l):a?d.replaceYamlForInit(l,c):d.saveYaml(l,c);console.log(`✓ 配置文件已创建: ${o}`),void 0!==u.backupPath&&console.log(`✓ 已备份原文件: ${u.backupPath}`)}export function migrateConfig(){const o=f();if("not-found"===o.status)throw new Error("未找到配置文件。请先运行 roll config init");if("valid"===o.status)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);if("invalid"===o.status)throw o.error;const e=u(o.raw,o.configPath),r=h(e);if(!r.ok){const o=r.issues.map(o=>` - ${o.message}`).join("\n");throw new Error(`配置无法自动迁移:\n${o}`)}if(!r.changed)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);const t=new s({configPath:o.configPath}).saveStructured(m(r.document),g(o.raw));console.log(`✓ 配置文件已迁移: ${o.configPath}`),void 0!==t.backupPath&&console.log(`✓ 已备份原文件: ${t.backupPath}`);for(const o of r.summary)console.log(` - ${o}`)}function K(o){const{config:e,configPath:r}=d();if(!o)return console.log(JSON.stringify(e,null,2)),void(r&&console.error(`(来源: ${r})`));const t=v(o.split("."));let n=e;for(const e of t){if("object"!=typeof n||null===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);n=n[e]}if(void 0===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);console.log("object"==typeof n?JSON.stringify(n,null,2):String(n))}function S(o,e){if(!o||void 0===e)return console.error("✗ 用法: roll config set <key> <value>"),console.error(" 示例: roll config set ask.confirm-threshold 0.5"),void(process.exitCode=1);const{configPath:r}=d();if(!r)return console.error("✗ 未找到配置文件。请先运行 roll config init"),void(process.exitCode=1);const t=v(o.split("."));if(0===t.length)return console.error("✗ 配置键不能为空"),void(process.exitCode=1);let n=e;"true"===e?n=!0:"false"===e?n=!1:/^\d+(\.\d+)?$/.test(e)&&(n=Number(e));const i=new s({configPath:r}),c=i.read();i.savePatches([{op:"set",path:t,value:n}],c.revision),console.log(`✓ ${o} = ${String(n)}`),console.error(` (已写入: ${r})`)}
1
+ import{defineCommand as o}from"citty";import{existsSync as e,readFileSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as n}from"node:path";import{createInterface as i}from"node:readline/promises";import{ConfigApplicationService as s}from"../../config/application-service.js";import{DEFAULT_CONFIG as c,DEFAULT_LLM_MODELS as a,LLM_PROVIDER_OPTIONS as l}from"../../config/defaults.js";import{inspectConfigFile as f,loadConfig as d,parseConfigDocument as u,resolveConfigPath as p}from"../../config/loader.js";import{createConfigRevision as g}from"../../config/document-store.js";import{decodeFromYaml as m,normalizeUserPath as v}from"../../config/key-codec.js";import{applyKnownConfigMigrations as h}from"../../config/migration.js";import{explainConfig as y}from"./config-explain.js";import{runConfigSetup as $}from"./config-setup.js";export default o({meta:{description:"管理全局配置"},args:{action:{type:"positional",description:"操作(init/get/set/migrate/setup/explain)",required:!0},key:{type:"positional",description:"配置键(get/set 时使用,用英文句点 `.` 分隔,如 ask.confirm-threshold)",required:!1},value:{type:"positional",description:"配置值(set 时使用)",required:!1}},async run({args:o}){try{if("init"===o.action)return void await Y();if("get"===o.action)return void A(o.key);if("set"===o.action)return void I(o.key,o.value);if("migrate"===o.action)return void migrateConfig();if("setup"===o.action)return process.stdin.isTTY?void await $(o.key,o.value):(console.error("✗ `roll config setup` 需要交互式终端。"),console.error(" 非交互环境请改用 `roll config set <key> <value>` 或直接编辑 roll.config.yaml。"),console.error(" 查看配置说明:`roll config explain <key>`。"),void(process.exitCode=1));if("explain"===o.action)return void y(o.key);console.error(`✗ 未知操作: ${o.action}。可用: init, get, set, migrate, setup, explain`),process.exitCode=1}catch(o){const e=o instanceof Error?o.message:String(o);console.error(`✗ ${e}`),process.exitCode=1}}});const P={anthropic:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY",qwen:"DASHSCOPE_API_KEY",deepseek:"DEEPSEEK_API_KEY",xai:"XAI_API_KEY"};function w(o,e){const r=o?.trim();return r||e}function k(o){return l.some(e=>e===o)}function E(o){return k(o)?a[o]:c.llm.defaultModel}function x(o){return k(o)?P[o]:P.anthropic}function C({provider:o,model:e,apiKeyEnv:r}){return`llm:\n default-provider: ${o}\n default-model: ${e}\n providers:\n ${o}:\n api-key: \${${r}}\n\nask:\n confirm-threshold: 0.5\n\nagents:\n data-dir: ~/.roll-agent/agents\n`}async function j(){if(!process.stdin.isTTY){const[o,e,t]=r(0,"utf-8").split(/\r?\n/u),n=w(o,c.llm.defaultProvider);return{provider:n,model:w(e,E(n)),apiKeyEnv:w(t,x(n))}}const o=i({input:process.stdin,output:process.stderr}),e=w(await o.question(`默认 LLM provider (${l.join("/")}) [${c.llm.defaultProvider}]: `),c.llm.defaultProvider),t=E(e),n=w(await o.question(`默认 model [${t}]: `),t),s=x(e),a=w(await o.question(`API Key 环境变量名 [${s}]: `),s);return o.close(),{provider:e,model:n,apiKeyEnv:a}}async function Y(){const o=p()??n(t(),"roll.config.yaml");let c,a=!1;if(e(o)){c=g(r(o,"utf-8"));const e=f({configPath:o});switch(e.status){case"needs-migration":console.error(`⚠ 现有配置文件需要迁移: ${o}`),console.error(" 建议先运行 `roll config migrate`,再决定是否重新初始化。");break;case"invalid":a=!0,console.error(`⚠ 现有配置文件存在问题:\n${e.error.message}`)}if(console.error(`⚠ 配置文件已存在: ${o}`),!process.stdin.isTTY)throw new Error("非交互模式下不会覆盖现有配置文件,请手动删除后重试。");const t=i({input:process.stdin,output:process.stderr}),n=await t.question("是否覆盖?(y/N) ");if(t.close(),"y"!==n.toLowerCase())return void console.error("已取消。")}const l=C(await j()),d=new s({configPath:o}),u=void 0===c?d.saveYaml(l):a?d.replaceYamlForInit(l,c):d.saveYaml(l,c);console.log(`✓ 配置文件已创建: ${o}`),void 0!==u.backupPath&&console.log(`✓ 已备份原文件: ${u.backupPath}`)}export function migrateConfig(){const o=f();if("not-found"===o.status)throw new Error("未找到配置文件。请先运行 roll config init");if("valid"===o.status)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);if("invalid"===o.status)throw o.error;const e=u(o.raw,o.configPath),r=h(e);if(!r.ok){const o=r.issues.map(o=>` - ${o.message}`).join("\n");throw new Error(`配置无法自动迁移:\n${o}`)}if(!r.changed)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);const t=new s({configPath:o.configPath}).saveStructured(m(r.document),g(o.raw));console.log(`✓ 配置文件已迁移: ${o.configPath}`),void 0!==t.backupPath&&console.log(`✓ 已备份原文件: ${t.backupPath}`);for(const o of r.summary)console.log(` - ${o}`)}function A(o){const{config:e,configPath:r}=d();if(!o)return console.log(JSON.stringify(e,null,2)),void(r&&console.error(`(来源: ${r})`));const t=v(o.split("."));let n=e;for(const e of t){if("object"!=typeof n||null===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);n=n[e]}if(void 0===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);console.log("object"==typeof n?JSON.stringify(n,null,2):String(n))}function I(o,e){if(!o||void 0===e)return console.error("✗ 用法: roll config set <key> <value>"),console.error(" 示例: roll config set ask.confirm-threshold 0.5"),void(process.exitCode=1);const{configPath:r}=d();if(!r)return console.error("✗ 未找到配置文件。请先运行 roll config init"),void(process.exitCode=1);const t=v(o.split("."));if(0===t.length)return console.error("✗ 配置键不能为空"),void(process.exitCode=1);let n=e;"true"===e?n=!0:"false"===e?n=!1:/^\d+(\.\d+)?$/.test(e)&&(n=Number(e));const i=new s({configPath:r}),c=i.read();i.savePatches([{op:"set",path:t,value:n}],c.revision),console.log(`✓ ${o} = ${String(n)}`),console.error(` (已写入: ${r})`)}
@@ -1 +1 @@
1
- import t from"chalk";import{isCancel as s,select as e}from"@clack/prompts";import{createSpinner as n,log as o}from"./output.js";import{GLYPHS as a}from"./glyphs.js";import{computeUsageParts as i,formatUsageLine as r}from"./token-format.js";import{formatToolInput as p,formatApprovalDetails as l}from"./tool-format.js";export const clackConfirm=async t=>{const n=await e({message:t,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!s(n)&&"yes"===n};function c(t){const s=[`chat.${t.stage}`,t.message];return void 0!==t.elapsedMs&&s.push(`${String(t.elapsedMs)}ms`),void 0!==t.data&&s.push(JSON.stringify(t.data)),s.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;toolLabels=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(t,s){this.confirm=t,this.contextWindow=s}async handle(s,e){switch(s.type){case"debug":o.debug(c(s));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(s.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const e=`${s.agentName}.${s.toolName}`,o=n(`${t.cyan(e)} ${t.gray(p(s.input))}`);o.start(),this.spinners.set(s.toolCallId,o),this.toolLabels.set(s.toolCallId,e);break}case"tool-output-delta":{const e=this.spinners.get(s.toolCallId),n=this.toolLabels.get(s.toolCallId);if(e&&n){const o=s.delta.split("\n").map(t=>t.trim()).filter(t=>t.length>0).at(-1);if(o){const s=o.length>80?`${o.slice(0,79)}…`:o;e.text=`${t.cyan(n)} ${t.gray(s)}`}}break}case"tool-result":{const t=this.spinners.get(s.toolCallId);t&&(s.isError?t.fail():t.succeed(),this.spinners.delete(s.toolCallId)),this.toolLabels.delete(s.toolCallId);break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const t=s.reason?`(${s.reason})`:"",n=l(s.input),o=`执行 ${s.agentName}.${s.toolName}${t}?`;await this.confirm(n?`${o}\n${n}`:o)?e.approve(s.approvalId):e.reject(s.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=n(t.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const e="auto"===s.reason?"自动压缩":"手动压缩",n=s.truncatedTools?`,精简 ${String(s.truncatedTools)} 个工具结果`:"",o=0!==s.removed||s.truncatedTools?`${a.compact} ${e}(${s.strategy}):移除 ${String(s.removed)} 条 → 保留 ${String(s.kept)} 条${n}`:`${a.compact} ${e}:无需压缩`;process.stderr.write(`${t.gray(o)}\n`);break}case"turn-cancelled":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine();for(const s of this.spinners.values())s.warn(`${s.text} ${t.yellow("已中断")}`);this.spinners.clear(),this.toolLabels.clear(),o.warn(s.message);break;case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),o.error(s.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===s.text.length&&(s.totalUsage?.outputTokens??0)>0&&o.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),s.stoppedAtStepLimit&&o.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const e=r(i(s.totalUsage,s.sessionUsage,this.contextWindow,s.contextInputTokens));e&&process.stderr.write(`${t.gray(e)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=n(t.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
1
+ import e from"chalk";import{isCancel as t,select as s}from"@clack/prompts";import{createSpinner as o,log as n}from"./output.js";import{GLYPHS as a}from"./glyphs.js";import{computeUsageParts as i,formatUsageLine as r}from"./token-format.js";import{formatToolInput as p,formatApprovalDetails as l}from"./tool-format.js";import{formatDebugEvent as c}from"./debug-format.js";export const clackConfirm=async e=>{const o=await s({message:e,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!t(o)&&"yes"===o};export class ChatRenderer{confirm;contextWindow;spinners=new Map;toolLabels=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(e,t){this.confirm=e,this.contextWindow=t}async handle(t,s){switch(t.type){case"debug":n.debug(c(t));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(t.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const s=`${t.agentName}.${t.toolName}`,n=o(`${e.cyan(s)} ${e.gray(p(t.input))}`);n.start(),this.spinners.set(t.toolCallId,n),this.toolLabels.set(t.toolCallId,s);break}case"tool-output-delta":{const s=this.spinners.get(t.toolCallId),o=this.toolLabels.get(t.toolCallId);if(s&&o){const n=t.delta.split("\n").map(e=>e.trim()).filter(e=>e.length>0).at(-1);if(n){const t=n.length>80?`${n.slice(0,79)}…`:n;s.text=`${e.cyan(o)} ${e.gray(t)}`}}break}case"tool-result":{const e=this.spinners.get(t.toolCallId);e&&(t.isError?e.fail():e.succeed(),this.spinners.delete(t.toolCallId)),this.toolLabels.delete(t.toolCallId);break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const e=t.reason?`(${t.reason})`:"",o=l(t.input),n=`执行 ${t.agentName}.${t.toolName}${e}?`;await this.confirm(o?`${n}\n${o}`:n)?s.approve(t.approvalId):s.reject(t.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=o(e.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const s="auto"===t.reason?"自动压缩":"手动压缩",o=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"",n=0!==t.removed||t.truncatedTools?`${a.compact} ${s}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${o}`:`${a.compact} ${s}:无需压缩`;process.stderr.write(`${e.gray(n)}\n`);break}case"turn-cancelled":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine();for(const t of this.spinners.values())t.warn(`${t.text} ${e.yellow("已中断")}`);this.spinners.clear(),this.toolLabels.clear(),"user"===t.reason?process.stderr.write(`${e.gray(`■ ${t.message}`)}\n`):"timeout"===t.reason?n.warn(t.message):n.error(t.message);break;case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),n.error(t.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===t.text.length&&(t.totalUsage?.outputTokens??0)>0&&n.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),t.stoppedAtStepLimit&&n.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const s=r(i(t.totalUsage,t.sessionUsage,this.contextWindow,t.contextInputTokens));s&&process.stderr.write(`${e.gray(s)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=o(e.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
@@ -0,0 +1,14 @@
1
+ export interface CurrentCliShim {
2
+ readonly path: string;
3
+ dispose(): void;
4
+ }
5
+ export interface InstallCurrentCliShimOptions {
6
+ readonly env: NodeJS.ProcessEnv;
7
+ readonly executable?: string;
8
+ readonly execArgv?: readonly string[];
9
+ readonly entryPath?: string;
10
+ readonly launchCwd?: string;
11
+ readonly platform?: NodeJS.Platform;
12
+ readonly tempRoot?: string;
13
+ }
14
+ export declare function installCurrentCliShim(options: InstallCurrentCliShimOptions): CurrentCliShim;
@@ -0,0 +1 @@
1
+ import{chmodSync as e,mkdtempSync as t,rmSync as n,writeFileSync as r}from"node:fs";import{tmpdir as o}from"node:os";import{join as i,resolve as s}from"node:path";const c="ROLL_CURRENT_CLI",a=/^--inspect/u,l=["--require","-r","--import","--loader","--experimental-loader"],p=new WeakMap;function u(e){return`'${e.replaceAll("'","'\\''")}'`}function h(e){return`"${e.replaceAll("%","%%").replaceAll('"','\\"')}"`}function f(e,t){return/^\.{1,2}[\\/]/u.test(e)?s(t,e):e}function d(e,t){const n=[];for(let r=0;r<e.length;r+=1){const o=e[r];if(void 0===o||a.test(o))continue;if(void 0!==l.find(e=>e===o)){n.push(o);const i=e[r+1];void 0!==i&&(n.push(f(i,t)),r+=1);continue}const i=l.find(e=>o.startsWith(`${e}=`));if(void 0!==i){const e=o.slice(i.length+1);n.push(`${i}=${f(e,t)}`);continue}n.push(o)}return n}function v(e,t){return"win32"!==t?"PATH":Object.keys(e).find(e=>"path"===e.toLowerCase())??"Path"}function m(e){return`#!/bin/sh\nexec ${e.map(u).join(" ")} "$@"\n`}function C(e){return["@echo off",`${e.map(h).join(" ")} %*`,"exit /b %ERRORLEVEL%",""].join("\r\n")}export function installCurrentCliShim(s){const a=s.executable??process.execPath,l=s.entryPath??process.argv[1];if(!l)throw new Error("无法确定当前 Roll CLI 入口");const u=s.platform??process.platform,h=s.env,f="win32"===u?";":":";let w,$;try{w=t(i(s.tempRoot??o(),"roll-current-cli-")),e(w,448),$=i(w,"win32"===u?"roll.cmd":"roll");const n=[a,...d(s.execArgv??process.execArgv,s.launchCwd??process.cwd()),l],c="win32"===u?C(n):m(n);r($,c,{encoding:"utf8",mode:448})}catch(e){throw void 0!==w&&n(w,{recursive:!0,force:!0}),e}const b=v(h,u),x=p.get(h),y=x??{pathKey:b,basePath:h[b],baseCurrentCli:h[c],entries:[]};void 0===x&&p.set(h,y);const g={directory:w,path:$,active:!0};y.entries.push(g);const L=h[b];h[b]=L?`${w}${f}${L}`:w,h[c]=$;const R=()=>{n(w,{recursive:!0,force:!0})};process.once("exit",R);let P=!1;return{path:$,dispose(){if(P)return;P=!0,process.removeListener("exit",R),g.active=!1;const e=y.entries.filter(e=>e.active);if(0===e.length)void 0===y.basePath?delete h[y.pathKey]:h[y.pathKey]=y.basePath,void 0===y.baseCurrentCli?delete h[c]:h[c]=y.baseCurrentCli,p.delete(h);else{const t=h[y.pathKey];void 0!==t&&(h[y.pathKey]=t.split(f).filter(e=>e!==w).join(f)),y.entries.some(e=>e.path===h[c])&&(h[c]=e.at(-1)?.path)}R()}}}
@@ -0,0 +1,4 @@
1
+ import type { SessionEvent } from "@roll-agent/runtime";
2
+ export declare function formatDebugEvent(event: Extract<SessionEvent, {
3
+ type: "debug";
4
+ }>): string;
@@ -0,0 +1 @@
1
+ export function formatDebugEvent(s){const t=[`chat.${s.stage}`,s.message];return void 0!==s.elapsedMs&&t.push(`${String(s.elapsedMs)}ms`),void 0!==s.data&&t.push(JSON.stringify(s.data)),t.join(" · ")}
@@ -1 +1 @@
1
- import{homedir as t}from"node:os";import{resolve as e}from"node:path";import{stringify as r}from"yaml";import{DEFAULT_CONFIG as n}from"./defaults.js";import{ConfigRevisionConflictError as i,YamlConfigDocumentStore as o,applyPatchesToYamlText as s}from"./document-store.js";import{CONFIG_KEY_CODEC as a,decodeFromYaml as c,encodePathToYaml as p,encodeToYaml as h,kebabToCamel as u}from"./key-codec.js";import{parseConfigDocument as f,resolveConfigPath as l,validateConfigText as d}from"./loader.js";import{detectKnownConfigMigrations as g,formatConfigMigrationError as m}from"./migration.js";import{isRollConfigSecretPath as w,isSecretConfigValue as v}from"./secret-policy.js";export const CONFIG_UI_SECRET_SENTINEL="__ROLL_UI_KEEP_EXISTING_SECRET__";export const CONFIG_ACTIVATION_KINDS=["next-command","next-chat","restart-agent","manual"];export class ConfigApplicationValidationError extends Error{code="config_validation_failed";issues;constructor(t){const e=t instanceof Error?t:new Error(String(t));super(e.message,{cause:e}),this.name="ConfigApplicationValidationError",this.issues=D(e.message)}}export class ConfigApplicationService{configPath;store;isSecretPath;constructor(r={}){this.configPath=l(r)??e(t(),"roll.config.yaml"),this.store=new o(this.configPath,Y());const n=new Set(r.secretEnvNames??[]),i=new Map((r.agentEnvFields??[]).map(t=>[F(t.agentName,t.name),t.secret]));this.isSecretPath=t=>V(t,n,i,r.redactUnknownAgentEnv??!1)}read(){const t=this.store.read();return y(t.raw,t.configPath),P(t,this.isSecretPath)}readForRepair(){const t=this.store.read(),e=f(t.raw,t.configPath),r=g(e);if(r.needsMigration)throw new ConfigApplicationValidationError(new Error(m(t.configPath,r)));return P(t,this.isSecretPath)}previewPatches(t,e){return this.preparePatches(t,e).publicPreview}savePatches(t,e){return this.commitPrepared(this.preparePatches(t,e))}previewStructured(t,e){return this.prepareStructured(t,e).publicPreview}saveStructured(t,e){return this.commitPrepared(this.prepareStructured(t,e))}previewYaml(t,e){return this.prepareYaml(t,e).publicPreview}saveYaml(t,e){return this.commitPrepared(this.prepareYaml(t,e))}replaceYamlForInit(t,e){y(t,this.configPath);const r=this.store.replaceRawForRecovery(t,e);return{snapshot:P(r,this.isSecretPath),changed:r.changed,...void 0!==r.backupPath?{backupPath:r.backupPath}:{}}}preparePatches(t,e){const r=t.map(S),n=this.store.previewPatches(r,e);return this.prepareDocumentPreview(n)}prepareStructured(t,e){if(!M(t))throw new ConfigApplicationValidationError(new Error("配置内容必须是一个 JSON/YAML object。"));const r=this.store.read();if(void 0!==e&&e!==r.revision)throw new i(e,r.revision);const n=O(t,A(r.persisted),[],this.isSecretPath),o=h(n);if(!M(o))throw new ConfigApplicationValidationError(new Error("编码后的配置必须是 object。"));const s=this.store.previewObject(o,r.revision);return this.prepareDocumentPreview(s)}prepareYaml(t,e){const r=this.store.read();if(void 0!==e&&e!==r.revision)throw new i(e,r.revision);let n,o=t;try{n=f(o,this.configPath)}catch(t){throw new ConfigApplicationValidationError(t)}const c=[];let p;j(n,A(r.persisted),[],[],c,this.isSecretPath,a),c.length>0&&(o=s(o,this.configPath,c));try{p=this.store.previewRaw(o,r.revision)}catch(t){throw new ConfigApplicationValidationError(t)}return this.prepareDocumentPreview(p)}prepareDocumentPreview(t){y(t.raw,this.configPath);const e=_(A(f(t.previousRaw,this.configPath)),A(t.persisted)),r=P(t,this.isSecretPath),n=f(t.previousRaw,this.configPath),i=P({configPath:t.configPath,existed:t.existed,raw:t.previousRaw,revision:t.previousRevision,persisted:n},this.isSecretPath);return{documentPreview:t,publicPreview:{snapshot:r,changed:t.changed,changedPaths:e,effects:planConfigActivation(e),diff:U(i.yaml,r.yaml)}}}commitPrepared(t){const e=this.store.commit(t.documentPreview,{backup:!0}),r=P(e,this.isSecretPath);return{...t.publicPreview,snapshot:r,...void 0!==e.backupPath?{backupPath:e.backupPath}:{}}}}export function planConfigActivation(t){const e=new Map;for(const r of t){const t=E(r),n=`${t.kind}:${t.agentName??""}:${t.title}`,i=e.get(n);void 0!==i?i.paths.push(r):e.set(n,{...t,paths:[r]})}return[...e.values()].map(t=>({...t,paths:t.paths}))}export function createConfigPatches(t,e,r=[]){if(M(t)&&M(e)){const n=[],i=new Set([...Object.keys(t),...Object.keys(e)]);for(const o of i)o in e?o in t?n.push(...createConfigPatches(t[o],e[o],[...r,o])):n.push({op:"set",path:[...r,o],value:e[o]}):n.push({op:"delete",path:[...r,o]});return n}if(q(t,e))return[];if(0===r.length)throw new ConfigApplicationValidationError(new Error("配置根节点必须保持为 object。"));return[{op:"set",path:r,value:e}]}function E(t){const[e,r,n]=t;return"agents"===e&&"dataDir"===r?{kind:"manual",title:"Agent 数据目录需要人工迁移",description:"保存不会搬迁旧 PID、日志或注册数据;请先停止 Agent,再人工迁移目录。",requiresConfirmation:!0}:"agents"===e&&"env"===r&&"string"==typeof n?{kind:"restart-agent",title:`重启 ${n}`,description:"运行中的 core-managed Agent 需要重启;已停止的 Agent 保持停止。",agentName:n,requiresConfirmation:!0}:"browser"===e?{kind:"restart-agent",title:"重启 browser-use-agent",description:"浏览器实例声明在 Agent 启动时注入;重启 Agent 后 Chrome 仍会在首次工具调用时懒启动。",agentName:"browser-use-agent",requiresConfirmation:!0}:"runtime"===e||"skills"===e?{kind:"next-chat",title:"新会话生效",description:"当前 roll chat 会话保持原配置,新会话会重新加载。",requiresConfirmation:!1}:{kind:"next-command",title:"后续命令生效",description:"后续 Roll 命令会重新加载该配置。",requiresConfirmation:!1}}function P(t,e){const r=A(t.persisted),n=N(t.persisted,[],[],e,a),i=n.map(t=>t.path),o=new Set(i.map(L)),c=I(r,[],o);if(!M(c))throw new ConfigApplicationValidationError(new Error("脱敏后的配置必须是 object。"));const p=n.filter(t=>{const e=G(r,t.path);return!("string"==typeof e&&$(e))}).map(t=>({op:"set",path:t.persistedPath,value:CONFIG_UI_SECRET_SENTINEL})),h=0===p.length?t.raw:s(t.raw,t.configPath,p);return{configPath:t.configPath,existed:t.existed,revision:t.revision,persisted:c,yaml:h,configuredSecretPaths:i}}function y(t,e){try{d(t,e)}catch(t){throw new ConfigApplicationValidationError(t)}}function A(t){const e=c(t);if(!M(e))throw new ConfigApplicationValidationError(new Error("持久化配置必须是 object。"));return e}function S(t){const e=C(t.path);return"set"===t.op?{op:"set",path:e,value:k(t.value,t.path)}:{op:"delete",path:e}}function C(t){return p(t.map(String)).map((e,r)=>"number"==typeof t[r]?t[r]:e)}function k(t,e){if(!M(t)&&!Array.isArray(t))return t;const r=b({},e,t),n=h(r);return M(n)?G(n,C(e)):t}function b(t,e,r){let n=t;for(const[t,i]of e.entries()){const o=String(i);if(t===e.length-1){n[o]=r;break}const s={};n[o]=s,n=s}return t}function _(t,e,r=[]){const n=[],i=new Set([...Object.keys(t),...Object.keys(e)]);for(const o of i){const i=t[o],s=e[o],a=[...r,o];M(i)||M(s)?n.push(..._(M(i)?i:{},M(s)?s:{},a)):q(i,s)||n.push(a)}return n}function N(t,e,r,n,i){const o=[];if(Array.isArray(t)){const s="array"===i?.kind?i.item:i;return t.forEach((t,i)=>{const a=[...e,i],c=[...r,i],p=R(t,s);v(a,p,n)?o.push({path:a,persistedPath:c}):(Array.isArray(t)||M(t))&&o.push(...N(t,a,c,n,s))}),o}if(!M(t))return o;for(const[s,a]of Object.entries(t)){const t=x(s,a,i),c=[...e,t.key],p=[...r,s];v(c,t.value,n)?o.push({path:c,persistedPath:p}):(Array.isArray(a)||M(a))&&o.push(...N(a,c,p,n,t.node))}return o}function I(t,e,r){return r.has(L(e))?"string"==typeof t&&$(t)?t:CONFIG_UI_SECRET_SENTINEL:"string"==typeof t?t:Array.isArray(t)?t.map((t,n)=>I(t,[...e,n],r)):M(t)?Object.fromEntries(Object.entries(t).map(([t,n])=>[t,I(n,[...e,t],r)])):t}function O(t,e,r,n){if(t===CONFIG_UI_SECRET_SENTINEL)return T(e,r,n),e;if(Array.isArray(t)){const i=Array.isArray(e)?e:[];return t.map((t,e)=>O(t,i[e],[...r,e],n))}if(!M(t))return t;const i=M(e)?e:{};return Object.fromEntries(Object.entries(t).map(([t,e])=>[t,O(e,i[t],[...r,t],n)]))}function j(t,e,r,n,i,o,s){if(Array.isArray(t)){const a="array"===s?.kind?s.item:s;return void t.forEach((t,s)=>{const c=[...r,s],p=[...n,s],h=G(e,c);if(t===CONFIG_UI_SECRET_SENTINEL)return T(h,c,o),void i.push({op:"set",path:p,value:h});j(t,e,c,p,i,o,a)})}if(M(t))for(const[a,c]of Object.entries(t)){const t=x(a,c,s),p=[...r,t.key],h=[...n,a],u=G(e,p);c!==CONFIG_UI_SECRET_SENTINEL?j(c,e,p,h,i,o,t.node):(T(u,p,o),i.push({op:"set",path:h,value:u}))}}function x(t,e,r){if("object"===r?.kind){const n=u(t),i=r.fields[n];return{key:n,value:R(e,i),node:i}}return"record"===r?.kind?{key:t,value:R(e,r.value),node:r.value}:{key:t,value:e,node:void 0}}function R(t,e){return void 0===e||"leaf"===e.kind?t:c(t,e)}function T(t,e,r){if(void 0===t||t===CONFIG_UI_SECRET_SENTINEL||!v(e,t,r))throw new ConfigApplicationValidationError(new Error(`无法在原路径恢复敏感配置: ${K(e)}`))}function V(t,e=new Set,r=new Map,n=!1){if(w(t))return!0;if(4===t.length&&"agents"===t[0]&&"env"===t[1]){const i=t[2],o=t[3];if("string"!=typeof i||"string"!=typeof o)return!1;const s=F(i,o);return r.has(s)?!0===r.get(s):!(!e.has(o)&&!n)||(/(?:^|_)(?:TOKEN|API_KEY|SECRET|PASSWORD|PRIVATE_KEY)(?:_|$)/u.test(o)||o.endsWith("_WEBHOOK"))}return!1}function F(t,e){return`${t}\0${e}`}function $(t){return/^\$\{[^{}]+\}$/u.test(t)}function L(t){return JSON.stringify(t)}function G(t,e){let r=t;for(const t of e)if(Array.isArray(r)&&"number"==typeof t)r=r[t];else{if(!M(r))return;r=r[String(t)]}return r}function U(t,e){if(t===e)return t.split("\n").map(t=>({kind:"context",text:t}));const r=t.split("\n"),n=e.split("\n");let i=0;for(;i<r.length&&i<n.length&&r[i]===n[i];)i+=1;let o=0;for(;o<r.length-i&&o<n.length-i&&r[r.length-o-1]===n[n.length-o-1];)o+=1;return[...r.slice(0,i).map(t=>({kind:"context",text:t})),...r.slice(i,r.length-o).map(t=>({kind:"remove",text:t})),...n.slice(i,n.length-o).map(t=>({kind:"add",text:t})),...r.slice(r.length-o).map(t=>({kind:"context",text:t}))]}function Y(){const t={llm:n.llm,ask:n.ask,agents:n.agents};return r(h(t),{lineWidth:0})}function D(t){const e=t.split("\n").map(t=>t.match(/^\s*-\s+([^:]+):\s*(.+)$/u)).filter(t=>null!==t).map(e=>({path:e[1]??"",message:e[2]??t}));return e.length>0?e:[{path:"",message:t}]}function K(t){return t.map(String).join(" / ")}function q(t,e){if(Object.is(t,e))return!0;if(Array.isArray(t)&&Array.isArray(e))return t.length===e.length&&t.every((t,r)=>q(t,e[r]));if(!M(t)||!M(e))return!1;const r=Object.keys(t),n=Object.keys(e);return r.length===n.length&&r.every(r=>r in e&&q(t[r],e[r]))}function M(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
1
+ import{homedir as t}from"node:os";import{resolve as e}from"node:path";import{stringify as r}from"yaml";import{DEFAULT_CONFIG as n}from"./defaults.js";import{ConfigRevisionConflictError as i,YamlConfigDocumentStore as o,applyPatchesToYamlText as s}from"./document-store.js";import{CONFIG_KEY_CODEC as a,decodeFromYaml as c,encodePathToYaml as p,encodeToYaml as h,kebabToCamel as u}from"./key-codec.js";import{parseConfigDocument as f,resolveConfigPath as l,validateConfigText as d}from"./loader.js";import{detectKnownConfigMigrations as g,formatConfigMigrationError as m}from"./migration.js";import{isRollConfigSecretPath as w,isSecretConfigValue as v}from"./secret-policy.js";export const CONFIG_UI_SECRET_SENTINEL="__ROLL_UI_KEEP_EXISTING_SECRET__";export const CONFIG_ACTIVATION_KINDS=["next-command","next-chat","restart-agent","manual"];export class ConfigApplicationValidationError extends Error{code="config_validation_failed";issues;constructor(t){const e=t instanceof Error?t:new Error(String(t));super(e.message,{cause:e}),this.name="ConfigApplicationValidationError",this.issues=D(e.message)}}export class ConfigApplicationService{configPath;store;isSecretPath;constructor(r={}){this.configPath=l(r)??e(t(),"roll.config.yaml"),this.store=new o(this.configPath,Y());const n=new Set(r.secretEnvNames??[]),i=new Map((r.agentEnvFields??[]).map(t=>[F(t.agentName,t.name),t.secret]));this.isSecretPath=t=>V(t,n,i,r.redactUnknownAgentEnv??!1)}read(){const t=this.store.read();return y(t.raw,t.configPath),P(t,this.isSecretPath)}readForRepair(){const t=this.store.read(),e=f(t.raw,t.configPath),r=g(e);if(r.needsMigration)throw new ConfigApplicationValidationError(new Error(m(t.configPath,r)));return P(t,this.isSecretPath)}previewPatches(t,e){return this.preparePatches(t,e).publicPreview}savePatches(t,e){return this.commitPrepared(this.preparePatches(t,e))}previewStructured(t,e){return this.prepareStructured(t,e).publicPreview}saveStructured(t,e){return this.commitPrepared(this.prepareStructured(t,e))}previewYaml(t,e){return this.prepareYaml(t,e).publicPreview}saveYaml(t,e){return this.commitPrepared(this.prepareYaml(t,e))}replaceYamlForInit(t,e){y(t,this.configPath);const r=this.store.replaceRawForRecovery(t,e);return{snapshot:P(r,this.isSecretPath),changed:r.changed,...void 0!==r.backupPath?{backupPath:r.backupPath}:{}}}preparePatches(t,e){const r=t.map(S),n=this.store.previewPatches(r,e);return this.prepareDocumentPreview(n)}prepareStructured(t,e){if(!M(t))throw new ConfigApplicationValidationError(new Error("配置内容必须是一个 JSON/YAML object。"));const r=this.store.read();if(void 0!==e&&e!==r.revision)throw new i(e,r.revision);const n=O(t,A(r.persisted),[],this.isSecretPath),o=h(n);if(!M(o))throw new ConfigApplicationValidationError(new Error("编码后的配置必须是 object。"));const s=this.store.previewObject(o,r.revision);return this.prepareDocumentPreview(s)}prepareYaml(t,e){const r=this.store.read();if(void 0!==e&&e!==r.revision)throw new i(e,r.revision);let n,o=t;try{n=f(o,this.configPath)}catch(t){throw new ConfigApplicationValidationError(t)}const c=[];let p;j(n,A(r.persisted),[],[],c,this.isSecretPath,a),c.length>0&&(o=s(o,this.configPath,c));try{p=this.store.previewRaw(o,r.revision)}catch(t){throw new ConfigApplicationValidationError(t)}return this.prepareDocumentPreview(p)}prepareDocumentPreview(t){y(t.raw,this.configPath);const e=_(A(f(t.previousRaw,this.configPath)),A(t.persisted)),r=P(t,this.isSecretPath),n=f(t.previousRaw,this.configPath),i=P({configPath:t.configPath,existed:t.existed,raw:t.previousRaw,revision:t.previousRevision,persisted:n},this.isSecretPath);return{documentPreview:t,publicPreview:{snapshot:r,changed:t.changed,changedPaths:e,effects:planConfigActivation(e),diff:U(i.yaml,r.yaml)}}}commitPrepared(t){const e=this.store.commit(t.documentPreview,{backup:!0}),r=P(e,this.isSecretPath);return{...t.publicPreview,snapshot:r,...void 0!==e.backupPath?{backupPath:e.backupPath}:{}}}}export function planConfigActivation(t){const e=new Map;for(const r of t){const t=E(r),n=`${t.kind}:${t.agentName??""}:${t.title}`,i=e.get(n);void 0!==i?i.paths.push(r):e.set(n,{...t,paths:[r]})}return[...e.values()].map(t=>({...t,paths:t.paths}))}export function createConfigPatches(t,e,r=[]){if(M(t)&&M(e)){const n=[],i=new Set([...Object.keys(t),...Object.keys(e)]);for(const o of i)o in e?o in t?n.push(...createConfigPatches(t[o],e[o],[...r,o])):n.push({op:"set",path:[...r,o],value:e[o]}):n.push({op:"delete",path:[...r,o]});return n}if(q(t,e))return[];if(0===r.length)throw new ConfigApplicationValidationError(new Error("配置根节点必须保持为 object。"));return[{op:"set",path:r,value:e}]}function E(t){const[e,r,n]=t;return"agents"===e&&"dataDir"===r?{kind:"manual",title:"Agent 数据目录需要人工迁移",description:"保存不会搬迁旧 PID、日志或注册数据;请先停止 Agent,再人工迁移目录。",requiresConfirmation:!0}:"agents"===e&&"env"===r&&"string"==typeof n?{kind:"restart-agent",title:`重启 ${n}`,description:"运行中的 core-managed Agent 需要重启;已停止的 Agent 保持停止。",agentName:n,requiresConfirmation:!0}:"browser"===e?{kind:"restart-agent",title:"重启 browser-use-agent",description:"浏览器实例声明在 Agent 启动时注入;重启 Agent 后 Chrome 仍会在首次工具调用时懒启动。",agentName:"browser-use-agent",requiresConfirmation:!0}:"chat"===e||"runtime"===e||"skills"===e?{kind:"next-chat",title:"新会话生效",description:"当前 roll chat 会话保持原配置,新会话会重新加载。",requiresConfirmation:!1}:{kind:"next-command",title:"后续命令生效",description:"后续 Roll 命令会重新加载该配置。",requiresConfirmation:!1}}function P(t,e){const r=A(t.persisted),n=N(t.persisted,[],[],e,a),i=n.map(t=>t.path),o=new Set(i.map(L)),c=I(r,[],o);if(!M(c))throw new ConfigApplicationValidationError(new Error("脱敏后的配置必须是 object。"));const p=n.filter(t=>{const e=G(r,t.path);return!("string"==typeof e&&$(e))}).map(t=>({op:"set",path:t.persistedPath,value:CONFIG_UI_SECRET_SENTINEL})),h=0===p.length?t.raw:s(t.raw,t.configPath,p);return{configPath:t.configPath,existed:t.existed,revision:t.revision,persisted:c,yaml:h,configuredSecretPaths:i}}function y(t,e){try{d(t,e)}catch(t){throw new ConfigApplicationValidationError(t)}}function A(t){const e=c(t);if(!M(e))throw new ConfigApplicationValidationError(new Error("持久化配置必须是 object。"));return e}function S(t){const e=C(t.path);return"set"===t.op?{op:"set",path:e,value:k(t.value,t.path)}:{op:"delete",path:e}}function C(t){return p(t.map(String)).map((e,r)=>"number"==typeof t[r]?t[r]:e)}function k(t,e){if(!M(t)&&!Array.isArray(t))return t;const r=b({},e,t),n=h(r);return M(n)?G(n,C(e)):t}function b(t,e,r){let n=t;for(const[t,i]of e.entries()){const o=String(i);if(t===e.length-1){n[o]=r;break}const s={};n[o]=s,n=s}return t}function _(t,e,r=[]){const n=[],i=new Set([...Object.keys(t),...Object.keys(e)]);for(const o of i){const i=t[o],s=e[o],a=[...r,o];M(i)||M(s)?n.push(..._(M(i)?i:{},M(s)?s:{},a)):q(i,s)||n.push(a)}return n}function N(t,e,r,n,i){const o=[];if(Array.isArray(t)){const s="array"===i?.kind?i.item:i;return t.forEach((t,i)=>{const a=[...e,i],c=[...r,i],p=R(t,s);v(a,p,n)?o.push({path:a,persistedPath:c}):(Array.isArray(t)||M(t))&&o.push(...N(t,a,c,n,s))}),o}if(!M(t))return o;for(const[s,a]of Object.entries(t)){const t=x(s,a,i),c=[...e,t.key],p=[...r,s];v(c,t.value,n)?o.push({path:c,persistedPath:p}):(Array.isArray(a)||M(a))&&o.push(...N(a,c,p,n,t.node))}return o}function I(t,e,r){return r.has(L(e))?"string"==typeof t&&$(t)?t:CONFIG_UI_SECRET_SENTINEL:"string"==typeof t?t:Array.isArray(t)?t.map((t,n)=>I(t,[...e,n],r)):M(t)?Object.fromEntries(Object.entries(t).map(([t,n])=>[t,I(n,[...e,t],r)])):t}function O(t,e,r,n){if(t===CONFIG_UI_SECRET_SENTINEL)return T(e,r,n),e;if(Array.isArray(t)){const i=Array.isArray(e)?e:[];return t.map((t,e)=>O(t,i[e],[...r,e],n))}if(!M(t))return t;const i=M(e)?e:{};return Object.fromEntries(Object.entries(t).map(([t,e])=>[t,O(e,i[t],[...r,t],n)]))}function j(t,e,r,n,i,o,s){if(Array.isArray(t)){const a="array"===s?.kind?s.item:s;return void t.forEach((t,s)=>{const c=[...r,s],p=[...n,s],h=G(e,c);if(t===CONFIG_UI_SECRET_SENTINEL)return T(h,c,o),void i.push({op:"set",path:p,value:h});j(t,e,c,p,i,o,a)})}if(M(t))for(const[a,c]of Object.entries(t)){const t=x(a,c,s),p=[...r,t.key],h=[...n,a],u=G(e,p);c!==CONFIG_UI_SECRET_SENTINEL?j(c,e,p,h,i,o,t.node):(T(u,p,o),i.push({op:"set",path:h,value:u}))}}function x(t,e,r){if("object"===r?.kind){const n=u(t),i=r.fields[n];return{key:n,value:R(e,i),node:i}}return"record"===r?.kind?{key:t,value:R(e,r.value),node:r.value}:{key:t,value:e,node:void 0}}function R(t,e){return void 0===e||"leaf"===e.kind?t:c(t,e)}function T(t,e,r){if(void 0===t||t===CONFIG_UI_SECRET_SENTINEL||!v(e,t,r))throw new ConfigApplicationValidationError(new Error(`无法在原路径恢复敏感配置: ${K(e)}`))}function V(t,e=new Set,r=new Map,n=!1){if(w(t))return!0;if(4===t.length&&"agents"===t[0]&&"env"===t[1]){const i=t[2],o=t[3];if("string"!=typeof i||"string"!=typeof o)return!1;const s=F(i,o);return r.has(s)?!0===r.get(s):!(!e.has(o)&&!n)||(/(?:^|_)(?:TOKEN|API_KEY|SECRET|PASSWORD|PRIVATE_KEY)(?:_|$)/u.test(o)||o.endsWith("_WEBHOOK"))}return!1}function F(t,e){return`${t}\0${e}`}function $(t){return/^\$\{[^{}]+\}$/u.test(t)}function L(t){return JSON.stringify(t)}function G(t,e){let r=t;for(const t of e)if(Array.isArray(r)&&"number"==typeof t)r=r[t];else{if(!M(r))return;r=r[String(t)]}return r}function U(t,e){if(t===e)return t.split("\n").map(t=>({kind:"context",text:t}));const r=t.split("\n"),n=e.split("\n");let i=0;for(;i<r.length&&i<n.length&&r[i]===n[i];)i+=1;let o=0;for(;o<r.length-i&&o<n.length-i&&r[r.length-o-1]===n[n.length-o-1];)o+=1;return[...r.slice(0,i).map(t=>({kind:"context",text:t})),...r.slice(i,r.length-o).map(t=>({kind:"remove",text:t})),...n.slice(i,n.length-o).map(t=>({kind:"add",text:t})),...r.slice(r.length-o).map(t=>({kind:"context",text:t}))]}function Y(){const t={llm:n.llm,ask:n.ask,agents:n.agents};return r(h(t),{lineWidth:0})}function D(t){const e=t.split("\n").map(t=>t.match(/^\s*-\s+([^:]+):\s*(.+)$/u)).filter(t=>null!==t).map(e=>({path:e[1]??"",message:e[2]??t}));return e.length>0?e:[{path:"",message:t}]}function K(t){return t.map(String).join(" / ")}function q(t,e){if(Object.is(t,e))return!0;if(Array.isArray(t)&&Array.isArray(e))return t.length===e.length&&t.every((t,r)=>q(t,e[r]));if(!M(t)||!M(e))return!1;const r=Object.keys(t),n=Object.keys(e);return r.length===n.length&&r.every(r=>r in e&&q(t[r],e[r]))}function M(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
@@ -1,5 +1,5 @@
1
1
  import { type RollConfig } from "./schema.ts";
2
- export declare const LLM_PROVIDER_OPTIONS: readonly ["anthropic", "openai", "qwen", "deepseek"];
2
+ export declare const LLM_PROVIDER_OPTIONS: readonly ["anthropic", "openai", "qwen", "deepseek", "xai"];
3
3
  export type LlmProviderOption = (typeof LLM_PROVIDER_OPTIONS)[number];
4
4
  export declare const DEFAULT_LLM_PROVIDER: LlmProviderOption;
5
5
  export declare const DEFAULT_LLM_MODELS: {
@@ -7,6 +7,7 @@ export declare const DEFAULT_LLM_MODELS: {
7
7
  readonly openai: "gpt-5.5";
8
8
  readonly qwen: "qwen3.6-plus";
9
9
  readonly deepseek: "deepseek-v4-flash";
10
+ readonly xai: "grok-4.5";
10
11
  };
11
12
  /** 默认配置值;除上述必填 seed 外,全部由 rollConfigSchema 的默认值生成。 */
12
13
  export declare const DEFAULT_CONFIG: RollConfig;
@@ -1 +1 @@
1
- import{rollConfigSchema as e}from"./schema.js";export const LLM_PROVIDER_OPTIONS=["anthropic","openai","qwen","deepseek"];export const DEFAULT_LLM_PROVIDER="anthropic";export const DEFAULT_LLM_MODELS={anthropic:"claude-sonnet-4-6",openai:"gpt-5.5",qwen:"qwen3.6-plus",deepseek:"deepseek-v4-flash"};const o={llm:{defaultProvider:"anthropic",defaultModel:DEFAULT_LLM_MODELS.anthropic,providers:{}},ask:{},agents:{dataDir:"~/.roll-agent/agents"}};export const DEFAULT_CONFIG=e.parse(o);export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
1
+ import{rollConfigSchema as e}from"./schema.js";export const LLM_PROVIDER_OPTIONS=["anthropic","openai","qwen","deepseek","xai"];export const DEFAULT_LLM_PROVIDER="anthropic";export const DEFAULT_LLM_MODELS={anthropic:"claude-sonnet-4-6",openai:"gpt-5.5",qwen:"qwen3.6-plus",deepseek:"deepseek-v4-flash",xai:"grok-4.5"};const o={llm:{defaultProvider:"anthropic",defaultModel:DEFAULT_LLM_MODELS.anthropic,providers:{}},ask:{},agents:{dataDir:"~/.roll-agent/agents"}};export const DEFAULT_CONFIG=e.parse(o);export const CONFIG_FILE_NAMES=["roll.config.yaml","roll.config.yml"];
@@ -81,6 +81,18 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
81
81
  readonly purpose: "路由置信度低于此值时停止自动执行,并返回需要确认的结果。";
82
82
  readonly defaultBehavior: "不配置时使用内置阈值 `0.5`。";
83
83
  readonly example: "ask:\n confirm-threshold: 0.5";
84
+ }, {
85
+ readonly path: "chat";
86
+ readonly title: "Chat 终端界面";
87
+ readonly purpose: "控制 `roll chat` 交互会话使用全屏 TUI 还是基础 REPL。";
88
+ readonly defaultBehavior: "默认自动判断终端能力与运行环境。";
89
+ readonly example: "chat:\n screen-mode: auto" | "chat:\n screen-mode: fullscreen" | "chat:\n screen-mode: inline";
90
+ }, {
91
+ readonly path: "chat.screen-mode";
92
+ readonly title: "Chat 界面模式";
93
+ readonly purpose: "选择 `auto` 自动检测、`fullscreen` 强制全屏 TUI,或 `inline` 使用基础 REPL。";
94
+ readonly defaultBehavior: "默认值为 `auto`;普通交互终端使用全屏,CI、无 ANSI 能力、screen reader、Zellij 和 tmux control mode 回退基础 REPL。";
95
+ readonly example: "chat:\n screen-mode: auto" | "chat:\n screen-mode: fullscreen" | "chat:\n screen-mode: inline";
84
96
  }, {
85
97
  readonly path: "runtime";
86
98
  readonly title: "Chat 运行时";
@@ -139,13 +151,13 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
139
151
  readonly title: "工具审批";
140
152
  readonly purpose: "控制 Chat 调用工具时的默认授权方式,并允许按工具精确覆盖。";
141
153
  readonly defaultBehavior: string;
142
- readonly example: "runtime:\n approval:\n default: auto\n overrides: {}" | "runtime:\n approval:\n default: guarded\n overrides: {}" | "runtime:\n approval:\n default: deny\n overrides: {}";
154
+ readonly example: "runtime:\n approval:\n default: guarded\n overrides: {}" | "runtime:\n approval:\n default: auto\n overrides: {}" | "runtime:\n approval:\n default: deny\n overrides: {}";
143
155
  }, {
144
156
  readonly path: "runtime.approval.default";
145
157
  readonly title: "工具默认审批策略";
146
158
  readonly purpose: "决定 `roll chat` 未被单独覆盖的工具如何审批:`guarded` 按风险判断,`auto` 放行非破坏性操作,`deny` 拒绝。";
147
- readonly defaultBehavior: "默认值为 `auto`。此配置不影响 `roll ask`。" | "默认值为 `guarded`。此配置不影响 `roll ask`。" | "默认值为 `deny`。此配置不影响 `roll ask`。";
148
- readonly example: "runtime:\n approval:\n default: auto" | "runtime:\n approval:\n default: guarded" | "runtime:\n approval:\n default: deny";
159
+ readonly defaultBehavior: "默认值为 `guarded`。此配置不影响 `roll ask`。" | "默认值为 `auto`。此配置不影响 `roll ask`。" | "默认值为 `deny`。此配置不影响 `roll ask`。";
160
+ readonly example: "runtime:\n approval:\n default: guarded" | "runtime:\n approval:\n default: auto" | "runtime:\n approval:\n default: deny";
149
161
  }, {
150
162
  readonly path: "runtime.approval.overrides";
151
163
  readonly title: "单个工具审批规则";
@@ -176,8 +188,30 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
176
188
  readonly path: "runtime.compaction.strategy";
177
189
  readonly title: "上下文压缩策略";
178
190
  readonly purpose: "选择 `summarize` 生成交接摘要,或用 `truncate` 直接移除较早消息。";
179
- readonly defaultBehavior: "默认值为 `summarize`;摘要失败时自动退回 `truncate`。" | "默认值为 `truncate`;摘要失败时自动退回 `truncate`。";
191
+ readonly defaultBehavior: "默认值为 `summarize`;结构化输出无效或过长时退回 `truncate`,Provider、网络或超时错误则保留原上下文。" | "默认值为 `truncate`;结构化输出无效或过长时退回 `truncate`,Provider、网络或超时错误则保留原上下文。";
180
192
  readonly example: "runtime:\n compaction:\n strategy: summarize" | "runtime:\n compaction:\n strategy: truncate";
193
+ }, {
194
+ readonly path: "runtime.compaction.timeout-ms";
195
+ readonly title: "压缩模型超时";
196
+ readonly purpose: "限制结构化 Checkpoint 模型请求的总等待时间,不包含本地证据整理和持久化。";
197
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\` \u6BEB\u79D2\uFF1B\u8D85\u65F6\u4FDD\u6301\u539F\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u81EA\u52A8\u622A\u65AD\u3002`;
198
+ readonly example: `runtime:
199
+ compaction:
200
+ timeout-ms: ${string}`;
201
+ }, {
202
+ readonly path: "runtime.compaction.thinking-level";
203
+ readonly title: "压缩推理强度";
204
+ readonly purpose: "覆盖结构化 Checkpoint 生成的推理强度;使用 AI SDK 的统一 reasoning 语义映射到当前 Provider。";
205
+ readonly defaultBehavior: "默认不单独设置并继承 `runtime.thinking-level`;Qwen 结构化输出仍会强制关闭 thinking,不支持关闭推理的模型会在调用前报错。";
206
+ readonly example: "runtime:\n compaction:\n thinking-level: high";
207
+ }, {
208
+ readonly path: "runtime.compaction.max-output-tokens";
209
+ readonly title: "压缩输出 Token 上限";
210
+ readonly purpose: "限制结构化 Checkpoint 请求的 AI SDK 输出 token 预算。";
211
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\uFF1BProvider \u7684 reasoning token \u4E5F\u53EF\u80FD\u5360\u7528\u8BE5\u9884\u7B97\u3002`;
212
+ readonly example: `runtime:
213
+ compaction:
214
+ max-output-tokens: ${string}`;
181
215
  }, {
182
216
  readonly path: "runtime.compaction.threshold";
183
217
  readonly title: "自动压缩触发比例";