@roll-agent/core 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli/chat/ink/app.d.ts +4 -4
  2. package/dist/cli/chat/ink/app.js +1 -1
  3. package/dist/cli/chat/ink/banner-view.d.ts +3 -4
  4. package/dist/cli/chat/ink/commands.d.ts +4 -12
  5. package/dist/cli/chat/ink/commands.js +1 -1
  6. package/dist/cli/chat/ink/confirm-select.d.ts +3 -1
  7. package/dist/cli/chat/ink/confirm-select.js +1 -1
  8. package/dist/cli/chat/ink/history-from-messages.js +1 -1
  9. package/dist/cli/chat/ink/history-item.js +1 -1
  10. package/dist/cli/chat/ink/input-history.d.ts +2 -0
  11. package/dist/cli/chat/ink/input-history.js +1 -0
  12. package/dist/cli/chat/ink/layout.d.ts +13 -0
  13. package/dist/cli/chat/ink/layout.js +1 -0
  14. package/dist/cli/chat/ink/line-buffer.d.ts +8 -0
  15. package/dist/cli/chat/ink/line-buffer.js +1 -1
  16. package/dist/cli/chat/ink/live-region.d.ts +2 -0
  17. package/dist/cli/chat/ink/live-region.js +1 -1
  18. package/dist/cli/chat/ink/mouse-input.d.ts +9 -0
  19. package/dist/cli/chat/ink/mouse-input.js +1 -0
  20. package/dist/cli/chat/ink/reasoning-block.d.ts +4 -0
  21. package/dist/cli/chat/ink/reasoning-block.js +1 -0
  22. package/dist/cli/chat/ink/run-ink-repl.d.ts +2 -2
  23. package/dist/cli/chat/ink/run-ink-repl.js +1 -1
  24. package/dist/cli/chat/ink/slash-popup.d.ts +3 -1
  25. package/dist/cli/chat/ink/slash-popup.js +1 -1
  26. package/dist/cli/chat/ink/spinner.d.ts +3 -1
  27. package/dist/cli/chat/ink/spinner.js +1 -1
  28. package/dist/cli/chat/ink/state.d.ts +26 -3
  29. package/dist/cli/chat/ink/state.js +1 -1
  30. package/dist/cli/chat/ink/status-line.d.ts +3 -2
  31. package/dist/cli/chat/ink/status-line.js +1 -1
  32. package/dist/cli/chat/ink/terminal-output.d.ts +6 -0
  33. package/dist/cli/chat/ink/terminal-output.js +1 -0
  34. package/dist/cli/chat/ink/text-prompt.d.ts +7 -1
  35. package/dist/cli/chat/ink/text-prompt.js +1 -1
  36. package/dist/cli/chat/ink/transcript-viewport.d.ts +22 -0
  37. package/dist/cli/chat/ink/transcript-viewport.js +1 -0
  38. package/dist/cli/chat/ink/turn-activity.d.ts +19 -0
  39. package/dist/cli/chat/ink/turn-activity.js +1 -0
  40. package/dist/cli/chat/ink/turn-status-line.d.ts +15 -0
  41. package/dist/cli/chat/ink/turn-status-line.js +1 -0
  42. package/dist/cli/chat/ink/use-session.js +1 -1
  43. package/dist/cli/chat/screen-mode.d.ts +66 -0
  44. package/dist/cli/chat/screen-mode.js +1 -0
  45. package/dist/cli/commands/browser-stop.d.ts +4 -4
  46. package/dist/cli/commands/chat.d.ts +33 -1
  47. package/dist/cli/commands/chat.js +1 -1
  48. package/dist/cli/commands/config.js +1 -1
  49. package/dist/cli/utils/chat-renderer.js +1 -1
  50. package/dist/cli/utils/current-cli-shim.d.ts +14 -0
  51. package/dist/cli/utils/current-cli-shim.js +1 -0
  52. package/dist/cli/utils/debug-format.d.ts +4 -0
  53. package/dist/cli/utils/debug-format.js +1 -0
  54. package/dist/config/application-service.js +1 -1
  55. package/dist/config/defaults.d.ts +2 -1
  56. package/dist/config/defaults.js +1 -1
  57. package/dist/config/guidance.d.ts +38 -4
  58. package/dist/config/guidance.js +1 -1
  59. package/dist/config/schema.d.ts +136 -67
  60. package/dist/config/schema.js +1 -1
  61. package/dist/llm/providers.d.ts +6 -4
  62. package/dist/llm/providers.js +1 -1
  63. package/dist/registry/install.js +1 -1
  64. package/dist/skills/invocation.d.ts +27 -0
  65. package/dist/skills/invocation.js +1 -0
  66. package/dist/types/chat.d.ts +3 -0
  67. package/package.json +3 -2
@@ -1,20 +1,20 @@
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
- import { type BannerLine } from "../banner.ts";
6
+ import { type BannerInfo } from "../banner.ts";
7
7
  export interface ChatAppProps {
8
8
  readonly session: AgentSession;
9
9
  readonly model: string;
10
10
  readonly contextWindow: number | undefined;
11
11
  readonly initialHistory?: readonly HistoryItem[];
12
- /** Banner played as an entrance animation in the dynamic region, then committed to Static. */
13
- readonly animatedBanner?: readonly BannerLine[];
12
+ readonly banner?: BannerInfo;
14
13
  readonly initialThinkingLevel?: ThinkingLevel;
15
14
  readonly availableSkills?: readonly SlashSkillSummary[];
16
15
  readonly onThinkingChange?: (level: ThinkingLevel) => void;
17
16
  readonly onUserSubmit: (text: string) => void;
18
17
  readonly onExit: () => void;
19
18
  }
