@roll-agent/core 0.19.0 → 0.20.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 (46) hide show
  1. package/dist/cli/chat/ink/app.d.ts +1 -1
  2. package/dist/cli/chat/ink/app.js +1 -1
  3. package/dist/cli/chat/ink/commands.d.ts +4 -12
  4. package/dist/cli/chat/ink/commands.js +1 -1
  5. package/dist/cli/chat/ink/history-from-messages.js +1 -1
  6. package/dist/cli/chat/ink/history-item.js +1 -1
  7. package/dist/cli/chat/ink/input-history.d.ts +2 -0
  8. package/dist/cli/chat/ink/input-history.js +1 -0
  9. package/dist/cli/chat/ink/line-buffer.d.ts +2 -0
  10. package/dist/cli/chat/ink/line-buffer.js +1 -1
  11. package/dist/cli/chat/ink/live-region.d.ts +2 -0
  12. package/dist/cli/chat/ink/live-region.js +1 -1
  13. package/dist/cli/chat/ink/reasoning-block.d.ts +4 -0
  14. package/dist/cli/chat/ink/reasoning-block.js +1 -0
  15. package/dist/cli/chat/ink/spinner.d.ts +3 -1
  16. package/dist/cli/chat/ink/spinner.js +1 -1
  17. package/dist/cli/chat/ink/state.d.ts +26 -3
  18. package/dist/cli/chat/ink/state.js +1 -1
  19. package/dist/cli/chat/ink/text-prompt.d.ts +3 -1
  20. package/dist/cli/chat/ink/text-prompt.js +1 -1
  21. package/dist/cli/chat/ink/turn-activity.d.ts +19 -0
  22. package/dist/cli/chat/ink/turn-activity.js +1 -0
  23. package/dist/cli/chat/ink/turn-status-line.d.ts +14 -0
  24. package/dist/cli/chat/ink/turn-status-line.js +1 -0
  25. package/dist/cli/chat/ink/use-session.js +1 -1
  26. package/dist/cli/commands/chat.d.ts +28 -0
  27. package/dist/cli/commands/chat.js +1 -1
  28. package/dist/cli/commands/config.js +1 -1
  29. package/dist/cli/utils/chat-renderer.js +1 -1
  30. package/dist/cli/utils/current-cli-shim.d.ts +14 -0
  31. package/dist/cli/utils/current-cli-shim.js +1 -0
  32. package/dist/cli/utils/debug-format.d.ts +4 -0
  33. package/dist/cli/utils/debug-format.js +1 -0
  34. package/dist/config/defaults.d.ts +2 -1
  35. package/dist/config/defaults.js +1 -1
  36. package/dist/config/guidance.d.ts +23 -1
  37. package/dist/config/guidance.js +1 -1
  38. package/dist/config/schema.d.ts +52 -7
  39. package/dist/config/schema.js +1 -1
  40. package/dist/llm/providers.d.ts +6 -4
  41. package/dist/llm/providers.js +1 -1
  42. package/dist/registry/install.js +1 -1
  43. package/dist/skills/invocation.d.ts +27 -0
  44. package/dist/skills/invocation.js +1 -0
  45. package/dist/types/chat.d.ts +3 -0
  46. package/package.json +3 -2
@@ -1,7 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { AgentSession } from "@roll-agent/runtime";
3
3
  import type { ThinkingLevel } from "../../../llm/providers.ts";
4
- import type { HistoryItem } from "./state.ts";
4
+ import { type HistoryItem } from "./state.ts";
5
5
  import { type SlashSkillSummary } from "./commands.ts";
6
6
  import { type BannerLine } from "../banner.ts";
7
7
  export interface ChatAppProps {
@@ -1 +1 @@
1
- import{randomUUID as i}from"node:crypto";import{createElement as t,useCallback as n,useMemo as e,useRef as o,useState as r}from"react";import{Box as s,Static as a,useInput as m}from"ink";import{useSession as l}from"./use-session.js";import{HistoryItemView as d}from"./history-item.js";import{LiveRegion as f}from"./live-region.js";import{StatusLine as p}from"./status-line.js";import{TextPrompt as h}from"./text-prompt.js";import{ConfirmSelect as c}from"./confirm-select.js";import{SlashPopup as u}from"./slash-popup.js";import{buildSkillInvocationPrompt as g,buildSkillListLines as v,filterSlashEntries as k,parseSkillInvocation as j,SLASH_COMMANDS as b}from"./commands.js";import{bannerTextLine as x}from"../banner.js";import{BannerHistoryView as C}from"./banner-view.js";import{cycleThinking as y}from"./thinking.js";function L(){return b.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(M){const{session:T,model:A,contextWindow:S,onUserSubmit:$,onExit:w}=M,H=M.availableSkills??[],{state:W,submit:D,compact:B,cancel:E,resolveConfirm:P,setDraft:R,setThinking:U,setAutoMode:q,toggleAutoMode:z,commitHistory:F}=l(T,{model:A,contextWindow:S,...M.initialHistory?{initialHistory:M.initialHistory}:{},...M.initialThinkingLevel?{initialThinkingLevel:M.initialThinkingLevel}:{},...M.onThinkingChange?{onThinkingChange:M.onThinkingChange}:{}}),G=e(()=>[...W.history],[W.history]),[I,J]=r(!1),K=o(M.animatedBanner),N=n(()=>{const i=K.current;void 0!==i&&(K.current=void 0,F({kind:"banner",id:"banner",lines:i})),J(!0)},[F]),O=I?void 0:M.animatedBanner,[Q,V]=r(0),X="idle"===W.phase&&W.draft.startsWith("/"),Y=X&&!0===W.draft.split(/\s+/).at(-1)?.startsWith("/"),Z=Y?k(W.draft,H):[],_=Math.max(Z.length-1,0),ii=Math.min(Q,_);m((i,t)=>{"busy"===W.phase&&t.escape&&!t.meta?E():t.tab&&t.shift?z():t.meta&&"."===i?U(y(W.status.thinkingLevel,1)):t.meta&&","===i&&U(y(W.status.thinkingLevel,-1))});const ti=(i,t)=>{const n=i.trim();0!==n.length?(N(),$(n),D(n,t)):R("")},ni="confirm"===W.phase&&void 0!==W.pendingConfirm?t(c,{prompt:W.pendingConfirm.prompt,args:W.pendingConfirm.args,onDecide:P}):t(h,{value:W.draft,disabled:"idle"!==W.phase,slashActive:X,slashPopupActive:Y,autoApprove:W.status.autoApprove,onChange:R,onSubmit:ti,onSlashMove:i=>{V(t=>Math.min(Math.max(Math.min(t,_)+i,0),_))},onSlashComplete:()=>{const i=Z[ii];if(i){const t=W.draft.split(/\s+/);t[t.length-1]=i.name,R(`${t.join(" ")} `),V(0)}},onSlashRun:()=>{const t=W.draft.trim().split(/\s+/,1)[0]??"",n=b.some(i=>i.name===t),e=Z[ii];if(!n&&"skill"===e?.kind){const i=W.draft.split(/\s+/);return i[i.length-1]=e.name,R(`${i.join(" ")} `),void V(0)}(t=>{N(),R("");const n=t.trim(),e=n.split(/\s+/),o=e[0]??"",r=(e[1]??"").toLowerCase(),s=W.status.thinkingLevel;if("/compact"===o)return void B();if("/think"===o)return void U("off"===r?"off":"on"===r?"off"===s?"medium":s:"off"===s?"medium":"off");if("/effort"===o)return void("low"===r||"medium"===r||"high"===r?U(r):F({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===o)return void("on"===r?q(!0):"off"===r?q(!1):z());if("/skills"===o){const t=(process.stdout.columns||80)-2,[n,...e]=v(H,t);return void F({kind:"banner",id:i(),lines:[x(n??""),...e.map(i=>x(i,{dim:!0}))]})}if("/help"===o)return void F({kind:"notice",id:i(),text:L()});if("/exit"===o)return void w();const a=j(n,H);if(a)return 0===a.prompt.length?void R(`${a.skills.map(i=>`/${i.name}`).join(" ")} `):void ti(n,g(a));F({kind:"notice",id:i(),text:`未知命令 ${o}`})})(n?W.draft:e?.name??W.draft)}});return t(s,{flexDirection:"column"},t(a,{items:G,children:i=>{const n="user"===i.kind||"assistant"===i.kind,e="tool"===i.kind||"denied"===i.kind||"cancelled"===i.kind;return t(s,{key:i.id,marginTop:n?1:0,marginLeft:e?3:1},t(d,{item:i}))}}),void 0!==O?t(s,{marginLeft:1},t(C,{lines:O,onSettled:N})):null,t(s,{marginLeft:1},t(f,{live:W.live})),t(p,{status:W.status}),X?t(u,{matches:Z,selected:ii}):null,ni)}
1
+ import{randomUUID as i}from"node:crypto";import{createElement as t,useCallback as n,useMemo as o,useRef as e,useState as r}from"react";import{Box as s,Static as a,useInput as m}from"ink";import{useSession as l}from"./use-session.js";import{CHAT_PHASES as d}from"./state.js";import{HistoryItemView as p}from"./history-item.js";import{LiveRegion as f}from"./live-region.js";import{StatusLine as c}from"./status-line.js";import{TextPrompt as u}from"./text-prompt.js";import{ConfirmSelect as h}from"./confirm-select.js";import{SlashPopup as g}from"./slash-popup.js";import{buildSkillListLines as v,filterSlashEntries as k,parseSkillInvocation as j,SLASH_COMMANDS as y}from"./commands.js";import{bannerTextLine as b}from"../banner.js";import{BannerHistoryView as x}from"./banner-view.js";import{cycleThinking as C}from"./thinking.js";import{appendInputHistory as L}from"./input-history.js";import{resolveTurnActivity as T}from"./turn-activity.js";import{TurnStatusLine as M}from"./turn-status-line.js";function A(){return y.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(H){const{session:S,model:$,contextWindow:w,onUserSubmit:W,onExit:D}=H,B=H.availableSkills??[],[E,P]=r(()=>(H.initialHistory??[]).reduce((i,t)=>"user"===t.kind?L(i,t.text):i,[])),R=n(i=>{P(t=>L(t,i))},[]),{state:U,submit:q,compact:z,cancel:F,resolveConfirm:G,setDraft:I,setThinking:J,setAutoMode:K,toggleAutoMode:N,commitHistory:O}=l(S,{model:$,contextWindow:w,...H.initialHistory?{initialHistory:H.initialHistory}:{},...H.initialThinkingLevel?{initialThinkingLevel:H.initialThinkingLevel}:{},...H.onThinkingChange?{onThinkingChange:H.onThinkingChange}:{}}),Q=o(()=>[...U.history],[U.history]),[V,X]=r(!1),Y=e(H.animatedBanner),Z=n(()=>{const i=Y.current;void 0!==i&&(Y.current=void 0,O({kind:"banner",id:"banner",lines:i})),X(!0)},[O]),_=V?void 0:H.animatedBanner,[ii,ti]=r(0),ni=U.phase===d.idle&&U.draft.startsWith("/"),oi=ni&&!0===U.draft.split(/\s+/).at(-1)?.startsWith("/"),ei=oi?k(U.draft,B):[],ri=Math.max(ei.length-1,0),si=Math.min(ii,ri);m((i,t)=>{U.phase===d.busy&&t.escape&&!t.meta?F():t.tab&&t.shift?N():t.meta&&"."===i?J(C(U.status.thinkingLevel,1)):t.meta&&","===i&&J(C(U.status.thinkingLevel,-1))});const ai=i=>{const t=i.trim();0!==t.length?(R(t),Z(),W(t),q(t)):I("")},mi=U.phase===d.confirm&&void 0!==U.pendingConfirm?t(h,{prompt:U.pendingConfirm.prompt,args:U.pendingConfirm.args,onDecide:G}):t(u,{value:U.draft,inputHistory:E,disabled:U.phase!==d.idle,...U.phase===d.cancelling?{disabledHint:"中断请求已发送,等待当前活动退出…"}:{},slashActive:ni,slashPopupActive:oi,autoApprove:U.status.autoApprove,onChange:I,onSubmit:ai,onSlashMove:i=>{ti(t=>Math.min(Math.max(Math.min(t,ri)+i,0),ri))},onSlashComplete:()=>{const i=ei[si];if(i){const t=U.draft.split(/\s+/);t[t.length-1]=i.name,I(`${t.join(" ")} `),ti(0)}},onSlashRun:t=>{const n=t.trim().split(/\s+/,1)[0]??"",o=y.some(i=>i.name===n),e=ei[si];if(!o&&"skill"===e?.kind){const i=U.draft.split(/\s+/);return i[i.length-1]=e.name,I(`${i.join(" ")} `),void ti(0)}(t=>{Z(),I("");const n=t.trim();R(n);const o=n.split(/\s+/),e=o[0]??"",r=(o[1]??"").toLowerCase(),s=U.status.thinkingLevel;if("/compact"===e)return void z();if("/think"===e)return void J("off"===r?"off":"on"===r?"off"===s?"medium":s:"off"===s?"medium":"off");if("/effort"===e)return void("low"===r||"medium"===r||"high"===r?J(r):O({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===r?K(!0):"off"===r?K(!1):N());if("/skills"===e){const t=(process.stdout.columns||80)-2,[n,...o]=v(B,t);return void O({kind:"banner",id:i(),lines:[b(n??""),...o.map(i=>b(i,{dim:!0}))]})}if("/help"===e)return void O({kind:"notice",id:i(),text:A()});if("/exit"===e)return void D();const a=j(n,B);if(a)return 0===a.prompt.length?void I(`${a.skills.map(i=>`/${i.name}`).join(" ")} `):void ai(n);O({kind:"notice",id:i(),text:`未知命令 ${e}`})})(o?t:e?.name??t)}}),li=T(U);return t(s,{flexDirection:"column"},t(a,{items:Q,children:(i,n)=>{const o=Q[n-1],e="user"===i.kind||"assistant"===i.kind||"reasoning"===i.kind||"reasoning"===o?.kind,r="tool"===i.kind||"denied"===i.kind||"cancelled"===i.kind;return t(s,{key:i.id,marginTop:e?1:0,marginLeft:r?3:1},t(p,{item:i}))}}),void 0!==_?t(s,{marginLeft:1},t(x,{lines:_,onSettled:Z})):null,t(s,{marginLeft:1},t(f,{live:U.live})),t(s,{marginTop:void 0===li?0:1},void 0===li?t(c,{status:U.status}):t(M,{activity:li})),ni?t(g,{matches:ei,selected:si}):null,mi)}
@@ -1,13 +1,12 @@
1
+ import { findSkillBySlashName, parseSkillInvocation, type SkillInvocation, type SkillInvocationSummary } from "../../../skills/invocation.ts";
2
+ export { findSkillBySlashName, parseSkillInvocation };
3
+ export type { SkillInvocation };
1
4
  export interface SlashCommand {
2
5
  readonly kind: "command";
3
6
  readonly name: string;
4
7
  readonly description: string;
5
8
  }
6
- export interface SlashSkillSummary {
7
- readonly name: string;
8
- readonly description: string;
9
- readonly source: string;
10
- }
9
+ export type SlashSkillSummary = SkillInvocationSummary;
11
10
  export interface SlashSkillEntry {
12
11
  readonly kind: "skill";
13
12
  readonly name: string;
@@ -23,12 +22,5 @@ export declare function buildSkillEntries(skills: readonly SlashSkillSummary[]):
23
22
  export declare function currentSlashToken(input: string): string;
24
23
  export declare function filterSlashEntries(input: string, skills?: readonly SlashSkillSummary[]): SlashEntry[];
25
24
  export declare function filterCommands(input: string): SlashCommand[];
26
- export declare function findSkillBySlashName(token: string, skills: readonly SlashSkillSummary[]): SlashSkillSummary | undefined;
27
- export interface SkillInvocation {
28
- readonly skills: readonly SlashSkillSummary[];
29
- readonly prompt: string;
30
- }
31
- export declare function parseSkillInvocation(input: string, skills: readonly SlashSkillSummary[]): SkillInvocation | undefined;
32
- export declare function buildSkillInvocationPrompt(invocation: SkillInvocation): string;
33
25
  export declare function buildSkillListLines(skills: readonly SlashSkillSummary[], width?: number): string[];
34
26
  export declare function formatSkillList(skills: readonly SlashSkillSummary[], width?: number): string;
@@ -1 +1 @@
1
- import{SKILL_TOOL_ID as n}from"../../../skills/library.js";import{displayWidth as e}from"./display-width.js";const t=new Intl.Segmenter(void 0,{granularity:"grapheme"}),i="…";export const SLASH_COMMANDS=[{kind:"command",name:"/compact",description:"压缩上下文,释放 token"},{kind:"command",name:"/think",description:"开关 thinking/reasoning (on | off)"},{kind:"command",name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{kind:"command",name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{kind:"command",name:"/skills",description:"列出可加载的 SKILL"},{kind:"command",name:"/help",description:"列出可用命令"},{kind:"command",name:"/exit",description:"退出对话"}];function r(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,r){if(r<=0)return"";if(e(n)<=r)return n;const o=r-e(i);let a="",s=0;for(const{segment:i}of t.segment(n)){const n=e(i);if(s+n>o)break;a+=i,s+=n}return`${a}${i}`}export function skillSlashName(n){return`/${n}`}export function buildSkillEntries(n){return[...n].filter(n=>!SLASH_COMMANDS.some(e=>e.name===skillSlashName(n.name))).sort((n,e)=>n.name.localeCompare(e.name)).map(n=>{const e=r(n.description);return{kind:"skill",name:skillSlashName(n.name),skillName:n.name,source:n.source,description:e.length>0?`${n.source} · ${e}`:n.source}})}export function currentSlashToken(n){const e=n.split(/\s+/),t=e.at(-1)??"";return t.startsWith("/")?t:e[0]??""}export function filterSlashEntries(n,e=[]){const t=currentSlashToken(n).toLowerCase();return[...SLASH_COMMANDS,...buildSkillEntries(e)].filter(n=>n.name.toLowerCase().startsWith(t))}export function filterCommands(n){const e=currentSlashToken(n).toLowerCase();return SLASH_COMMANDS.filter(n=>n.name.toLowerCase().startsWith(e))}export function findSkillBySlashName(n,e){const t=n.toLowerCase();return e.find(n=>skillSlashName(n.name).toLowerCase()===t)}export function parseSkillInvocation(n,e){let t=n.trimStart();const i=[],r=new Set;for(;;){const n=/^(\/\S+)(\s*)/.exec(t);if(!n)break;const o=findSkillBySlashName(n[1]??"",e);if(!o)break;r.has(o.name)||(i.push(o),r.add(o.name)),t=t.slice(n[0].length)}if(0!==i.length)return{skills:i,prompt:t.trim()}}export function buildSkillInvocationPrompt(e){const t=e.skills.map(n=>`- ${n.name}`).join("\n");return[`请先调用 \`${n}\` 工具加载以下 skill,并严格按它们的说明处理后续请求:`,t,"","用户请求:",e.prompt].join("\n")}const o=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const t=[...n].sort((n,e)=>n.name.localeCompare(e.name)),i=Math.min(Math.max(...t.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(t.length)} 个)· 用法: /<skill-name> 你的请求`,...t.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(i)} [${n.source}] ${r(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
1
+ import{findSkillBySlashName as n,parseSkillInvocation as e}from"../../../skills/invocation.js";import{displayWidth as t}from"./display-width.js";export{n as findSkillBySlashName,e as parseSkillInvocation};const i=new Intl.Segmenter(void 0,{granularity:"grapheme"}),r="…";export const SLASH_COMMANDS=[{kind:"command",name:"/compact",description:"压缩上下文,释放 token"},{kind:"command",name:"/think",description:"开关 thinking/reasoning (on | off)"},{kind:"command",name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{kind:"command",name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{kind:"command",name:"/skills",description:"列出可加载的 SKILL"},{kind:"command",name:"/help",description:"列出可用命令"},{kind:"command",name:"/exit",description:"退出对话"}];function o(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,e){if(e<=0)return"";if(t(n)<=e)return n;const o=e-t(r);let a="",s=0;for(const{segment:e}of i.segment(n)){const n=t(e);if(s+n>o)break;a+=e,s+=n}return`${a}${r}`}export function skillSlashName(n){return`/${n}`}export function buildSkillEntries(n){return[...n].filter(n=>!SLASH_COMMANDS.some(e=>e.name===skillSlashName(n.name))).sort((n,e)=>n.name.localeCompare(e.name)).map(n=>{const e=o(n.description);return{kind:"skill",name:skillSlashName(n.name),skillName:n.name,source:n.source,description:e.length>0?`${n.source} · ${e}`:n.source}})}export function currentSlashToken(n){const e=n.split(/\s+/),t=e.at(-1)??"";return t.startsWith("/")?t:e[0]??""}export function filterSlashEntries(n,e=[]){const t=currentSlashToken(n).toLowerCase();return[...SLASH_COMMANDS,...buildSkillEntries(e)].filter(n=>n.name.toLowerCase().startsWith(t))}export function filterCommands(n){const e=currentSlashToken(n).toLowerCase();return SLASH_COMMANDS.filter(n=>n.name.toLowerCase().startsWith(e))}const a=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const t=[...n].sort((n,e)=>n.name.localeCompare(e.name)),i=Math.min(Math.max(...t.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(t.length)} 个)· 用法: /<skill-name> 你的请求`,...t.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(i)} [${n.source}] ${o(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
@@ -1 +1 @@
1
- import{SUMMARY_ACK as t,SUMMARY_PREFIX as o}from"@roll-agent/runtime";import{formatToolInput as n}from"../../utils/tool-format.js";import{GLYPHS as e}from"../../utils/glyphs.js";const r=new Set(["error-text","error-json","execution-denied"]);function s(t){return"string"==typeof t?t:t.map(t=>"text"===t.type?t.text:"").join("")}function i(t){const o=t.indexOf("__");return o>=0?`${t.slice(0,o)}.${t.slice(o+2)}`:t}export function messagesToHistory(c,l="h"){const a=new Map;for(const t of c)if("tool"===t.role&&"string"!=typeof t.content)for(const o of t.content)if("tool-result"===o.type){const t=o.output.type;a.set(o.toolCallId,!r.has(t))}const f=[];return c.forEach((r,c)=>{const p=`${l}-${String(c)}`;if("user"===r.role){const t=s(r.content);return t.startsWith(o)?void f.push({kind:"compaction",id:p,notice:`${e.compact} 已恢复的上下文摘要`}):void(t.length>0&&f.push({kind:"user",id:p,text:t}))}if("assistant"===r.role){const o=s(r.content);if(o.startsWith(t))return;o.length>0&&f.push({kind:"assistant",id:p,text:o}),"string"!=typeof r.content&&r.content.forEach((t,o)=>{"tool-call"===t.type&&f.push({kind:"tool",id:`${p}-${String(o)}`,name:i(t.toolName),args:n(t.input),ok:a.get(t.toolCallId)??!0})})}}),f}
1
+ import{readTurnCancellationReason as t,SUMMARY_ACK as o,SUMMARY_PREFIX as n}from"@roll-agent/runtime";import{formatToolInput as e}from"../../utils/tool-format.js";import{GLYPHS as r}from"../../utils/glyphs.js";const s=new Set(["error-text","error-json","execution-denied"]);function i(t){return"string"==typeof t?t:t.map(t=>"text"===t.type?t.text:"").join("")}function c(t){const o=t.indexOf("__");return o>=0?`${t.slice(0,o)}.${t.slice(o+2)}`:t}export function messagesToHistory(l,a="h"){const f=new Map;for(const t of l)if("tool"===t.role&&"string"!=typeof t.content)for(const o of t.content)if("tool-result"===o.type){const t=o.output.type;f.set(o.toolCallId,!s.has(t))}const u=[];return l.forEach((s,l)=>{const p=`${a}-${String(l)}`;if("user"===s.role){const t=i(s.content);return t.startsWith(n)?void u.push({kind:"compaction",id:p,notice:`${r.compact} 已恢复的上下文摘要`}):void(t.length>0&&u.push({kind:"user",id:p,text:t}))}if("assistant"===s.role){const n=i(s.content);if(n.startsWith(o))return;if(n.length>0){const o=t(s);u.push(o?{kind:"turn-cancelled",id:p,text:n,reason:o}:{kind:"assistant",id:p,text:n})}"string"!=typeof s.content&&s.content.forEach((t,o)=>{"tool-call"===t.type&&u.push({kind:"tool",id:`${p}-${String(o)}`,name:c(t.toolName),args:e(t.input),ok:f.get(t.toolCallId)??!0})})}}),u}
@@ -1 +1 @@
1
- import{createElement as r}from"react";import{Box as e,Text as o}from"ink";import{parseThinking as t}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as l}from"./tool-label.js";import{BannerLinesView as i}from"./banner-view.js";const a=["已取消执行","策略拒绝执行"];function c(r){const e=r.trimStart();return a.some(r=>e.startsWith(r))}export function HistoryItemView({item:a}){switch(a.kind){case"banner":return r(i,{lines:a.lines});case"user":return r(e,null,r(o,{color:"cyan",bold:!0},"▌ "),r(o,{color:"cyan"},a.text));case"assistant":return c(a.text)?r(e,null,r(o,{dimColor:!0},"⊘ "),r(o,{dimColor:!0},a.text.trim())):r(e,{flexDirection:"column"},...t(a.text).map((e,t)=>e.thinking?r(o,{key:String(t),dimColor:!0},e.text):r(n,{key:String(t),text:e.text})));case"tool":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(o,null,r(o,a.ok?{color:"green"}:{color:"red"},a.ok?"✓ ":"✗ "),r(l,{name:a.name}),e.length>0?r(o,{dimColor:!0},e):null)}case"denied":return r(o,{dimColor:!0},`⊘ ${a.name} ${a.label}`);case"cancelled":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(o,{color:"yellow"},"■ ",r(l,{name:a.name}),e.length>0?r(o,{dimColor:!0},e):null,r(o,{dimColor:!0}," 已中断"))}case"compaction":return r(o,{dimColor:!0},a.notice);case"notice":return r(e,null,r(o,{color:"yellow"},"⚠ "),r(o,{color:"yellow"},a.text));case"error":return r(e,null,r(o,{color:"red"},"✗ "),r(o,{color:"red"},a.message));default:return r(o,null,"")}}
1
+ import{createElement as r}from"react";import{Box as e,Text as t}from"ink";import{parseThinking as o}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as l}from"./tool-label.js";import{BannerLinesView as i}from"./banner-view.js";import{ReasoningBlock as s}from"./reasoning-block.js";const a=["已取消执行","策略拒绝执行"];function c(r){const e=r.trimStart();return a.some(r=>e.startsWith(r))}export function HistoryItemView({item:a}){switch(a.kind){case"banner":return r(i,{lines:a.lines});case"user":return r(e,null,r(t,{color:"cyan",bold:!0},"▌ "),r(t,{color:"cyan"},a.text));case"assistant":return c(a.text)?r(e,null,r(t,{dimColor:!0},"⊘ "),r(t,{dimColor:!0},a.text.trim())):r(e,{flexDirection:"column"},...o(a.text).map((e,o)=>e.thinking?r(t,{key:String(o),dimColor:!0},e.text):r(n,{key:String(o),text:e.text})));case"reasoning":return r(s,{text:a.text});case"tool":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(t,null,r(t,a.ok?{color:"green"}:{color:"red"},a.ok?"✓ ":"✗ "),r(l,{name:a.name}),e.length>0?r(t,{dimColor:!0},e):null)}case"denied":return r(t,{dimColor:!0},`⊘ ${a.name} ${a.label}`);case"cancelled":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(t,{color:"yellow"},"■ ",r(l,{name:a.name}),e.length>0?r(t,{dimColor:!0},e):null,r(t,{dimColor:!0}," 已中断"))}case"compaction":return r(t,{dimColor:!0},a.notice);case"turn-cancelled":{const o="user"===a.reason?{prefix:"■",textProps:{dimColor:!0}}:"timeout"===a.reason?{prefix:"⚠",textProps:{color:"yellow"}}:{prefix:"✗",textProps:{color:"red"}};return r(e,{alignItems:"flex-start"},r(e,{width:2,flexShrink:0},r(t,o.textProps,o.prefix)),r(e,{flexGrow:1,flexShrink:1},r(t,{...o.textProps,wrap:"wrap"},a.text)))}case"notice":return r(e,null,r(t,{color:"yellow"},"⚠ "),r(t,{color:"yellow"},a.text));case"error":return r(e,null,r(t,{color:"red"},"✗ "),r(t,{color:"red"},a.message));default:return r(t,null,"")}}
@@ -0,0 +1,2 @@
1
+ export declare const INPUT_HISTORY_LIMIT = 50;
2
+ export declare function appendInputHistory(history: readonly string[], raw: string): readonly string[];
@@ -0,0 +1 @@
1
+ export const INPUT_HISTORY_LIMIT=50;export function appendInputHistory(t,n){const r=n.trim();if(0===r.length)return t;return[...t.filter(t=>t!==r).slice(-49),r]}
@@ -19,3 +19,5 @@ export declare function moveToLineStart(state: LineBufferState): LineBufferState
19
19
  export declare function moveToLineEnd(state: LineBufferState): LineBufferState;
20
20
  export declare function moveUp(state: LineBufferState): LineBufferState;
21
21
  export declare function moveDown(state: LineBufferState): LineBufferState;
22
+ export declare function moveVisualUp(state: LineBufferState, columns: number): LineBufferState;
23
+ export declare function moveVisualDown(state: LineBufferState, columns: number): LineBufferState;
@@ -1 +1 @@
1
- import{displayWidth as r}from"./display-width.js";const e=new Intl.Segmenter(void 0,{granularity:"grapheme"}),n=new Intl.Segmenter(void 0,{granularity:"word"});function t(r,e){return 0===e?0:r.lastIndexOf("\n",e-1)+1}function o(r,e){const n=t(r,e),o=r.indexOf("\n",e);return{start:n,end:-1===o?r.length:o}}function u(r){const[n]=e.segment(r);return n?.segment}function l(r,e){if(e>=r.length)return r.length;const n=u(r.slice(e));return void 0===n?r.length:e+n.length}function c(r,n){if(n<=0)return 0;const o=t(r,n);if(n===o)return o-1;let u=o;for(const t of e.segment(r.slice(o,n)))u=o+t.index;return u}function a(r,n,t){const o=Math.max(0,Math.min(n,r.length));if(0===o)return 0;if(o===r.length)return r.length;const u=e.segment(r).containing(o);return void 0===u||u.index===o?o:"backward"===t?u.index:u.index+u.segment.length}function i(r,e,n,t=""){const o=r.value.slice(0,e)+t+r.value.slice(n);return{value:o,cursor:a(o,e+t.length,"forward"),goalColumn:void 0}}function s(r){return!0===r.isWordLike}function v(r,e){let t=0;for(const o of n.segment(r.slice(0,e)))s(o)&&(t=o.index);return t}function f(r,e){for(const t of n.segment(r.slice(e)))if(s(t))return e+t.index+t.segment.length;return r.length}function g(e,n){return r(e.slice(t(e,n),n))}function d(n,t,o,u){let l=t,c=0;for(const a of e.segment(n.slice(t,o))){const e=r(a.segment);if(c+e>u)break;c+=e,l=t+a.index+a.segment.length}return l}function m(r){return void 0===r.goalColumn?r:{value:r.value,cursor:r.cursor,goalColumn:void 0}}export function createLineBuffer(r,e){return{value:r,cursor:a(r,Math.max(0,Math.min(e??r.length,r.length)),"backward"),goalColumn:void 0}}export function graphemeAt(r,e){return e<0||e>=r.length?"":u(r.slice(e))??""}export function insertText(r,e){return 0===e.length?m(r):i(r,r.cursor,r.cursor,e)}export function deleteBackward(r){if(0===r.cursor)return m(r);return i(r,c(r.value,r.cursor),r.cursor)}export function deleteForward(r){if(r.cursor>=r.value.length)return m(r);const e=l(r.value,r.cursor);return i(r,r.cursor,e)}export function deleteWordBackward(r){if(0===r.cursor)return m(r);return i(r,v(r.value,r.cursor),r.cursor)}export function killToLineStart(r){const{start:e}=o(r.value,r.cursor);return e===r.cursor?m(r):i(r,e,r.cursor)}export function killToLineEnd(r){const{end:e}=o(r.value,r.cursor);return r.cursor===e?e===r.value.length?m(r):i(r,r.cursor,e+1):i(r,r.cursor,e)}export function moveLeft(r){return 0===r.cursor?m(r):{value:r.value,cursor:c(r.value,r.cursor),goalColumn:void 0}}export function moveRight(r){return r.cursor>=r.value.length?m(r):{value:r.value,cursor:l(r.value,r.cursor),goalColumn:void 0}}export function moveWordLeft(r){return{value:r.value,cursor:v(r.value,r.cursor),goalColumn:void 0}}export function moveWordRight(r){return{value:r.value,cursor:f(r.value,r.cursor),goalColumn:void 0}}export function moveToLineStart(r){return{value:r.value,cursor:o(r.value,r.cursor).start,goalColumn:void 0}}export function moveToLineEnd(r){return{value:r.value,cursor:o(r.value,r.cursor).end,goalColumn:void 0}}export function moveUp(r){const{start:e}=o(r.value,r.cursor);if(0===e)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e-1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveDown(r){const{end:e}=o(r.value,r.cursor);if(e===r.value.length)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e+1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}
1
+ import{displayWidth as r}from"./display-width.js";const e=new Intl.Segmenter(void 0,{granularity:"grapheme"}),n=new Intl.Segmenter(void 0,{granularity:"word"});function t(r,e){return 0===e?0:r.lastIndexOf("\n",e-1)+1}function o(r,e){const n=t(r,e),o=r.indexOf("\n",e);return{start:n,end:-1===o?r.length:o}}function u(r){const[n]=e.segment(r);return n?.segment}function l(r,e){if(e>=r.length)return r.length;const n=u(r.slice(e));return void 0===n?r.length:e+n.length}function s(r,n){if(n<=0)return 0;const o=t(r,n);if(n===o)return o-1;let u=o;for(const t of e.segment(r.slice(o,n)))u=o+t.index;return u}function c(r,n,t){const o=Math.max(0,Math.min(n,r.length));if(0===o)return 0;if(o===r.length)return r.length;const u=e.segment(r).containing(o);return void 0===u||u.index===o?o:"backward"===t?u.index:u.index+u.segment.length}function a(r,e,n,t=""){const o=r.value.slice(0,e)+t+r.value.slice(n);return{value:o,cursor:c(o,e+t.length,"forward"),goalColumn:void 0}}function i(r){return!0===r.isWordLike}function f(r,e){let t=0;for(const o of n.segment(r.slice(0,e)))i(o)&&(t=o.index);return t}function v(r,e){for(const t of n.segment(r.slice(e)))if(i(t))return e+t.index+t.segment.length;return r.length}function g(e,n){return r(e.slice(t(e,n),n))}function d(n,t,o,u){let l=t,s=0;for(const c of e.segment(n.slice(t,o))){const e=r(c.segment);if(s+e>u)break;s+=e,l=t+c.index+c.segment.length}return l}function m(n,t,o){const u=Math.max(1,Math.floor(t)),l=[];let s=0;for(;s<=n.length;){const t=n.indexOf("\n",s),c=-1===t?n.length:t;let a=s,i=0;for(const t of e.segment(n.slice(s,c))){const e=s+t.index,n=r(t.segment);i>0&&i+n>u&&(l.push({start:a,end:e,softWrap:!0}),a=e,i=0),i+=n}const f=o===c&&i>=u;if(l.push({start:a,end:c,softWrap:f}),f&&l.push({start:c,end:c,softWrap:!1}),-1===t)break;s=t+1}return l}function h(r,e){for(const[n,t]of r.entries())if(e<t.end||e===t.end&&!t.softWrap)return n;return Math.max(0,r.length-1)}function x(e,n,t){const o=m(e.value,t,e.cursor),u=h(o,e.cursor),l=o[u],c=o[u+n];if(void 0===l||void 0===c)return e;const a=e.goalColumn??r(e.value.slice(l.start,e.cursor));let i=d(e.value,c.start,c.end,a);return c.softWrap&&i===c.end&&c.start<c.end&&(i=s(e.value,c.end)),{value:e.value,cursor:i,goalColumn:a}}function p(r){return void 0===r.goalColumn?r:{value:r.value,cursor:r.cursor,goalColumn:void 0}}export function createLineBuffer(r,e){return{value:r,cursor:c(r,Math.max(0,Math.min(e??r.length,r.length)),"backward"),goalColumn:void 0}}export function graphemeAt(r,e){return e<0||e>=r.length?"":u(r.slice(e))??""}export function insertText(r,e){return 0===e.length?p(r):a(r,r.cursor,r.cursor,e)}export function deleteBackward(r){if(0===r.cursor)return p(r);return a(r,s(r.value,r.cursor),r.cursor)}export function deleteForward(r){if(r.cursor>=r.value.length)return p(r);const e=l(r.value,r.cursor);return a(r,r.cursor,e)}export function deleteWordBackward(r){if(0===r.cursor)return p(r);return a(r,f(r.value,r.cursor),r.cursor)}export function killToLineStart(r){const{start:e}=o(r.value,r.cursor);return e===r.cursor?p(r):a(r,e,r.cursor)}export function killToLineEnd(r){const{end:e}=o(r.value,r.cursor);return r.cursor===e?e===r.value.length?p(r):a(r,r.cursor,e+1):a(r,r.cursor,e)}export function moveLeft(r){return 0===r.cursor?p(r):{value:r.value,cursor:s(r.value,r.cursor),goalColumn:void 0}}export function moveRight(r){return r.cursor>=r.value.length?p(r):{value:r.value,cursor:l(r.value,r.cursor),goalColumn:void 0}}export function moveWordLeft(r){return{value:r.value,cursor:f(r.value,r.cursor),goalColumn:void 0}}export function moveWordRight(r){return{value:r.value,cursor:v(r.value,r.cursor),goalColumn:void 0}}export function moveToLineStart(r){return{value:r.value,cursor:o(r.value,r.cursor).start,goalColumn:void 0}}export function moveToLineEnd(r){return{value:r.value,cursor:o(r.value,r.cursor).end,goalColumn:void 0}}export function moveUp(r){const{start:e}=o(r.value,r.cursor);if(0===e)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e-1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveDown(r){const{end:e}=o(r.value,r.cursor);if(e===r.value.length)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e+1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveVisualUp(r,e){return x(r,-1,e)}export function moveVisualDown(r,e){return x(r,1,e)}
@@ -1,5 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { LiveState } from "./state.ts";
3
+ /** Live 推理只保留最近几行做滚动预览;完整文本在 reasoning 结束时照常落入 history。 */
4
+ export declare function reasoningTail(text: string): string;
3
5
  export declare function LiveRegion({ live }: {
4
6
  live: LiveState;
5
7
  }): ReactElement;
@@ -1 +1 @@
1
- import{createElement as l}from"react";import{Box as n,Text as t}from"ink";import{Spinner as i}from"./spinner.js";import{ThinkingText as o}from"./thinking-text.js";import{ToolLabel as e}from"./tool-label.js";const r=3;function m(l){return void 0===l?[]:l.split("\n").map(l=>l.trimEnd()).filter(l=>l.length>0).slice(-3)}export function LiveRegion({live:r}){return l(n,{flexDirection:"column"},r.streamingText.length>0?l(n,{marginTop:1},l(o,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText})):null,r.thinking?l(n,{marginTop:1},l(i,null),l(t,{dimColor:!0}," 思考中…")):null,...r.activeTools.map(o=>l(n,{key:o.toolCallId,marginLeft:2,flexDirection:"column"},l(n,null,l(i,null),l(t,null," "),l(e,{name:o.name}),o.args.length>0?l(t,{dimColor:!0},` ${o.args}`):null),...m(o.outputTail).map((n,i)=>l(t,{key:`${o.toolCallId}-tail-${String(i)}`,dimColor:!0},` ${n}`)))),r.compacting?l(n,null,l(i,null),l(t,{dimColor:!0}," 压缩上下文中…")):null)}
1
+ import{createElement as n}from"react";import{Box as t,Text as e}from"ink";import{ThinkingText as i}from"./thinking-text.js";import{ToolLabel as o}from"./tool-label.js";import{ReasoningBlock as l}from"./reasoning-block.js";const r=3,a=3;export function reasoningTail(n){const t=n.split("\n").map(n=>n.trimEnd()).filter(n=>n.length>0),e=t.slice(-3);return t.length>e.length?["…",...e].join("\n"):e.join("\n")}function m(n){return void 0===n?[]:n.split("\n").map(n=>n.trimEnd()).filter(n=>n.length>0).slice(-3)}export function LiveRegion({live:r}){const a=reasoningTail(r.reasoningText);return n(t,{flexDirection:"column"},a.length>0?n(t,{marginTop:1},n(l,{text:a})):null,r.streamingText.length>0?n(t,{marginTop:1},n(i,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText})):null,...r.activeTools.map(i=>n(t,{key:i.toolCallId,marginTop:1,marginLeft:2,flexDirection:"column"},n(t,null,n(e,{color:"cyan"},"· "),n(o,{name:i.name}),i.args.length>0?n(e,{dimColor:!0},` ${i.args}`):null),...m(i.outputTail).map((t,o)=>n(e,{key:`${i.toolCallId}-tail-${String(o)}`,dimColor:!0},` ${t}`)))))}
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function ReasoningBlock({ text }: {
3
+ text: string;
4
+ }): ReactElement;
@@ -0,0 +1 @@
1
+ import{createElement as o}from"react";import{Box as r,Text as e}from"ink";export function ReasoningBlock({text:t}){return o(r,{flexDirection:"column"},o(r,null,o(e,{color:"magenta"},"◇"),o(e,{dimColor:!0}," 推理过程")),o(r,{borderStyle:"single",borderColor:"gray",borderTop:!1,borderRight:!1,borderBottom:!1,paddingLeft:1},o(e,{dimColor:!0},t)))}
@@ -1,2 +1,4 @@
1
1
  import type { ReactElement } from "react";
2
- export declare function Spinner(): ReactElement;
2
+ export declare function Spinner({ color }: {
3
+ readonly color?: string;
4
+ }): ReactElement;
@@ -1 +1 @@
1
- import{createElement as r,useEffect as t,useState as e}from"react";import{Text as n}from"ink";import o from"is-unicode-supported";const c=o()?["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]:["-","\\","|","/"],i=80;export function Spinner(){const[o,a]=e(0);return t(()=>{const r=setInterval(()=>{a(r=>(r+1)%c.length)},i);return()=>{clearInterval(r)}},[]),r(n,{color:"cyan"},c[o]??c[0]??"-")}
1
+ import{createElement as r,useEffect as t,useState as o}from"react";import{Text as e}from"ink";import n from"is-unicode-supported";const c=n()?["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]:["-","\\","|","/"],i=80;export function Spinner({color:n="cyan"}){const[a,l]=o(0);return t(()=>{const r=setInterval(()=>{l(r=>(r+1)%c.length)},i);return()=>{clearInterval(r)}},[]),r(e,{color:n},c[a]??c[0]??"-")}
@@ -1,4 +1,4 @@
1
- import type { SessionEvent, SessionTokenUsage } from "@roll-agent/runtime";
1
+ import { type SessionEvent, type SessionTokenUsage } from "@roll-agent/runtime";
2
2
  import type { ThinkingLevel } from "../../../llm/providers.ts";
3
3
  import type { BannerLine } from "../banner.ts";
4
4
  export interface ToolRowState {
@@ -7,6 +7,9 @@ export interface ToolRowState {
7
7
  readonly args: string;
8
8
  readonly outputTail?: string;
9
9
  }
10
+ type TurnCancelledReason = Extract<SessionEvent, {
11
+ type: "turn-cancelled";
12
+ }>["reason"];
10
13
  export type HistoryItem = {
11
14
  readonly kind: "banner";
12
15
  readonly id: string;
@@ -19,6 +22,10 @@ export type HistoryItem = {
19
22
  readonly kind: "assistant";
20
23
  readonly id: string;
21
24
  readonly text: string;
25
+ } | {
26
+ readonly kind: "reasoning";
27
+ readonly id: string;
28
+ readonly text: string;
22
29
  } | {
23
30
  readonly kind: "tool";
24
31
  readonly id: string;
@@ -39,6 +46,11 @@ export type HistoryItem = {
39
46
  readonly kind: "compaction";
40
47
  readonly id: string;
41
48
  readonly notice: string;
49
+ } | {
50
+ readonly kind: "turn-cancelled";
51
+ readonly id: string;
52
+ readonly text: string;
53
+ readonly reason: TurnCancelledReason;
42
54
  } | {
43
55
  readonly kind: "notice";
44
56
  readonly id: string;
@@ -50,7 +62,9 @@ export type HistoryItem = {
50
62
  };
51
63
  export interface LiveState {
52
64
  readonly streamingText: string;
53
- readonly thinking: boolean;
65
+ readonly reasoningId: string | undefined;
66
+ readonly reasoningText: string;
67
+ readonly reasoningActive: boolean;
54
68
  readonly thinkTagOpen: boolean;
55
69
  readonly activeTools: readonly ToolRowState[];
56
70
  readonly compacting: boolean;
@@ -66,7 +80,13 @@ export interface StatusState {
66
80
  readonly thinkingLevel: ThinkingLevel;
67
81
  readonly autoApprove: boolean;
68
82
  }
69
- export type ChatPhase = "idle" | "busy" | "confirm";
83
+ export declare const CHAT_PHASES: {
84
+ readonly idle: "idle";
85
+ readonly busy: "busy";
86
+ readonly confirm: "confirm";
87
+ readonly cancelling: "cancelling";
88
+ };
89
+ export type ChatPhase = (typeof CHAT_PHASES)[keyof typeof CHAT_PHASES];
70
90
  export interface PendingConfirm {
71
91
  readonly approvalId: string;
72
92
  readonly prompt: string;
@@ -104,6 +124,8 @@ export type ChatUiAction = {
104
124
  readonly event: SessionEvent;
105
125
  } | {
106
126
  readonly type: "confirm-resolved";
127
+ } | {
128
+ readonly type: "cancel-requested";
107
129
  } | {
108
130
  readonly type: "turn-end";
109
131
  };
@@ -117,3 +139,4 @@ export declare function buildConfirmPrompt(event: Extract<SessionEvent, {
117
139
  }>): string;
118
140
  export declare function denialLabel(output: unknown): string | undefined;
119
141
  export declare function chatReducer(state: ChatUiState, action: ChatUiAction): ChatUiState;
142
+ export {};
@@ -1 +1 @@
1
- import{formatToolInput as t,formatApprovalDetails as e}from"../../utils/tool-format.js";import{GLYPHS as i}from"../../utils/glyphs.js";import{endsInsideThink as n}from"./thinking-text.js";const o=2e3,r={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function s(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,i){return{history:i?.history??[],draft:"",live:r,status:{model:t,contextWindow:e,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,outputTokensPerSecond:void 0,thinkingLevel:i?.thinkingLevel??"medium",autoApprove:!1},phase:"idle",pendingConfirm:void 0}}export function buildConfirmPrompt(t){const e=t.reason?`(${t.reason})`:"";return`执行 ${t.agentName}.${t.toolName}${e}?`}function a(t){const e="auto"===t.reason?"自动压缩":"手动压缩",n=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`${i.compact} ${e}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${n}`:`${i.compact} ${e}:无需压缩`}const l=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function u(t){return"string"==typeof t?t:"object"==typeof t&&null!==t&&"output"in t&&"string"==typeof t.output?t.output:void 0}export function denialLabel(t){const e=u(t);if(void 0!==e)return l.find(([t])=>e.startsWith(t))?.[1]}function c(t,e,i){const n=t.live.activeTools.find(t=>t.toolCallId===i.toolCallId),o=n?.name??`${i.agentName}.${i.toolName}`,r=n?.args??"",s=i.isError?denialLabel(i.output):void 0,a=void 0!==s?{kind:"denied",id:e,name:o,label:s}:{kind:"tool",id:e,name:o,args:r,ok:!i.isError};return{...t,history:[...t.history,a],live:{...t.live,activeTools:t.live.activeTools.filter(t=>t.toolCallId!==i.toolCallId)}}}function d(i,l,u){switch(u.type){case"message-start":return{...i,live:{...i.live,thinking:!0}};case"text-delta":return{...i,live:{...i.live,thinking:!1,producedOutput:!0,streamingText:i.live.streamingText+u.delta}};case"tool-call":{const e=i.live.streamingText.length>0?[{kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}]:[];return{...i,history:[...i.history,...e],live:{...i.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:n(i.live.streamingText,i.live.thinkTagOpen),activeTools:[...i.live.activeTools,{toolCallId:u.toolCallId,name:`${u.agentName}.${u.toolName}`,args:t(u.input)}]}}}case"tool-output-delta":{const t=i.live.activeTools.map(t=>{if(t.toolCallId!==u.toolCallId)return t;const e=`${t.outputTail??""}${u.delta}`,i=e.length>o?e.slice(e.length-o):e;return{...t,outputTail:i}});return{...i,live:{...i.live,activeTools:t}}}case"tool-result":return c(i,l,u);case"confirmation-required":return{...i,phase:"confirm",pendingConfirm:{approvalId:u.approvalId,prompt:buildConfirmPrompt(u),args:e(u.input)}};case"compaction-start":return{...i,live:{...i.live,compacting:!0}};case"context-compacted":return{...i,live:{...i.live,compacting:!1},history:[...i.history,{kind:"compaction",id:l,notice:a(u)}]};case"message-finish":{const t=[];return i.live.streamingText.length>0?t.push({kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}):!i.live.producedOutput&&(u.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:l,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),u.stoppedAtStepLimit&&t.push({kind:"notice",id:`${l}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...i,history:[...i.history,...t],live:{...r},status:{...i.status,turnUsage:u.totalUsage,sessionUsage:u.sessionUsage,contextInputTokens:u.contextInputTokens,outputTokensPerSecond:u.outputTokensPerSecond}}}case"turn-cancelled":{const t=i.live.activeTools.map((t,e)=>({kind:"cancelled",id:`${l}-tool-${String(e)}`,name:t.name,args:t.args}));return{...i,history:[...i.history,...t,{kind:"notice",id:l,text:u.message}],live:{...r}}}case"error":return{...i,history:[...i.history,{kind:"error",id:l,message:u.message}],live:{...r}};default:return i}}export function chatReducer(t,e){switch(e.type){case"submit-user":return{...t,history:[...t.history,{kind:"user",id:e.id,text:e.text}],draft:"",live:{...r},phase:"busy",pendingConfirm:void 0};case"set-draft":return{...t,draft:e.value};case"set-thinking":return{...t,status:{...t.status,thinkingLevel:e.level}};case"set-auto":return{...t,status:{...t.status,autoApprove:e.value}};case"commit-history":return{...t,history:[...t.history,e.item],draft:""};case"start-compaction":return{...t,live:{...r},phase:"busy",pendingConfirm:void 0};case"session-event":return d(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...r}};default:return t}}
1
+ import{TOOL_OUTCOME_KINDS as e}from"@roll-agent/runtime";import{formatToolInput as t,formatApprovalDetails as n}from"../../utils/tool-format.js";import{GLYPHS as i}from"../../utils/glyphs.js";import{endsInsideThink as o}from"./thinking-text.js";const r=2e3;export const CHAT_PHASES={idle:"idle",busy:"busy",confirm:"confirm",cancelling:"cancelling"};const s={streamingText:"",reasoningId:void 0,reasoningText:"",reasoningActive:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function a(e,t){return t?`<think>${e}`:e}export function createInitialState(e,t,n){return{history:n?.history??[],draft:"",live:s,status:{model:e,contextWindow:t,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,outputTokensPerSecond:void 0,thinkingLevel:n?.thinkingLevel??"medium",autoApprove:!1},phase:"idle",pendingConfirm:void 0}}export function buildConfirmPrompt(e){const t=e.reason?`(${e.reason})`:"";return`执行 ${e.agentName}.${e.toolName}${t}?`}function l(e){const t="auto"===e.reason?"自动压缩":"手动压缩",n=e.truncatedTools?`,精简 ${String(e.truncatedTools)} 个工具结果`:"",o=e.checkpointGeneration?`,checkpoint #${String(e.checkpointGeneration)}${e.checkpointSummaryStatus&&"valid"!==e.checkpointSummaryStatus?`(${e.checkpointSummaryStatus})`:""}`:"";return 0!==e.removed||e.truncatedTools?`${i.compact} ${t}(${e.strategy}):移除 ${String(e.removed)} 条 → 保留 ${String(e.kept)} 条${n}${o}`:`${i.compact} ${t}:无需压缩`}const c=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function u(e){return"string"==typeof e?e:"object"==typeof e&&null!==e&&"output"in e&&"string"==typeof e.output?e.output:void 0}export function denialLabel(e){const t=u(e);if(void 0!==t)return c.find(([e])=>t.startsWith(e))?.[1]}function d(t,n,i){const o=t.live.activeTools.find(e=>e.toolCallId===i.toolCallId),r=o?.name??`${i.agentName}.${i.toolName}`,s=o?.args??"",a=i.outcome?.kind===e.userRejected?"已取消":i.outcome?.kind===e.policyDenied?"策略拒绝":void 0===i.outcome&&i.isError?denialLabel(i.output):void 0,l=void 0!==a?{kind:"denied",id:n,name:r,label:a}:{kind:"tool",id:n,name:r,args:s,ok:!i.isError};return{...t,history:[...t.history,l],live:{...t.live,activeTools:t.live.activeTools.filter(e=>e.toolCallId!==i.toolCallId)}}}function g(e,t){return 0===e.live.streamingText.length?e:{...e,history:[...e.history,{kind:"assistant",id:t,text:a(e.live.streamingText,e.live.thinkTagOpen)}],live:{...e.live,streamingText:"",thinkTagOpen:o(e.live.streamingText,e.live.thinkTagOpen)}}}function p(e,t){const n=e.live.reasoningText.trim().length>0?[...e.history,{kind:"reasoning",id:t,text:e.live.reasoningText}]:e.history;return{...e,history:n,live:{...e.live,reasoningId:void 0,reasoningText:"",reasoningActive:!1}}}function v(e,t,n){const i=g(p(e,`${t}-previous-reasoning`),t);return{...i,live:{...i.live,reasoningId:n,reasoningText:"",reasoningActive:!0}}}function m(e,i,o){switch(o.type){case"message-start":default:return e;case"reasoning-start":return v(e,i,o.reasoningId);case"reasoning-delta":{const t=e.live.reasoningId===o.reasoningId?e:v(e,i,o.reasoningId);return{...t,live:{...t.live,reasoningText:t.live.reasoningText+o.delta,reasoningActive:!0}}}case"reasoning-end":return e.live.reasoningId===o.reasoningId?p(e,`${i}-reasoning`):e;case"text-delta":{const t=p(e,`${i}-reasoning`);return{...t,live:{...t.live,producedOutput:!0,streamingText:t.live.streamingText+o.delta}}}case"tool-call":{const n=g(p(e,`${i}-reasoning`),i);return{...n,live:{...n.live,producedOutput:!0,activeTools:[...n.live.activeTools,{toolCallId:o.toolCallId,name:`${o.agentName}.${o.toolName}`,args:t(o.input)}]}}}case"tool-output-delta":{const t=e.live.activeTools.map(e=>{if(e.toolCallId!==o.toolCallId)return e;const t=`${e.outputTail??""}${o.delta}`,n=t.length>r?t.slice(t.length-r):t;return{...e,outputTail:n}});return{...e,live:{...e.live,activeTools:t}}}case"tool-result":return d(e,i,o);case"confirmation-required":return{...e,phase:"confirm",pendingConfirm:{approvalId:o.approvalId,prompt:buildConfirmPrompt(o),args:n(o.input)}};case"compaction-start":return{...e,live:{...e.live,compacting:!0}};case"context-compacted":return{...e,live:{...e.live,compacting:!1},history:[...e.history,{kind:"compaction",id:i,notice:l(o)}]};case"message-finish":{const t=p(e,`${i}-reasoning`),n=[];return t.live.streamingText.length>0?n.push({kind:"assistant",id:i,text:a(t.live.streamingText,t.live.thinkTagOpen)}):!t.live.producedOutput&&(o.totalUsage?.outputTokens??0)>0&&n.push({kind:"notice",id:i,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),o.stoppedAtStepLimit&&n.push({kind:"notice",id:`${i}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...t,history:[...t.history,...n],live:{...s},status:{...t.status,turnUsage:o.totalUsage,sessionUsage:o.sessionUsage,contextInputTokens:o.contextInputTokens,outputTokensPerSecond:o.outputTokensPerSecond}}}case"turn-cancelled":{const t=e.live.activeTools.map((e,t)=>({kind:"cancelled",id:`${i}-tool-${String(t)}`,name:e.name,args:e.args}));return{...e,history:[...e.history,...t,{kind:"turn-cancelled",id:i,text:o.message,reason:o.reason}],live:{...s}}}case"error":return{...e,history:[...e.history,{kind:"error",id:i,message:o.message}],live:{...s}}}}export function chatReducer(e,t){switch(t.type){case"submit-user":return{...e,history:[...e.history,{kind:"user",id:t.id,text:t.text}],draft:"",live:{...s},phase:"busy",pendingConfirm:void 0};case"set-draft":return{...e,draft:t.value};case"set-thinking":return{...e,status:{...e.status,thinkingLevel:t.level}};case"set-auto":return{...e,status:{...e.status,autoApprove:t.value}};case"commit-history":return{...e,history:[...e.history,t.item],draft:""};case"start-compaction":return{...e,live:{...s,compacting:!0},phase:"busy",pendingConfirm:void 0};case"session-event":return m(e,t.id,t.event);case"confirm-resolved":return{...e,phase:"busy",pendingConfirm:void 0};case"cancel-requested":return{...e,phase:"cancelling",pendingConfirm:void 0};case"turn-end":return{...e,phase:"idle",live:{...s}};default:return e}}
@@ -1,7 +1,9 @@
1
1
  import type { ReactElement } from "react";
2
2
  export interface TextPromptProps {
3
3
  readonly value: string;
4
+ readonly inputHistory: readonly string[];
4
5
  readonly disabled: boolean;
6
+ readonly disabledHint?: string;
5
7
  readonly slashActive: boolean;
6
8
  readonly slashPopupActive: boolean;
7
9
  readonly autoApprove: boolean;
@@ -9,7 +11,7 @@ export interface TextPromptProps {
9
11
  readonly onSubmit: (value: string) => void;
10
12
  readonly onSlashMove: (direction: 1 | -1) => void;
11
13
  readonly onSlashComplete: () => void;
12
- readonly onSlashRun: () => void;
14
+ readonly onSlashRun: (value: string) => void;
13
15
  }
14
16
  export declare function cursorPositionOf(lines: readonly string[], cursor: number): {
15
17
  row: number;
@@ -1 +1 @@
1
- import{createElement as r,useLayoutEffect as t,useRef as e,useState as n}from"react";import{Box as o,Text as l,useInput as i,usePaste as u,useStdout as c}from"ink";import{GLYPHS as s}from"../../utils/glyphs.js";import{applyEditorCommand as f,resolveEditorCommand as a}from"./editor-keymap.js";import{createLineBuffer as d,graphemeAt as h,insertText as m}from"./line-buffer.js";function p(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function cursorPositionOf(r,t){let e=t;for(const[t,n]of r.entries()){if(e<=n.length)return{row:t,col:e};e-=n.length+1}const n=r.length-1;return{row:n,col:r[n]?.length??0}}export function TextPrompt(v){const{value:g,disabled:w,slashActive:S,slashPopupActive:b,autoApprove:A,onChange:y,onSubmit:C}=v,{stdout:E}=c(),x=E.columns||80,[k]=n(()=>d(g)),j=e(k),[,P]=n(0);t(()=>{j.current.value!==g&&(j.current=d(g),P(r=>r+1))},[g]);const T=r=>{const t=j.current.value!==r.value;j.current=r,P(r=>r+1),t&&y(r.value)};i((r,t)=>{if(t.meta&&("."===r||","===r))return;if(p(r))return;if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void T(m(j.current,"\n"));if(t.return||r.includes("\r")){if(S)return void v.onSlashRun();const t=r.split("\r",1)[0]??"",e=t.length>0?m(j.current,t):j.current;return void C(e.value)}if(b){if(t.upArrow)return void v.onSlashMove(-1);if(t.downArrow)return void v.onSlashMove(1);if(t.tab&&!t.shift)return void v.onSlashComplete()}if(S&&t.escape)return void T(d(""));const e=a(r,t);void 0===e?t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&T(m(j.current,r)):T(f(e,j.current))},{isActive:!w}),u(r=>{T(m(j.current,r.replace(/\r\n?/g,"\n")))},{isActive:!w});const $=j.current,D=$.value.split("\n"),J=cursorPositionOf(D,$.cursor),M=r(o,{flexDirection:"column"},...D.map((t,e)=>{const n=0===e?r(l,{color:"green"},"› "):r(l,null," ");if(w)return r(o,{key:String(e)},n,r(l,{dimColor:!0},t));if(e!==J.row)return r(o,{key:String(e)},n,r(l,null,t));const i=h(t,J.col),u=t.slice(0,J.col),c=t.slice(J.col+i.length);return r(o,{key:String(e)},n,u.length>0?r(l,null,u):null,r(l,{inverse:!0},""===i?" ":i),c.length>0?r(l,null,c):null)})),O=w?"Esc 中断本轮":S?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":A?"Shift+Tab 关闭 · Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",L=r(o,{marginLeft:1},...A?[r(l,{color:"yellow"},`${s.auto} auto`),r(l,{dimColor:!0},` · ${O}`)]:[r(l,{dimColor:!0},O)]);return r(o,{flexDirection:"column",width:x},r(o,{borderStyle:"round",borderColor:w?"yellow":"cyan",paddingX:2},M),L)}
1
+ import{createElement as r,useLayoutEffect as t,useRef as e,useState as n}from"react";import{Box as o,Text as i,useInput as u,usePaste as c,useStdout as l}from"ink";import{GLYPHS as s}from"../../utils/glyphs.js";import{applyEditorCommand as a,resolveEditorCommand as d}from"./editor-keymap.js";import{createLineBuffer as f,graphemeAt as v,insertText as h,moveVisualDown as p,moveVisualUp as m}from"./line-buffer.js";const w=2,g=4,A=2;function S(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function cursorPositionOf(r,t){let e=t;for(const[t,n]of r.entries()){if(e<=n.length)return{row:t,col:e};e-=n.length+1}const n=r.length-1;return{row:n,col:r[n]?.length??0}}export function TextPrompt(w){const{value:g,inputHistory:A,disabled:b,slashActive:y,slashPopupActive:C,autoApprove:x,onChange:E,onSubmit:k}=w,{stdout:j}=l(),P=j.columns||80,T=Math.max(1,P-2-4-2),[$]=n(()=>f(g)),H=e($),M=e(void 0),D=e(""),[,J]=n(0);t(()=>{H.current.value!==g&&(H.current=f(g),M.current=void 0,D.current="",J(r=>r+1))},[g]);const O=r=>{const t=H.current.value!==r.value;H.current=r,J(r=>r+1),t&&E(r.value)},L=()=>{M.current=void 0,D.current=""};u((r,t)=>{if(t.meta&&("."===r||","===r))return;if(S(r))return;if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return L(),void O(h(H.current,"\n"));if(t.return||r.includes("\r")){L();const t=r.split("\r",1)[0]??"",e=t.length>0?h(H.current,t):H.current;return y||e.value.startsWith("/")?void w.onSlashRun(e.value):void k(e.value)}if(t.upArrow||t.downArrow){const r=void 0!==M.current,e=t.upArrow&&0===H.current.value.length;if((r||e)&&(r=>{const t=M.current;if(void 0===t){if(1===r||H.current.value.length>0||0===A.length)return!1;const t=A.length-1,e=A[t];return void 0!==e&&(D.current=H.current.value,M.current=t,O(f(e)),!0)}const e=t+r;if(e<0)return!0;if(e>=A.length){const r=D.current;return L(),O(f(r)),!0}const n=A[e];return void 0===n?(L(),!1):(M.current=e,O(f(n)),!0)})(t.upArrow?-1:1))return}if(C){if(t.upArrow)return void w.onSlashMove(-1);if(t.downArrow)return void w.onSlashMove(1);if(t.tab&&!t.shift)return void w.onSlashComplete()}if(y&&t.escape)return L(),void O(f(""));const e=d(r,t);if(void 0!==e){L();const r="move-up"===e?m(H.current,T):"move-down"===e?p(H.current,T):a(e,H.current);return void O(r)}t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&(L(),O(h(H.current,r)))},{isActive:!b}),c(r=>{L(),O(h(H.current,r.replace(/\r\n?/g,"\n")))},{isActive:!b});const R=H.current,W=R.value.split("\n"),X=cursorPositionOf(W,R.cursor),q=r(o,{flexDirection:"column"},...W.map((t,e)=>{const n=r(o,{width:2,flexShrink:0},0===e?r(i,b?{dimColor:!0}:{color:"green"},"› "):r(i,null," "));if(b)return r(o,{key:String(e)},n,r(i,{dimColor:!0,wrap:"hard"},t));if(e!==X.row)return r(o,{key:String(e)},n,r(i,{wrap:"hard"},t));const u=v(t,X.col),c=t.slice(0,X.col),l=t.slice(X.col+u.length);return r(o,{key:String(e)},n,r(i,{wrap:"hard"},c,r(i,{inverse:!0},""===u?" ":u),l))})),z=b?w.disabledHint??"Esc 中断本轮":y?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":x?"Shift+Tab 关闭 · Enter 发送 · 空输入 ↑ 历史 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · 空输入 ↑ 历史 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",B=b&&void 0!==w.disabledHint?{color:"yellow",dimColor:!0}:{dimColor:!0},F=r(o,{marginLeft:1},...x?[r(i,{color:"yellow"},`${s.auto} auto`),r(i,B,` · ${z}`)]:[r(i,B,z)]);return r(o,{flexDirection:"column",width:P},r(o,{borderStyle:"round",borderColor:b?"gray":"cyan",paddingX:2},q),F)}
@@ -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,14 @@
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 }: {
13
+ activity: TurnActivity;
14
+ }): 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,useStdout as i}from"ink";import{displayWidth as l}from"./display-width.js";import{Spinner as s}from"./spinner.js";import{TURN_ACTIVITY_KINDS as u}from"./turn-activity.js";const m=250,c=2,p=2,d=2,h=4,f=1e3,g="…",y=new Intl.Segmenter(void 0,{granularity:"grapheme"}),T={[u.waitingModel]:"gray",[u.reasoning]:"magenta",[u.replying]:"green",[u.tool]:"cyan",[u.compacting]:"yellow",[u.waitingUser]:"yellow",[u.cancelling]:"yellow"};function v(t,e){if(e<=0)return"";if(l(t)<=e)return t;const n=e-l(g);let r="",o=0;for(const{segment:e}of y.segment(t)){const t=l(e);if(o+t>n)break;r+=e,o+=t}return`${r}${g}`}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=l(o);if(a>=r)return{label:"",phaseTime:void 0,spacer:"",turnTime:v(o,r)};const i=Math.max(0,r-a-2);let s=t.showPhaseElapsed&&e>=f?formatElapsed(e):void 0;let u=i-(void 0===s?0:l(` ${s}`));void 0!==s&&u<4&&(s=void 0,u=i);const m=v(t.label,Math.max(0,u)),c=void 0===s?"":` ${s}`;return{label:m,phaseTime:s,spacer:" ".repeat(Math.max(1,r-l(m)-l(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:l}){const{stdout:u}=i(),c=u.columns||80,p=Date.now(),d=n(p),h=n({key:l.key,startedAt:p});h.current.key!==l.key&&(h.current={key:l.key,startedAt:p});const[f,g]=r(p);e(()=>{const t=setInterval(()=>{g(Date.now())},m);return()=>{clearInterval(t)}},[]);const y=Math.max(f,p),v=composeTurnStatusLayout(l,y-h.current.startedAt,y-d.current,Math.max(0,c-2-2));return t(o,{width:c,paddingX:1},l.animated?t(s,{color:T[l.kind]}):t(a,{color:T[l.kind]},"◆"),c>3?t(a,null," "):null,v.label.length>0?t(a,{color:T[l.kind]},v.label):null,void 0===v.phaseTime?null:t(a,{dimColor:!0},` ${v.phaseTime}`),v.spacer.length>0?t(a,null,v.spacer):null,v.turnTime.length>0?t(a,{dimColor:!0},v.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}}
@@ -5,12 +5,40 @@ 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<{
@@ -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,n,s,r){return o?t?".jsx":".js":!n||s&&r?n+s+"."+r.toLowerCase()+"js":e}):e};import{defineCommand as t}from"citty";import{inspectLlmConfigReadiness as o}from"../../config/helpers.js";import{loadConfig as n}from"../../config/loader.js";import{resolveLLMCall as s,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 l}from"../utils/output.js";import{ChatRenderer as d,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{formatSkillList as v,parseSkillInvocation as C}from"../chat/ink/commands.js";import{installCurrentCliShim as S}from"../utils/current-cli-shim.js";const w=import.meta.url.endsWith(".ts")?"ts":"js";function y(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}export const CHAT_ENGINE_SURFACES={ink:"ink",basicRepl:"basic-repl",oneShot:"one-shot",json:"json",server:"server"};const k={[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 k[e]}function E(e){console.log(JSON.stringify(e,null,2))}function O(e){l.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function T(e){l.warn(`skill 目录加载警告:${e}`)}function N(){const e={...process.env};try{const t=S({env:e});return{env:e,dispose:()=>t.dispose()}}catch(t){return l.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:y(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:O,onSkillLibraryIssue:T,...e.shellEnv?{shellEnv:e.shellEnv}:{}})}async function b(e,t,o){l.debug(`chat.repl input waiting · ${o}`),e.resume();try{const n=await e.question(t);return l.debug(`chat.repl input received · ${o} · chars=${String(n.length)}`),n}catch(e){const t=e instanceof Error?e.message:String(e);return void l.debug(`chat.repl input closed · ${o} · ${t}`)}finally{e.pause()}}async function R(){return import("@roll-agent/runtime")}async function I(t,o){const n=new URL(`./setup.${w}`,import.meta.url).href;return(await import(e(n))).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,n,r,i=void 0,a=!0){const c=s(e,t,o,"chat",n,r),u=a?s(e,t,o,"structured-output",n,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 x(e){const t=resolveChatLlmReadiness(e);if(!t.configured||!t.providerConfig)return l.error(t.message),void(process.exitCode=1);const o=t.providerConfig,n=t.provider,s=t.model,r=await R(),{ThreadStore:i,RuntimeServer:a,createStdioConnection:c}=r,{model:u,providerOptions:d,structuredOutputProviderOptions:p,structuredOutputReasoning:m}=resolveChatLlmCalls(n,s,o.apiKey,o.baseUrl,e.runtime.thinkingLevel,e.runtime.compaction.thinkingLevel,"summarize"===e.runtime.compaction.strategy),g=new i(e.runtime.threadsDir),f=N();let h;try{h=createChatEngine({runtime:r,config:e,model:u,store:g,surface:CHAT_ENGINE_SURFACES.server,shellEnv:f.env,...d?{providerOptions:d}:{},...p?{structuredOutputProviderOptions:p}:{},...m?{structuredOutputReasoning:m}:{}});const t=h,o=c(process.stdin,process.stdout),n=new a(t,o);o.onClose(()=>{n.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}l.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function A(e,t){const{ThreadStore:o}=await R(),n=new o(e.runtime.threadsDir);try{const e=n.listThreads();if(t){const t=e.map(e=>({...e,messageCount:n.countMessages(e.id)}));return void console.log(JSON.stringify(t,null,2))}if(0===e.length)return void console.log('暂无会话。用 `roll chat "<message>"` 开始一个。');const o=new c({head:["Session ID","标题","消息","更新时间"],style:{head:["cyan"]}});for(const t of e)o.push([t.id,t.title??"-",String(n.countMessages(t.id)),t.updatedAt]);console.log(o.toString())}finally{n.close()}}export async function runJsonTurn(e,t){const o=[],n=[],s=[],r=[];let i,a,c,u,l="";for await(const d of e.send(t))switch(d.type){case"text-delta":l+=d.delta;break;case"tool-call":o.push({summary:`${d.agentName}.${d.toolName}`,agentName:d.agentName,toolName:d.toolName});break;case"confirmation-required":r.push({summary:`${d.agentName}.${d.toolName}`,agentName:d.agentName,toolName:d.toolName}),e.reject(d.approvalId,"json 模式不支持交互确认");break;case"step-finish":n.push({finishReason:d.finishReason,...d.usage?{usage:d.usage}:{}});break;case"message-finish":a=d.totalUsage,c=d.sessionUsage,u=d.contextInputTokens;break;case"context-compacted":s.push({reason:d.reason,strategy:d.strategy,removed:d.removed,kept:d.kept,...void 0!==d.truncatedTools?{truncatedTools:d.truncatedTools}:{},...void 0!==d.beforeInputTokens?{beforeInputTokens:d.beforeInputTokens}:{},...void 0!==d.checkpointId?{checkpointId:d.checkpointId}:{},...void 0!==d.checkpointGeneration?{checkpointGeneration:d.checkpointGeneration}:{},...void 0!==d.checkpointSummaryStatus?{checkpointSummaryStatus:d.checkpointSummaryStatus}:{}});break;case"turn-cancelled":case"error":i=d.message}const d=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:l,steps:o,...n.length>0?{stepUsages:n}:{},...a?{totalUsage:a}:{},...c?{sessionUsage:c}:{},...void 0!==d?{contextWindow:d}:{},...void 0!==u?{contextInputTokens:u}:{},...s.length>0?{compactions:s}:{}}}export async function runRepl(e,t,o,n={input:process.stdin,output:process.stdout}){const s=i({input:n.input,output:n.output});s.on("SIGINT",()=>s.close());const r=n.confirm??(async e=>{l.debug("chat.repl input waiting · confirm"),s.pause();const t=await p(e);return l.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),c=new d(r,e.getContextWindow()),u=e.getSkillSummaries();l.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let g=!o,f=!1;try{for(;;){const o=await b(s,a.green("› "),"prompt");if(void 0===o)break;const n=o.trim();if(0===n.length)continue;if("exit"===n||"quit"===n)break;if("/compact"===n){l.debug("chat.repl manual compact requested");for await(const t of e.compact("manual"))await c.handle(t,e);l.debug("chat.repl manual compact completed");continue}if("/skills"===n){l.info(v(u,(process.stdout.columns||96)-2));continue}const r=C(n,u);if(r&&0===r.prompt.length)l.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");else{g||(t.updateTitle(e.id,m(n)),g=!0),f=!0,l.debug(`chat.repl send start · chars=${String(n.length)}`);for await(const t of e.send(n))await c.handle(t,e);l.debug("chat.repl send completed")}}}finally{s.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}=n();if(e.server)return void await x(t);if(e.list)return void await A(t,e.json);let o=resolveChatLlmReadiness(t);if(!o.configured){!0===process.stdin.isTTY&&!0===process.stderr.isTTY&&!e.json&&await I(o.provider,o.model)&&(t=n().config,o=resolveChatLlmReadiness(t))}if(!o.configured||!o.providerConfig)return l.error(o.message),void(process.exitCode=1);const s=o.providerConfig,i=o.provider,a=o.model;if(e.json&&!e.message)return l.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const c=await R(),{ThreadStore:u}=c,{model:v,providerOptions:C,structuredOutputProviderOptions:S,structuredOutputReasoning:y}=resolveChatLlmCalls(i,a,s.apiKey,s.baseUrl,t.runtime.thinkingLevel,t.runtime.compaction.thinkingLevel,"summarize"===t.runtime.compaction.strategy),k=new u(t.runtime.threadsDir),O=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode),T=e.message?e.json?CHAT_ENGINE_SURFACES.json:CHAT_ENGINE_SURFACES.oneShot:O?CHAT_ENGINE_SURFACES.ink:CHAT_ENGINE_SURFACES.basicRepl,b=N();let j,_;try{let o;if(j=createChatEngine({runtime:c,config:t,model:v,store:k,surface:T,shellEnv:b.env,...C?{providerOptions:C}:{},...S?{structuredOutputProviderOptions:S}:{},...y?{structuredOutputReasoning:y}:{}}),e.session)o=await j.resumeSession(e.session);else if(e.last){const e=k.listThreads()[0];if(!e)return l.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);o=await j.resumeSession(e.id)}else o=await j.createSession(e.message?{title:m(e.message)}:{});if(_=o,e.json&&e.message){const t=await runJsonTurn(o,e.message);return E(t),void("completed"!==t.status&&(process.exitCode=1))}if(l.info(`会话 ${o.id}`),t.runtime.compaction.enabled&&void 0===o.getContextWindow()&&l.warn(`未知模型 "${a}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const t=new d(p,o.getContextWindow());for await(const n of o.send(e.message))await t.handle(n,o)}else{const n=!e.session&&!e.last,s=await j.getContextSummary(),c={version:h(),model:a,agentCount:s.agentCount,skillCount:s.skillCount};let u=!1;if(O)try{const e=new URL(`../chat/ink/run-ink-repl.${w}`,import.meta.url).href,{runInkRepl:s}=await import(e);await s(o,k,n,{model:a,banner:c,initialThinkingLevel:t.runtime.thinkingLevel,onThinkingChange:e=>o.setProviderOptions(r(i,a,e))}),u=!0}catch(e){l.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,k,n))}}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await(_?.close()),await(j?.dispose()),k.close(),b.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})`)}