19
+ export declare const INK_HINTS = "/exit \u9000\u51FA \u00B7 Esc \u4E2D\u65AD \u00B7 / \u547D\u4EE4 \u00B7 Shift+Enter/Ctrl+J \u6362\u884C \u00B7 Alt+./Alt+, \u8C03\u63A8\u7406 \u00B7 Shift+Tab \u81EA\u52A8\u6279\u51C6";
20
20
  export declare function ChatApp(props: ChatAppProps): ReactElement;
@@ -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 t}from"node:crypto";import{createElement as i,useCallback as o,useState as n}from"react";import{Box as e,Text as s,useInput as r,useWindowSize as m}from"ink";import{useSession as a}from"./use-session.js";import{CHAT_PHASES as l}from"./state.js";import{StatusLine as d}from"./status-line.js";import{TextPrompt as p}from"./text-prompt.js";import{ConfirmSelect as h}from"./confirm-select.js";import{SlashPopup as f}from"./slash-popup.js";import{buildSkillListLines as c,filterSlashEntries as u,parseSkillInvocation as v,SLASH_COMMANDS as g}from"./commands.js";import{bannerTextLine as w,buildBannerLines as k}from"../banner.js";import{cycleThinking as x}from"./thinking.js";import{appendInputHistory as j}from"./input-history.js";import{resolveTurnActivity as y}from"./turn-activity.js";import{TurnStatusLine as b}from"./turn-status-line.js";import{resolveChatLayout as C}from"./layout.js";import{TranscriptViewport as S}from"./transcript-viewport.js";export const INK_HINTS="/exit 退出 · Esc 中断 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";function H(){return g.map(t=>`${t.name} — ${t.description}`).join("\n")}export function ChatApp(R){const{session:T,model:A,contextWindow:M,onUserSubmit:L,onExit:$}=R,I=m(),W=C(I.columns,I.rows),D=R.availableSkills??[],[E,N]=n(()=>(R.initialHistory??[]).reduce((t,i)=>"user"===i.kind?j(t,i.text):t,[])),B=o(t=>{N(i=>j(i,t))},[]),{state:K,submit:_,compact:J,cancel:P,resolveConfirm:U,setDraft:q,setThinking:z,setAutoMode:F,toggleAutoMode:G,commitHistory:O}=a(T,{model:A,contextWindow:M,...R.initialHistory?{initialHistory:R.initialHistory}:{},...R.initialThinkingLevel?{initialThinkingLevel:R.initialThinkingLevel}:{},...R.onThinkingChange?{onThinkingChange:R.onThinkingChange}:{}}),Q=void 0!==R.banner&&0===(R.initialHistory?.length??0),[V,X]=n(!Q),Y=o(()=>{X(!0)},[]),Z=void 0===R.banner?void 0:k(R.banner,W.columns,{hints:INK_HINTS}),[tt,it]=n(0),ot=K.phase===l.idle&&K.draft.startsWith("/"),nt=ot&&!0===K.draft.split(/\s+/).at(-1)?.startsWith("/"),et=nt?u(K.draft,D):[],st=Math.max(et.length-1,0),rt=Math.min(tt,st);r((t,i)=>{K.phase===l.busy&&i.escape&&!i.meta?P():i.tab&&i.shift?G():i.meta&&"."===t?z(x(K.status.thinkingLevel,1)):i.meta&&","===t&&z(x(K.status.thinkingLevel,-1))});const mt=t=>{const i=t.trim();0!==i.length?(B(i),Y(),L(i),_(i)):q("")},at=K.phase===l.confirm&&void 0!==K.pendingConfirm?i(h,{prompt:K.pendingConfirm.prompt,args:K.pendingConfirm.args,width:W.columns,maxRows:W.promptRows+W.popupRows,onDecide:U}):i(p,{value:K.draft,width:W.columns,viewportRows:W.renderRows,maxRows:W.promptRows,showHint:W.showHelp,inputHistory:E,disabled:K.phase!==l.idle,...K.phase===l.cancelling?{disabledHint:"中断请求已发送,等待当前活动退出…"}:{},slashActive:ot,slashPopupActive:nt,autoApprove:K.status.autoApprove,onChange:q,onSubmit:mt,onSlashMove:t=>{it(i=>Math.min(Math.max(Math.min(i,st)+t,0),st))},onSlashComplete:()=>{const t=et[rt];if(t){const i=K.draft.split(/\s+/);i[i.length-1]=t.name,q(`${i.join(" ")} `),it(0)}},onSlashRun:i=>{const o=i.trim().split(/\s+/,1)[0]??"",n=g.some(t=>t.name===o),e=et[rt];if(!n&&"skill"===e?.kind){const t=K.draft.split(/\s+/);return t[t.length-1]=e.name,q(`${t.join(" ")} `),void it(0)}(i=>{Y(),q("");const o=i.trim();B(o);const n=o.split(/\s+/),e=n[0]??"",s=(n[1]??"").toLowerCase(),r=K.status.thinkingLevel;if("/compact"===e)return void J();if("/think"===e)return void z("off"===s?"off":"on"===s?"off"===r?"medium":r:"off"===r?"medium":"off");if("/effort"===e)return void("low"===s||"medium"===s||"high"===s?z(s):O({kind:"notice",id:t(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===s?F(!0):"off"===s?F(!1):G());if("/skills"===e){const[i,...o]=c(D,W.contentWidth);return void O({kind:"banner",id:t(),lines:[w(i??""),...o.map(t=>w(t,{dim:!0}))]})}if("/help"===e)return void O({kind:"notice",id:t(),text:H()});if("/exit"===e)return void $();const m=v(o,D);if(m)return 0===m.prompt.length?void q(`${m.skills.map(t=>`/${t.name}`).join(" ")} `):void mt(o);O({kind:"notice",id:t(),text:`未知命令 ${e}`})})(n?i:e?.name??i)}}),lt=y(K);return W.tooSmall?i(e,{width:W.columns,height:W.renderRows,alignItems:"center",justifyContent:"center",flexDirection:"column",overflow:"hidden"},i(s,{color:"yellow",bold:!0},"终端窗口过小"),i(s,{dimColor:!0},"请调整到至少 40 × 10;当前会话和草稿已保留")):i(e,{flexDirection:"column",width:W.columns,height:W.renderRows,overflow:"hidden"},i(S,{width:W.columns,history:K.history,live:K.live,onBannerSettled:Y,animateBanner:Q&&!V,navigationBlocked:K.phase===l.confirm||nt,...void 0===Z?{}:{banner:Z}}),i(e,{flexShrink:0,marginTop:void 0===lt?0:1},void 0===lt?i(d,{status:K.status,width:W.columns}):i(b,{activity:lt,width:W.columns})),ot?i(f,{matches:et,selected:rt,width:W.columns,maxRows:W.popupRows}):null,at)}
@@ -4,10 +4,9 @@ export declare function BannerLinesView({ lines, }: {
4
4
  readonly lines: readonly BannerLine[];
5
5
  }): ReactElement;
6
6
  /**
7
- * Animated banner entrance. Must be rendered OUTSIDE Ink's Static region (Static prints
8
- * each item exactly once, so interval-driven re-renders are never repainted there).
9
- * Calls `onSettled` once the animation completes (immediately when there is no logo),
10
- * letting the parent commit the final banner into Static.
7
+ * Animated banner entrance for the managed transcript viewport. Calls `onSettled` once the
8
+ * animation completes (immediately when there is no logo), so the parent can switch to the
9
+ * stable banner renderer without changing its semantic transcript entry.
11
10
  */
12
11
  export declare function BannerHistoryView({ lines, onSettled, }: {
13
12
  readonly lines: readonly BannerLine[];
@@ -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")}
@@ -2,6 +2,8 @@ import type { ReactElement } from "react";
2
2
  export interface ConfirmSelectProps {
3
3
  readonly prompt: string;
4
4
  readonly args: string;
5
+ readonly width: number;
6
+ readonly maxRows: number;
5
7
  readonly onDecide: (approved: boolean) => void;
6
8
  }
7
- export declare function ConfirmSelect({ prompt, args, onDecide }: ConfirmSelectProps): ReactElement;
9
+ export declare function ConfirmSelect({ prompt, args, width, maxRows, onDecide, }: ConfirmSelectProps): ReactElement;
@@ -1 +1 @@
1
- import{createElement as o,useState as r}from"react";import{Box as e,Text as n,useInput as t,useStdout as i}from"ink";export function ConfirmSelect({prompt:l,args:c,onDecide:s}){const[d,u]=r("no"),{stdout:m}=i(),f=m.columns||80;t((o,r)=>{if(r.leftArrow||r.rightArrow||r.upArrow||r.downArrow)return void u(o=>"yes"===o?"no":"yes");if(r.return||o.includes("\r")||o.includes("\n"))return void s("yes"===d);const e=o.toLowerCase();r.escape||"n"===e?s(!1):"y"===e&&s(!0)});const a=c.length>0&&"{}"!==c;return o(e,{flexDirection:"column",width:f},o(e,{flexDirection:"column",borderStyle:"round",borderColor:"yellow",paddingX:2},o(n,null,l),a?o(n,{dimColor:!0},c):null,o(e,{marginTop:1},o(n,"yes"===d?{color:"green"}:{},("yes"===d?"❯ ":" ")+"Yes"),o(n,"no"===d?{color:"green"}:{},` ${"no"===d?"❯ ":" "}No`))),o(e,{marginLeft:1},o(n,{dimColor:!0},"←→/y/n 选择 · Enter 确认 · Esc 取消 · Shift+Tab 自动批准本次及后续")))}
1
+ import{createElement as e,useState as r}from"react";import{Box as o,Text as n,useInput as t}from"ink";export function ConfirmSelect({prompt:i,args:l,width:d,maxRows:h,onDecide:c}){const[a,f]=r("no");t((e,r)=>{if(r.leftArrow||r.rightArrow||r.upArrow||r.downArrow)return void f(e=>"yes"===e?"no":"yes");if(r.return||e.includes("\r")||e.includes("\n"))return void c("yes"===a);const o=e.toLowerCase();r.escape||"n"===o?c(!1):"y"===o&&c(!0)});const s=l.length>0&&"{}"!==l,m=Math.max(1,Math.floor(h));return e(o,{flexDirection:"column",width:d,height:m,flexShrink:0,overflowY:"hidden"},e(o,{flexDirection:"column",borderStyle:"round",borderColor:"yellow",paddingX:2,height:Math.max(3,m-1),flexShrink:0,overflowY:"hidden"},e(n,null,i),s?e(n,{dimColor:!0},l):null,e(o,{marginTop:1},e(n,"yes"===a?{color:"green"}:{},("yes"===a?"❯ ":" ")+"Yes"),e(n,"no"===a?{color:"green"}:{},` ${"no"===a?"❯ ":" "}No`))),e(o,{marginLeft:1,height:1,flexShrink:0,overflowY:"hidden"},e(n,{dimColor:!0,wrap:"truncate-end"},"←→/y/n 选择 · Enter 确认 · Esc 取消 · Shift+Tab 自动批准本次及后续")))}
@@ -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]}
@@ -0,0 +1,13 @@
1
+ export interface ChatLayout {
2
+ readonly columns: number;
3
+ readonly rows: number;
4
+ readonly renderRows: number;
5
+ readonly contentWidth: number;
6
+ readonly promptRows: number;
7
+ readonly popupRows: number;
8
+ readonly showHelp: boolean;
9
+ readonly tooSmall: boolean;
10
+ }
11
+ export declare const MIN_CHAT_COLUMNS = 40;
12
+ export declare const MIN_CHAT_ROWS = 10;
13
+ export declare function resolveChatLayout(columns: number, rows: number): ChatLayout;
@@ -0,0 +1 @@
1
+ export const MIN_CHAT_COLUMNS=40;export const MIN_CHAT_ROWS=10;export function resolveChatLayout(o,t){const a=Math.max(1,Math.floor(o)),h=Math.max(1,Math.floor(t)),M=Math.max(1,h-1),r=M>=13,n=Math.min(12,Math.max(r?4:3,Math.floor(.4*M))),m=Math.max(3,Math.min(8,M-n-3));return{columns:a,rows:h,renderRows:M,contentWidth:Math.max(1,a-2),promptRows:n,popupRows:m,showHelp:r,tooSmall:a<40||h<10}}
@@ -3,6 +3,12 @@ export interface LineBufferState {
3
3
  readonly cursor: number;
4
4
  readonly goalColumn: number | undefined;
5
5
  }
6
+ export interface VisualLineMetrics {
7
+ readonly cursorRow: number;
8
+ readonly cursorColumn: number;
9
+ readonly totalRows: number;
10
+ }
11
+ export declare function visualLineMetrics(state: LineBufferState, columns: number): VisualLineMetrics;
6
12
  export declare function createLineBuffer(value: string, cursor?: number): LineBufferState;
7
13
  export declare function graphemeAt(value: string, offset: number): string;
8
14
  export declare function insertText(state: LineBufferState, text: string): LineBufferState;
@@ -19,3 +25,5 @@ export declare function moveToLineStart(state: LineBufferState): LineBufferState
19
25
  export declare function moveToLineEnd(state: LineBufferState): LineBufferState;
20
26
  export declare function moveUp(state: LineBufferState): LineBufferState;
21
27
  export declare function moveDown(state: LineBufferState): LineBufferState;
28
+ export declare function moveVisualUp(state: LineBufferState, columns: number): LineBufferState;
29
+ 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 v(r,e){let t=0;for(const o of n.segment(r.slice(0,e)))i(o)&&(t=o.index);return t}function f(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 v=o===c&&i>=u,f=-1===t&&v;if(l.push({start:a,end:c,softWrap:v}),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)}export function visualLineMetrics(e,n){const t=m(e.value,n,e.cursor),o=h(t,e.cursor),u=t[o];return{cursorRow:o,cursorColumn:void 0===u?0:r(e.value.slice(u.start,e.cursor)),totalRows:Math.max(1,t.length)}}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,v(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: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}}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,9 @@
1
+ export declare const ENABLE_MOUSE_TRACKING = "\u001B[?1000h\u001B[?1006h";
2
+ export declare const DISABLE_MOUSE_TRACKING = "\u001B[?1006l\u001B[?1000l";
3
+ export interface MouseWheelInput {
4
+ readonly direction: "up" | "down";
5
+ readonly column: number;
6
+ readonly row: number;
7
+ }
8
+ export declare function isMouseProtocolInput(input: string): boolean;
9
+ export declare function parseMouseWheelInput(input: string): MouseWheelInput | undefined;
@@ -0,0 +1 @@
1
+ export const ENABLE_MOUSE_TRACKING="[?1000h[?1006h";export const DISABLE_MOUSE_TRACKING="[?1006l[?1000l";const t=["[<","›<","[<"];function o(o){const n=t.find(t=>o.startsWith(t)),e=o.at(-1);if(void 0===n||"M"!==e&&"m"!==e)return;const r=o.slice(n.length,-1).split(";");if(3!==r.length||r.some(t=>!/^\d+$/.test(t)))return;const[u,i,s]=r,c=Number(u),d=Number(i),l=Number(s);return Number.isInteger(c)&&Number.isInteger(d)&&Number.isInteger(l)?{button:c,column:d,row:l}:void 0}export function isMouseProtocolInput(t){return void 0!==o(t)}export function parseMouseWheelInput(t){const n=o(t),e=void 0===n?void 0:3&n.button;if(void 0!==n&&64&n.button&&void 0!==e&&!(e>1))return{direction:0===e?"up":"down",column:n.column,row:n.row}}
@@ -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,7 +1,6 @@
1
1
  import type { AgentSession } from "@roll-agent/runtime";
2
2
  import type { ThinkingLevel } from "../../../llm/providers.ts";
3
- import { type BannerInfo } from "../banner.ts";
4
- export declare const INK_HINTS = "/exit \u9000\u51FA \u00B7 Esc \u4E2D\u65AD \u00B7 / \u547D\u4EE4 \u00B7 Shift+Enter/Ctrl+J \u6362\u884C \u00B7 Alt+./Alt+, \u8C03\u63A8\u7406 \u00B7 Shift+Tab \u81EA\u52A8\u6279\u51C6";
3
+ import type { BannerInfo } from "../banner.ts";
5
4
  export interface InkReplStore {
6
5
  updateTitle(threadId: string, title: string): void;
7
6
  countMessages(threadId: string): number;
@@ -12,5 +11,6 @@ export interface RunInkReplOptions {
12
11
  readonly banner?: BannerInfo;
13
12
  readonly initialThinkingLevel?: ThinkingLevel;
14
13
  readonly onThinkingChange?: (level: ThinkingLevel) => void;
14
+ readonly onStarted?: () => void;
15
15
  }
16
16
  export declare function runInkRepl(session: AgentSession, store: InkReplStore, isNewSession: boolean, options: RunInkReplOptions): Promise<void>;
@@ -1 +1 @@
1
- import{createElement as t}from"react";import{render as e}from"ink";import{ChatApp as i}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{buildBannerLines as s}from"../banner.js";import{log as a}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · Esc 中断 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(r,l,d,m){let p=!1,c=!d;const g=t=>{p=!0,c||(l.updateTitle(r.id,o(t)),c=!0)};m.banner||a.info(`多轮对话已就绪(${INK_HINTS})`);const h=n(r.getMessages()),u=m.banner?s(m.banner,process.stdout.columns||80,{hints:INK_HINTS}):void 0,f=void 0!==u&&0===h.length,T=void 0===u||f?[]:[{kind:"banner",id:"banner",lines:u}],k=!0===process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode;let b;k&&process.stdin.setRawMode(!0);try{b=e(t(i,{session:r,model:m.model,contextWindow:r.getContextWindow(),availableSkills:r.getSkillSummaries(),onUserSubmit:g,onExit:()=>{b.unmount()},initialHistory:[...T,...h],...f&&void 0!==u?{animatedBanner:u}:{},...m.initialThinkingLevel?{initialThinkingLevel:m.initialThinkingLevel}:{},...m.onThinkingChange?{onThinkingChange:m.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}})}catch(t){throw k&&process.stdin.setRawMode(!1),t}await b.waitUntilExit(),await r.close(),d&&!p&&0===l.countMessages(r.id)&&l.deleteThread(r.id)}
1
+ import{createElement as t}from"react";import{render as e}from"ink";import{ChatApp as i,INK_HINTS as n}from"./app.js";import{messagesToHistory as o}from"./history-from-messages.js";import{titleFromMessage as s}from"../title.js";import{log as r}from"../../utils/output.js";import{createChatTerminalOutput as a}from"./terminal-output.js";export async function runInkRepl(l,m,d,g){let p=!1,u=!d;const c=t=>{p=!0,u||(m.updateTitle(l.id,s(t)),u=!0)};g.banner||r.info(`多轮对话已就绪(${n})`);const f=o(l.getMessages()),h=a(process.stdout);try{const n=e(t(i,{session:l,model:g.model,contextWindow:l.getContextWindow(),availableSkills:l.getSkillSummaries(),onUserSubmit:c,onExit:()=>{n.unmount()},initialHistory:f,...void 0===g.banner?{}:{banner:g.banner},...g.initialThinkingLevel?{initialThinkingLevel:g.initialThinkingLevel}:{},...g.onThinkingChange?{onThinkingChange:g.onThinkingChange}:{}}),{stdout:h.stdout,interactive:!0,alternateScreen:!0,kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}});g.onStarted?.(),await n.waitUntilExit()}finally{h.dispose()}if(await l.close(),d&&!p&&0===m.countMessages(l.id))return m.deleteThread(l.id),void process.stderr.write("本次会话无消息,未保存\n");const k=l.getMessages().filter(t=>"user"===t.role||"assistant"===t.role).length;process.stderr.write(`会话 ${l.id} · ${String(k)} 条消息\n继续:roll chat --session ${l.id}\n`)}
@@ -3,5 +3,7 @@ import type { SlashEntry } from "./commands.ts";
3
3
  export interface SlashPopupProps {
4
4
  readonly matches: readonly SlashEntry[];
5
5
  readonly selected: number;
6
+ readonly width: number;
7
+ readonly maxRows: number;
6
8
  }
7
- export declare function SlashPopup({ matches, selected }: SlashPopupProps): ReactElement;
9
+ export declare function SlashPopup({ matches, selected, width, maxRows }: SlashPopupProps): ReactElement;
@@ -1 +1 @@
1
- import{createElement as o}from"react";import{Box as r,Text as t,useStdout as n}from"ink";const e=8;export function SlashPopup({matches:e,selected:l}){const{stdout:a}=n(),i=a.columns||80;if(0===e.length)return o(r,{borderStyle:"round",borderColor:"gray",paddingX:1},o(t,{dimColor:!0},"无匹配命令"));const d=Math.min(Math.max(l-8+1,0),Math.max(e.length-8,0)),m=e.slice(d,d+8),c=Math.max(i-4,20);return o(r,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1},...m.map((n,e)=>{const a=d+e===l;return o(r,{key:n.name,width:c},o(t,{wrap:"truncate-end"},o(t,a?{color:"cyan",bold:!0}:{dimColor:!0},`${a?"❯ ":" "}${n.name}`),"skill"===n.kind?o(t,{color:"magenta"}," ⚡"):null,o(t,{dimColor:!0},` ${n.description}`)))}),e.length>8?o(t,{dimColor:!0},` ${String(l+1)}/${String(e.length)} · ↑↓ 浏览`):null)}
1
+ import{createElement as r}from"react";import{Box as e,Text as o}from"ink";const t=8;export function SlashPopup({matches:t,selected:n,width:a,maxRows:i}){if(0===t.length)return r(e,{borderStyle:"round",borderColor:"gray",paddingX:1,height:Math.min(3,i),flexShrink:0,overflowY:"hidden"},r(o,{dimColor:!0},"无匹配命令"));const l=Math.max(1,i-2),d=l>=2&&t.length>l,h=Math.max(1,Math.min(8,l-(d?1:0))),m=Math.min(Math.max(n-h+1,0),Math.max(t.length-h,0)),c=t.slice(m,m+h),g=Math.min(i,h+2+(d?1:0)),s=Math.max(a-4,20);return r(e,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,height:g,flexShrink:0,overflowY:"hidden"},...c.map((t,a)=>{const i=m+a===n;return r(e,{key:t.name,width:s},r(o,{wrap:"truncate-end"},r(o,i?{color:"cyan",bold:!0}:{dimColor:!0},`${i?"❯ ":" "}${t.name}`),"skill"===t.kind?r(o,{color:"magenta"}," ⚡"):null,r(o,{dimColor:!0},` ${t.description}`)))}),d?r(o,{dimColor:!0},` ${String(n+1)}/${String(t.length)} · ↑↓ 浏览`):null)}
@@ -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}}
@@ -10,7 +10,8 @@ export interface StatusSegmentView {
10
10
  readonly props: SegmentProps;
11
11
  }
12
12
  export declare function composeStatusSegments(status: StatusState, width: number): StatusSegmentView[];
13
- export declare function StatusLine({ status }: {
14
- status: StatusState;
13
+ export declare function StatusLine({ status, width, }: {
14
+ readonly status: StatusState;
15
+ readonly width: number;
15
16
  }): ReactElement;
16
17
  export {};
@@ -1 +1 @@
1
- import{createElement as o}from"react";import{Box as t,Text as e,useStdout as s}from"ink";import{computeUsageParts as n,contextPressure as p,formatContextUsage as i,formatThroughput as r,formatTokens as l,formatTurnUsage as c}from"../../utils/token-format.js";import{displayWidth as u}from"./display-width.js";import{GLYPHS as m}from"../../utils/glyphs.js";import{thinkingLabel as a}from"./thinking.js";const k={ok:{dimColor:!0},warn:{color:"yellow"},critical:{color:"red"}},d={off:"off",low:"low",medium:"med",high:"high"},f=["tps","session","turn","think"],h=" · ";export function composeStatusSegments(o,t){const e=n(o.turnUsage,o.sessionUsage,o.contextWindow,o.contextInputTokens),s=[{key:"model",full:o.model,compact:o.model,props:{color:"magenta"}},{key:"think",full:a(o.thinkingLevel),compact:`${m.think} ${d[o.thinkingLevel]}`,props:"off"===o.thinkingLevel?{color:"yellow"}:{dimColor:!0}}];o.autoApprove&&s.push({key:"auto",full:`${m.auto} auto-approve`,compact:`${m.auto} auto`,props:{color:"yellow"}});const y=i(e);void 0!==y&&void 0!==e.usedTokens&&void 0!==e.contextWindow&&s.push({key:"ctx",full:y,compact:`ctx ${l(e.usedTokens)}/${l(e.contextWindow)}`,props:k[p(e.percentLeft)]});const v=c(e);if(void 0!==v){const o=[];void 0!==e.inputTokens&&o.push(`↑${l(e.inputTokens)}`),void 0!==e.outputTokens&&o.push(`↓${l(e.outputTokens)}`),s.push({key:"turn",full:v,compact:o.join(" "),props:{dimColor:!0}})}const g=r(o.outputTokensPerSecond);void 0!==g&&s.push({key:"tps",full:g,compact:g.replace(" tok/s","t/s"),props:{dimColor:!0}}),void 0!==e.sessionTokens&&s.push({key:"session",full:`session ${l(e.sessionTokens)}`,compact:`Σ${l(e.sessionTokens)}`,props:{dimColor:!0}});const w=(o,t)=>o.map(o=>({key:o.key,text:o[t],props:o.props})),x=o=>u(o.map(o=>o.text).join(h))<=t,T=w(s,"full");if(x(T))return T;let $=s,j=w($,"compact");for(const o of f){if(x(j))break;$=$.filter(t=>t.key!==o),j=w($,"compact")}return j}export function StatusLine({status:n}){const{stdout:p}=s(),i=p.columns||80,r=composeStatusSegments(n,i);return o(t,{width:i},o(e,{wrap:"truncate-end"},...r.flatMap((t,s)=>[...s>0?[o(e,{key:`${t.key}-sep`,dimColor:!0},h)]:[],o(e,{key:t.key,...t.props},t.text)])))}
1
+ import{createElement as o}from"react";import{Box as t,Text as e}from"ink";import{computeUsageParts as s,contextPressure as n,formatContextUsage as p,formatThroughput as i,formatTokens as r,formatTurnUsage as l}from"../../utils/token-format.js";import{displayWidth as c}from"./display-width.js";import{GLYPHS as u}from"../../utils/glyphs.js";import{thinkingLabel as m}from"./thinking.js";const a={ok:{dimColor:!0},warn:{color:"yellow"},critical:{color:"red"}},k={off:"off",low:"low",medium:"med",high:"high"},d=["tps","session","turn","think"],f=" · ";export function composeStatusSegments(o,t){const e=s(o.turnUsage,o.sessionUsage,o.contextWindow,o.contextInputTokens),h=[{key:"model",full:o.model,compact:o.model,props:{color:"magenta"}},{key:"think",full:m(o.thinkingLevel),compact:`${u.think} ${k[o.thinkingLevel]}`,props:"off"===o.thinkingLevel?{color:"yellow"}:{dimColor:!0}}];o.autoApprove&&h.push({key:"auto",full:`${u.auto} auto-approve`,compact:`${u.auto} auto`,props:{color:"yellow"}});const y=p(e);void 0!==y&&void 0!==e.usedTokens&&void 0!==e.contextWindow&&h.push({key:"ctx",full:y,compact:`ctx ${r(e.usedTokens)}/${r(e.contextWindow)}`,props:a[n(e.percentLeft)]});const v=l(e);if(void 0!==v){const o=[];void 0!==e.inputTokens&&o.push(`↑${r(e.inputTokens)}`),void 0!==e.outputTokens&&o.push(`↓${r(e.outputTokens)}`),h.push({key:"turn",full:v,compact:o.join(" "),props:{dimColor:!0}})}const w=i(o.outputTokensPerSecond);void 0!==w&&h.push({key:"tps",full:w,compact:w.replace(" tok/s","t/s"),props:{dimColor:!0}}),void 0!==e.sessionTokens&&h.push({key:"session",full:`session ${r(e.sessionTokens)}`,compact:`Σ${r(e.sessionTokens)}`,props:{dimColor:!0}});const g=(o,t)=>o.map(o=>({key:o.key,text:o[t],props:o.props})),x=o=>c(o.map(o=>o.text).join(f))<=t,T=g(h,"full");if(x(T))return T;let $=h,j=g($,"compact");for(const o of d){if(x(j))break;$=$.filter(t=>t.key!==o),j=g($,"compact")}return j}export function StatusLine({status:s,width:n}){const p=composeStatusSegments(s,n);return o(t,{width:n},o(e,{wrap:"truncate-end"},...p.flatMap((t,s)=>[...s>0?[o(e,{key:`${t.key}-sep`,dimColor:!0},f)]:[],o(e,{key:t.key,...t.props},t.text)])))}
@@ -0,0 +1,6 @@
1
+ export declare const CHAT_CURSOR_REFRESH_EVENT = "roll:chat-cursor-refresh";
2
+ export interface ChatTerminalOutput {
3
+ readonly stdout: NodeJS.WriteStream;
4
+ dispose(): void;
5
+ }
6
+ export declare function createChatTerminalOutput(source: NodeJS.WriteStream): ChatTerminalOutput;
@@ -0,0 +1 @@
1
+ import{Writable as e}from"node:stream";const s="",r="",i="[?2026h",t="[?2026l",n="[?25l",o="[?25h",u=16,h=["[?1049l","[?25h","[?2004l","[?1000l","[?1006l","[<u"],c=String.fromCharCode(27),a="\\[[0-?]*[ -/]*[@-~]",l=new RegExp(`${c}${a}`,"g"),d=new RegExp(`(?:${c}${a})+$`,"g");export const CHAT_CURSOR_REFRESH_EVENT="roll:chat-cursor-refresh";function p(e){let s=e;for(const e of h)s=s.replaceAll(e,"");return 0===s.length}function z(e){return e.replace(l,"").replace(/[\r\n\t]/g,"").length>0}function m(e){return/[\r\n]$/.test(e.replace(d,""))}class ManagedChatOutput extends e{source;renderedColumns;nextColumnsRead;suppressNextResizeClear=!1;suppressVisualWrites=!1;resizeSynchronizationOpen=!1;resizeFrameNeedsCursorLineAdvance=!1;resizeTimer;cursorRefreshQueued=!1;handleSourceResize=()=>{void 0!==this.resizeTimer&&clearTimeout(this.resizeTimer),this.resizeTimer=setTimeout(()=>{this.resizeTimer=void 0;const e=this.source.columns,s=this.listeners("resize"),t=s[0],n=Math.max(1,Math.min(this.renderedColumns,e)-1);if(void 0!==t&&n<this.renderedColumns){this.suppressVisualWrites=!0;try{for(const e of s.slice(1))e.call(this)}finally{this.suppressVisualWrites=!1}this.source.write(i+r),this.resizeSynchronizationOpen=!0,this.nextColumnsRead=n,this.suppressNextResizeClear=!0;try{t()}finally{this.nextColumnsRead=void 0,this.suppressNextResizeClear=!1}this.suppressVisualWrites=!0;try{t()}finally{this.suppressVisualWrites=!1}}else this.emit("resize");this.renderedColumns=e},u)};constructor(e){super({decodeStrings:!1}),this.source=e,this.renderedColumns=e.columns,e.on("resize",this.handleSourceResize)}get columns(){if(void 0!==this.nextColumnsRead){const e=this.nextColumnsRead;return this.nextColumnsRead=void 0,e}return this.source.columns}get rows(){return this.source.rows}get isTTY(){return!0}_write(e,r,u){const h=(Buffer.isBuffer(e)?e.toString("utf8"):e).replaceAll(s,"");if(this.suppressNextResizeClear)return this.suppressNextResizeClear=!1,void u();if(void 0===this.resizeTimer&&!this.suppressVisualWrites||p(h))try{h.includes(i)&&(this.resizeSynchronizationOpen=!0,this.resizeFrameNeedsCursorLineAdvance=!1);const e=this.resizeSynchronizationOpen&&this.resizeFrameNeedsCursorLineAdvance&&h.includes(o)&&!z(h)?"\r\n":"";this.source.write(e+h),e.length>0&&(this.resizeFrameNeedsCursorLineAdvance=!1),this.resizeSynchronizationOpen&&z(h)&&(this.resizeFrameNeedsCursorLineAdvance=!m(h)),h.includes(t)&&(this.resizeSynchronizationOpen=!1,this.resizeFrameNeedsCursorLineAdvance=!1),h.includes(n)&&!h.includes(o)&&this.queueCursorRefresh(),u()}catch(e){u(e instanceof Error?e:new Error(String(e)))}else u()}queueCursorRefresh(){this.cursorRefreshQueued||(this.cursorRefreshQueued=!0,queueMicrotask(()=>{this.cursorRefreshQueued=!1,this.destroyed||this.emit(CHAT_CURSOR_REFRESH_EVENT)}))}dispose(){void 0!==this.resizeTimer&&(clearTimeout(this.resizeTimer),this.resizeTimer=void 0),this.source.off("resize",this.handleSourceResize),this.resizeSynchronizationOpen&&(this.source.write(t),this.resizeSynchronizationOpen=!1,this.resizeFrameNeedsCursorLineAdvance=!1),this.destroy()}}export function createChatTerminalOutput(e){const s=new ManagedChatOutput(e);return{stdout:s,dispose:()=>s.dispose()}}
@@ -1,7 +1,13 @@
1
1
  import type { ReactElement } from "react";
2
2
  export interface TextPromptProps {
3
3
  readonly value: string;
4
+ readonly width: number;
5
+ readonly viewportRows: number;
6
+ readonly maxRows: number;
7
+ readonly showHint: boolean;
8
+ readonly inputHistory: readonly string[];
4
9
  readonly disabled: boolean;
10
+ readonly disabledHint?: string;
5
11
  readonly slashActive: boolean;
6
12
  readonly slashPopupActive: boolean;
7
13
  readonly autoApprove: boolean;
@@ -9,7 +15,7 @@ export interface TextPromptProps {
9
15
  readonly onSubmit: (value: string) => void;
10
16
  readonly onSlashMove: (direction: 1 | -1) => void;
11
17
  readonly onSlashComplete: () => void;
12
- readonly onSlashRun: () => void;
18
+ readonly onSlashRun: (value: string) => void;
13
19
  }
14
20
  export declare function cursorPositionOf(lines: readonly string[], cursor: number): {
15
21
  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,useEffect as t,useLayoutEffect as e,useRef as o,useState as n}from"react";import{Box as i,Text as u,useCursor as l,useInput as c,usePaste as a,useStdout as s}from"ink";import{GLYPHS as d}from"../../utils/glyphs.js";import{applyEditorCommand as h,resolveEditorCommand as f}from"./editor-keymap.js";import{createLineBuffer as v,insertText as m,moveVisualDown as p,moveVisualUp as w,visualLineMetrics as g}from"./line-buffer.js";import{isMouseProtocolInput as x}from"./mouse-input.js";import{CHAT_CURSOR_REFRESH_EVENT as A}from"./terminal-output.js";const S=2,b=4,C=2,y=5;function M(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function cursorPositionOf(r,t){let e=t;for(const[t,o]of r.entries()){if(e<=o.length)return{row:t,col:e};e-=o.length+1}const o=r.length-1;return{row:o,col:r[o]?.length??0}}export function TextPrompt(S){const{value:b,width:C,maxRows:y,showHint:E,inputHistory:R,disabled:k,slashActive:j,slashPopupActive:H,autoApprove:P,onChange:T,onSubmit:$}=S,{setCursorPosition:D}=l(),{stdout:Y}=s(),[,J]=n(0);t(()=>{if(k)return;const r=()=>{J(r=>r+1)};return Y.on(A,r),()=>{Y.off(A,r)}},[k,Y]);const L=Math.max(1,C-2-4-2),[O]=n(()=>v(b)),W=o(O),X=o(void 0),q=o(""),[,z]=n(0);e(()=>{W.current.value!==b&&(W.current=v(b),X.current=void 0,q.current="",z(r=>r+1))},[b]);const B=r=>{const t=W.current.value!==r.value;W.current=r,z(r=>r+1),t&&T(r.value)},F=()=>{X.current=void 0,q.current=""};c((r,t)=>{if(t.meta&&("."===r||","===r))return;if(M(r))return;if(x(r))return;if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return F(),void B(m(W.current,"\n"));if(t.return||r.includes("\r")){F();const t=r.split("\r",1)[0]??"",e=t.length>0?m(W.current,t):W.current;return j||e.value.startsWith("/")?void S.onSlashRun(e.value):void $(e.value)}if(t.upArrow||t.downArrow){const r=void 0!==X.current,e=t.upArrow&&0===W.current.value.length;if((r||e)&&(r=>{const t=X.current;if(void 0===t){if(1===r||W.current.value.length>0||0===R.length)return!1;const t=R.length-1,e=R[t];return void 0!==e&&(q.current=W.current.value,X.current=t,B(v(e)),!0)}const e=t+r;if(e<0)return!0;if(e>=R.length){const r=q.current;return F(),B(v(r)),!0}const o=R[e];return void 0===o?(F(),!1):(X.current=e,B(v(o)),!0)})(t.upArrow?-1:1))return}if(H){if(t.upArrow)return void S.onSlashMove(-1);if(t.downArrow)return void S.onSlashMove(1);if(t.tab&&!t.shift)return void S.onSlashComplete()}if(j&&t.escape)return F(),void B(v(""));const e=f(r,t);if(void 0!==e){F();const r="move-up"===e?w(W.current,L):"move-down"===e?p(W.current,L):h(e,W.current);return void B(r)}t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&(F(),B(m(W.current,r)))},{isActive:!k}),a(r=>{F(),B(m(W.current,r.replace(/\r\n?/g,"\n")))},{isActive:!k});const G=W.current,I=G.value.split("\n"),K=g(G,L),N=Math.max(1,Math.floor(y)-2-(E?1:0)),Q=Math.min(K.totalRows,N),U=Math.min(Math.max(0,K.cursorRow-Q+1),Math.max(0,K.totalRows-Q)),V=K.cursorRow-U,Z=Q+2+(E?1:0),_=Math.max(0,S.viewportRows-Z);D(k?void 0:{x:5+K.cursorColumn,y:_+1+V});const rr=r(i,{flexDirection:"column",flexShrink:0,position:"relative",top:-U,height:K.totalRows},...I.map((t,e)=>{const o=r(i,{width:2,flexShrink:0},0===e?r(u,k?{dimColor:!0}:{color:"green"},"› "):r(u,null," "));return r(i,{key:String(e)},o,r(u,k?{dimColor:!0,wrap:"hard"}:{wrap:"hard"},t))})),tr=r(i,{flexDirection:"column",height:Q,overflowY:"hidden"},rr),er=k?S.disabledHint??"Esc 中断本轮":j?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":P?"Shift+Tab 关闭 · Enter 发送 · 空输入 ↑ 历史 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · 空输入 ↑ 历史 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",or=k&&void 0!==S.disabledHint?{color:"yellow",dimColor:!0}:{dimColor:!0},nr=E?r(i,{marginLeft:1,flexShrink:0,height:1,overflowY:"hidden"},...P?[r(u,{color:"yellow",wrap:"truncate-end"},`${d.auto} auto`),r(u,{...or,wrap:"truncate-end"},` · ${er}`)]:[r(u,{...or,wrap:"truncate-end"},er)]):null;return r(i,{flexDirection:"column",width:C,flexShrink:0},r(i,{borderStyle:"round",borderColor:k?"gray":"cyan",paddingX:2,height:Q+2,overflowY:"hidden"},tr),nr)}