@roll-agent/core 0.15.0 → 0.17.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 (88) hide show
  1. package/dist/cli/chat/ink/app.js +1 -1
  2. package/dist/cli/chat/ink/commands.js +1 -1
  3. package/dist/cli/chat/ink/display-width.d.ts +1 -0
  4. package/dist/cli/chat/ink/display-width.js +1 -0
  5. package/dist/cli/chat/ink/editor-keymap.d.ts +29 -0
  6. package/dist/cli/chat/ink/editor-keymap.js +1 -0
  7. package/dist/cli/chat/ink/history-item.js +1 -1
  8. package/dist/cli/chat/ink/line-buffer.d.ts +21 -0
  9. package/dist/cli/chat/ink/line-buffer.js +1 -0
  10. package/dist/cli/chat/ink/live-region.js +1 -1
  11. package/dist/cli/chat/ink/markdown.d.ts +0 -1
  12. package/dist/cli/chat/ink/markdown.js +1 -1
  13. package/dist/cli/chat/ink/run-ink-repl.d.ts +1 -1
  14. package/dist/cli/chat/ink/run-ink-repl.js +1 -1
  15. package/dist/cli/chat/ink/state.d.ts +6 -0
  16. package/dist/cli/chat/ink/state.js +1 -1
  17. package/dist/cli/chat/ink/status-line.js +1 -1
  18. package/dist/cli/chat/ink/text-prompt.d.ts +4 -0
  19. package/dist/cli/chat/ink/text-prompt.js +1 -1
  20. package/dist/cli/chat/ink/use-session.d.ts +1 -0
  21. package/dist/cli/chat/ink/use-session.js +1 -1
  22. package/dist/cli/commands/agent-add.js +1 -1
  23. package/dist/cli/commands/agent-env-guidance.d.ts +2 -0
  24. package/dist/cli/commands/agent-env-guidance.js +1 -0
  25. package/dist/cli/commands/agent-install.d.ts +5 -0
  26. package/dist/cli/commands/agent-install.js +1 -1
  27. package/dist/cli/commands/agent-list.d.ts +5 -0
  28. package/dist/cli/commands/agent-list.js +1 -1
  29. package/dist/cli/commands/ask.js +1 -1
  30. package/dist/cli/commands/chat.d.ts +3 -0
  31. package/dist/cli/commands/chat.js +1 -1
  32. package/dist/cli/commands/config-guidance.d.ts +21 -0
  33. package/dist/cli/commands/config-guidance.js +1 -1
  34. package/dist/cli/commands/config-prompts.d.ts +6 -0
  35. package/dist/cli/commands/config-prompts.js +1 -1
  36. package/dist/cli/commands/config-setup.d.ts +6 -1
  37. package/dist/cli/commands/config-setup.js +1 -1
  38. package/dist/cli/commands/config.d.ts +1 -0
  39. package/dist/cli/commands/config.js +1 -1
  40. package/dist/cli/commands/run.js +1 -1
  41. package/dist/cli/commands/setup.d.ts +38 -0
  42. package/dist/cli/commands/setup.js +1 -0
  43. package/dist/cli/commands/skills-install.d.ts +30 -0
  44. package/dist/cli/commands/skills-install.js +1 -0
  45. package/dist/cli/commands/skills.js +1 -1
  46. package/dist/cli/index.js +1 -1
  47. package/dist/cli/utils/catalog-status.d.ts +20 -0
  48. package/dist/cli/utils/catalog-status.js +1 -0
  49. package/dist/cli/utils/chat-renderer.d.ts +1 -0
  50. package/dist/cli/utils/chat-renderer.js +1 -1
  51. package/dist/cli/utils/git-source.d.ts +7 -0
  52. package/dist/cli/utils/git-source.js +1 -0
  53. package/dist/cli/utils/tool-format.d.ts +1 -0
  54. package/dist/cli/utils/tool-format.js +1 -1
  55. package/dist/config/defaults.js +1 -1
  56. package/dist/config/helpers.d.ts +17 -0
  57. package/dist/config/helpers.js +1 -1
  58. package/dist/config/key-codec.js +1 -1
  59. package/dist/config/loader.d.ts +2 -0
  60. package/dist/config/loader.js +1 -1
  61. package/dist/config/migration.d.ts +5 -1
  62. package/dist/config/migration.js +1 -1
  63. package/dist/config/runtime-env.d.ts +30 -30
  64. package/dist/config/schema.d.ts +250 -0
  65. package/dist/config/schema.js +1 -1
  66. package/dist/llm/providers.d.ts +12 -0
  67. package/dist/llm/providers.js +1 -1
  68. package/dist/mcp/client-manager.d.ts +5 -1
  69. package/dist/mcp/client-manager.js +1 -1
  70. package/dist/mcp/sampling-handler.d.ts +14 -2
  71. package/dist/mcp/sampling-handler.js +1 -1
  72. package/dist/registry/catalog-discovery.d.ts +30 -0
  73. package/dist/registry/catalog-discovery.js +1 -0
  74. package/dist/registry/catalog.d.ts +42 -0
  75. package/dist/registry/catalog.js +1 -0
  76. package/dist/registry/discovery.js +1 -1
  77. package/dist/registry/index.d.ts +4 -0
  78. package/dist/registry/index.js +1 -1
  79. package/dist/registry/install.d.ts +69 -0
  80. package/dist/registry/install.js +1 -0
  81. package/dist/skills/documents.js +1 -1
  82. package/dist/skills/install.d.ts +17 -0
  83. package/dist/skills/install.js +1 -0
  84. package/dist/skills/library.d.ts +6 -0
  85. package/dist/skills/library.js +1 -1
  86. package/dist/skills/orchestrators.d.ts +39 -0
  87. package/dist/skills/orchestrators.js +1 -0
  88. package/package.json +3 -2
@@ -1 +1 @@
1
- import{randomUUID as i}from"node:crypto";import{createElement as t,useMemo as n,useState as o}from"react";import{Box as e,Static as s,useInput as r}from"ink";import{useSession as m}from"./use-session.js";import{HistoryItemView as a}from"./history-item.js";import{LiveRegion as l}from"./live-region.js";import{StatusLine as d}from"./status-line.js";import{TextPrompt as f}from"./text-prompt.js";import{ConfirmSelect as p}from"./confirm-select.js";import{SlashPopup as h}from"./slash-popup.js";import{buildSkillInvocationPrompt as u,buildSkillListLines as c,filterSlashEntries as g,parseSkillInvocation as v,SLASH_COMMANDS as k}from"./commands.js";import{bannerTextLine as j}from"../banner.js";import{cycleThinking as x}from"./thinking.js";function C(){return k.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(y){const{session:L,model:b,contextWindow:M,onUserSubmit:T,onExit:A}=y,$=y.availableSkills??[],{state:S,submit:w,compact:H,resolveConfirm:W,setDraft:D,setThinking:E,setAutoMode:P,toggleAutoMode:R,commitHistory:U}=m(L,{model:b,contextWindow:M,...y.initialHistory?{initialHistory:y.initialHistory}:{},...y.initialThinkingLevel?{initialThinkingLevel:y.initialThinkingLevel}:{},...y.onThinkingChange?{onThinkingChange:y.onThinkingChange}:{}}),q=n(()=>[...S.history],[S.history]),[z,B]=o(0),F="idle"===S.phase&&S.draft.startsWith("/"),G=F&&!0===S.draft.split(/\s+/).at(-1)?.startsWith("/"),I=G?g(S.draft,$):[],J=Math.max(I.length-1,0),K=Math.min(z,J);r((i,t)=>{t.tab&&t.shift?R():t.meta&&"."===i?E(x(S.status.thinkingLevel,1)):t.meta&&","===i&&E(x(S.status.thinkingLevel,-1))});const N=(i,t)=>{const n=i.trim();0!==n.length?(T(n),w(n,t)):D("")},O="confirm"===S.phase&&void 0!==S.pendingConfirm?t(p,{prompt:S.pendingConfirm.prompt,args:S.pendingConfirm.args,onDecide:W}):t(f,{value:S.draft,disabled:"idle"!==S.phase,slashActive:F,slashPopupActive:G,autoApprove:S.status.autoApprove,onChange:D,onSubmit:N,onSlashMove:i=>{B(t=>Math.min(Math.max(Math.min(t,J)+i,0),J))},onSlashComplete:()=>{const i=I[K];if(i){const t=S.draft.split(/\s+/);t[t.length-1]=i.name,D(`${t.join(" ")} `),B(0)}},onSlashRun:()=>{const t=S.draft.trim().split(/\s+/,1)[0]??"",n=k.some(i=>i.name===t),o=I[K];if(!n&&"skill"===o?.kind){const i=S.draft.split(/\s+/);return i[i.length-1]=o.name,D(`${i.join(" ")} `),void B(0)}(t=>{D("");const n=t.trim(),o=n.split(/\s+/),e=o[0]??"",s=(o[1]??"").toLowerCase(),r=S.status.thinkingLevel;if("/compact"===e)return void H();if("/think"===e)return void E("off"===s?"off":"on"===s?"off"===r?"medium":r:"off"===r?"medium":"off");if("/effort"===e)return void("low"===s||"medium"===s||"high"===s?E(s):U({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===s?P(!0):"off"===s?P(!1):R());if("/skills"===e){const t=(process.stdout.columns||80)-2,[n,...o]=c($,t);return void U({kind:"banner",id:i(),lines:[j(n??""),...o.map(i=>j(i,{dim:!0}))]})}if("/help"===e)return void U({kind:"notice",id:i(),text:C()});if("/exit"===e)return void A();const m=v(n,$);if(m)return 0===m.prompt.length?void D(`${m.skills.map(i=>`/${i.name}`).join(" ")} `):void N(n,u(m));U({kind:"notice",id:i(),text:`未知命令 ${e}`})})(n?S.draft:o?.name??S.draft)}});return t(e,{flexDirection:"column"},t(s,{items:q,children:i=>{const n="user"===i.kind||"assistant"===i.kind,o="tool"===i.kind||"denied"===i.kind;return t(e,{key:i.id,marginTop:n?1:0,marginLeft:o?3:1},t(a,{item:i}))}}),t(e,{marginLeft:1},t(l,{live:S.live})),t(d,{status:S.status}),F?t(h,{matches:I,selected:K}):null,O)}
1
+ import{randomUUID as i}from"node:crypto";import{createElement as t,useMemo as n,useState as o}from"react";import{Box as e,Static as s,useInput as r}from"ink";import{useSession as a}from"./use-session.js";import{HistoryItemView as m}from"./history-item.js";import{LiveRegion as l}from"./live-region.js";import{StatusLine as d}from"./status-line.js";import{TextPrompt as f}from"./text-prompt.js";import{ConfirmSelect as p}from"./confirm-select.js";import{SlashPopup as h}from"./slash-popup.js";import{buildSkillInvocationPrompt as c,buildSkillListLines as u,filterSlashEntries as g,parseSkillInvocation as k,SLASH_COMMANDS as v}from"./commands.js";import{bannerTextLine as j}from"../banner.js";import{cycleThinking as x}from"./thinking.js";function C(){return v.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(y){const{session:b,model:L,contextWindow:M,onUserSubmit:T,onExit:A}=y,$=y.availableSkills??[],{state:S,submit:w,compact:H,cancel:W,resolveConfirm:D,setDraft:E,setThinking:P,setAutoMode:R,toggleAutoMode:U,commitHistory:q}=a(b,{model:L,contextWindow:M,...y.initialHistory?{initialHistory:y.initialHistory}:{},...y.initialThinkingLevel?{initialThinkingLevel:y.initialThinkingLevel}:{},...y.onThinkingChange?{onThinkingChange:y.onThinkingChange}:{}}),z=n(()=>[...S.history],[S.history]),[B,F]=o(0),G="idle"===S.phase&&S.draft.startsWith("/"),I=G&&!0===S.draft.split(/\s+/).at(-1)?.startsWith("/"),J=I?g(S.draft,$):[],K=Math.max(J.length-1,0),N=Math.min(B,K);r((i,t)=>{"busy"===S.phase&&t.escape&&!t.meta?W():t.tab&&t.shift?U():t.meta&&"."===i?P(x(S.status.thinkingLevel,1)):t.meta&&","===i&&P(x(S.status.thinkingLevel,-1))});const O=(i,t)=>{const n=i.trim();0!==n.length?(T(n),w(n,t)):E("")},Q="confirm"===S.phase&&void 0!==S.pendingConfirm?t(p,{prompt:S.pendingConfirm.prompt,args:S.pendingConfirm.args,onDecide:D}):t(f,{value:S.draft,disabled:"idle"!==S.phase,slashActive:G,slashPopupActive:I,autoApprove:S.status.autoApprove,onChange:E,onSubmit:O,onSlashMove:i=>{F(t=>Math.min(Math.max(Math.min(t,K)+i,0),K))},onSlashComplete:()=>{const i=J[N];if(i){const t=S.draft.split(/\s+/);t[t.length-1]=i.name,E(`${t.join(" ")} `),F(0)}},onSlashRun:()=>{const t=S.draft.trim().split(/\s+/,1)[0]??"",n=v.some(i=>i.name===t),o=J[N];if(!n&&"skill"===o?.kind){const i=S.draft.split(/\s+/);return i[i.length-1]=o.name,E(`${i.join(" ")} `),void F(0)}(t=>{E("");const n=t.trim(),o=n.split(/\s+/),e=o[0]??"",s=(o[1]??"").toLowerCase(),r=S.status.thinkingLevel;if("/compact"===e)return void H();if("/think"===e)return void P("off"===s?"off":"on"===s?"off"===r?"medium":r:"off"===r?"medium":"off");if("/effort"===e)return void("low"===s||"medium"===s||"high"===s?P(s):q({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===s?R(!0):"off"===s?R(!1):U());if("/skills"===e){const t=(process.stdout.columns||80)-2,[n,...o]=u($,t);return void q({kind:"banner",id:i(),lines:[j(n??""),...o.map(i=>j(i,{dim:!0}))]})}if("/help"===e)return void q({kind:"notice",id:i(),text:C()});if("/exit"===e)return void A();const a=k(n,$);if(a)return 0===a.prompt.length?void E(`${a.skills.map(i=>`/${i.name}`).join(" ")} `):void O(n,c(a));q({kind:"notice",id:i(),text:`未知命令 ${e}`})})(n?S.draft:o?.name??S.draft)}});return t(e,{flexDirection:"column"},t(s,{items:z,children:i=>{const n="user"===i.kind||"assistant"===i.kind,o="tool"===i.kind||"denied"===i.kind||"cancelled"===i.kind;return t(e,{key:i.id,marginTop:n?1:0,marginLeft:o?3:1},t(m,{item:i}))}}),t(e,{marginLeft:1},t(l,{live:S.live})),t(d,{status:S.status}),G?t(h,{matches:J,selected:N}):null,Q)}
@@ -1 +1 @@
1
- import{SKILL_TOOL_ID as n}from"../../../skills/library.js";import{displayWidth as e}from"./markdown.js";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 t(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,t){if(e(n)<=t)return n;let i="",o=0;for(const r of n){const n=e(r);if(o+n>t-1)break;i+=r,o+=n}return`${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=t(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=[],o=new Set;for(;;){const n=/^(\/\S+)(\s*)/.exec(t);if(!n)break;const r=findSkillBySlashName(n[1]??"",e);if(!r)break;o.has(r.name)||(i.push(r),o.add(r.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 i=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const i=[...n].sort((n,e)=>n.name.localeCompare(e.name)),o=Math.min(Math.max(...i.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(i.length)} 个)· 用法: /<skill-name> 你的请求`,...i.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(o)} [${n.source}] ${t(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
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")}
@@ -0,0 +1 @@
1
+ export declare function displayWidth(text: string): number;
@@ -0,0 +1 @@
1
+ import t from"string-width";export function displayWidth(i){return t(i)}
@@ -0,0 +1,29 @@
1
+ import type { Key } from "ink";
2
+ import type { LineBufferState } from "./line-buffer.ts";
3
+ declare const EDITOR_KEY_BINDINGS: {
4
+ readonly leftArrow: "move-left";
5
+ readonly rightArrow: "move-right";
6
+ readonly "ctrl+leftArrow": "move-word-left";
7
+ readonly "meta+leftArrow": "move-word-left";
8
+ readonly "meta+b": "move-word-left";
9
+ readonly "ctrl+rightArrow": "move-word-right";
10
+ readonly "meta+rightArrow": "move-word-right";
11
+ readonly "meta+f": "move-word-right";
12
+ readonly home: "move-line-start";
13
+ readonly "ctrl+a": "move-line-start";
14
+ readonly end: "move-line-end";
15
+ readonly "ctrl+e": "move-line-end";
16
+ readonly upArrow: "move-up";
17
+ readonly downArrow: "move-down";
18
+ readonly backspace: "delete-backward";
19
+ readonly delete: "delete-forward";
20
+ readonly "ctrl+w": "delete-word-backward";
21
+ readonly "meta+backspace": "delete-word-backward";
22
+ readonly "ctrl+backspace": "delete-word-backward";
23
+ readonly "ctrl+u": "kill-line-start";
24
+ readonly "ctrl+k": "kill-line-end";
25
+ };
26
+ export type EditorCommand = (typeof EDITOR_KEY_BINDINGS)[keyof typeof EDITOR_KEY_BINDINGS];
27
+ export declare function resolveEditorCommand(input: string, key: Key): EditorCommand | undefined;
28
+ export declare function applyEditorCommand(command: EditorCommand, state: LineBufferState): LineBufferState;
29
+ export {};
@@ -0,0 +1 @@
1
+ import{deleteBackward as e,deleteForward as r,deleteWordBackward as o,killToLineEnd as t,killToLineStart as l,moveDown as d,moveLeft as n,moveRight as a,moveToLineEnd as i,moveToLineStart as w,moveUp as m,moveWordLeft as c,moveWordRight as v}from"./line-buffer.js";const f={leftArrow:"move-left",rightArrow:"move-right","ctrl+leftArrow":"move-word-left","meta+leftArrow":"move-word-left","meta+b":"move-word-left","ctrl+rightArrow":"move-word-right","meta+rightArrow":"move-word-right","meta+f":"move-word-right",home:"move-line-start","ctrl+a":"move-line-start",end:"move-line-end","ctrl+e":"move-line-end",upArrow:"move-up",downArrow:"move-down",backspace:"delete-backward",delete:"delete-forward","ctrl+w":"delete-word-backward","meta+backspace":"delete-word-backward","ctrl+backspace":"delete-word-backward","ctrl+u":"kill-line-start","ctrl+k":"kill-line-end"},s=["leftArrow","rightArrow","upArrow","downArrow","home","end","backspace","delete"];function k(e,r){const o=s.find(e=>r[e])??(1===e.length?e.toLowerCase():void 0);if(void 0!==o)return`${r.ctrl?"ctrl+":""}${r.meta?"meta+":""}${o}`}function u(e){return e in f}export function resolveEditorCommand(e,r){const o=k(e,r);if(void 0!==o&&u(o))return f[o]}const h={"move-left":n,"move-right":a,"move-word-left":c,"move-word-right":v,"move-line-start":w,"move-line-end":i,"move-up":m,"move-down":d,"delete-backward":e,"delete-forward":r,"delete-word-backward":o,"kill-line-start":l,"kill-line-end":t};export function applyEditorCommand(e,r){return h[e](r)}
@@ -1 +1 @@
1
- import{createElement as r}from"react";import{Box as o,Text as t}from"ink";import{parseThinking as e}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as l}from"./tool-label.js";const i=["已取消执行","策略拒绝执行"];function c(r){const o=r.trimStart();return i.some(r=>o.startsWith(r))}export function HistoryItemView({item:i}){switch(i.kind){case"banner":return r(o,{flexDirection:"column"},...i.lines.map((o,e)=>r(t,{key:String(e)},...o.spans.map((o,e)=>r(t,{key:String(e),...void 0!==o.color?{color:o.color}:{},...!0===o.dim?{dimColor:!0}:{}},o.text)))));case"user":return r(o,null,r(t,{color:"cyan",bold:!0},"▌ "),r(t,{color:"cyan"},i.text));case"assistant":return c(i.text)?r(o,null,r(t,{dimColor:!0},"⊘ "),r(t,{dimColor:!0},i.text.trim())):r(o,{flexDirection:"column"},...e(i.text).map((o,e)=>o.thinking?r(t,{key:String(e),dimColor:!0},o.text):r(n,{key:String(e),text:o.text})));case"tool":{const o=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return r(t,null,r(t,i.ok?{color:"green"}:{color:"red"},i.ok?"✓ ":"✗ "),r(l,{name:i.name}),o.length>0?r(t,{dimColor:!0},o):null)}case"denied":return r(t,{dimColor:!0},`⊘ ${i.name} ${i.label}`);case"compaction":return r(t,{dimColor:!0},i.notice);case"notice":return r(o,null,r(t,{color:"yellow"},"⚠ "),r(t,{color:"yellow"},i.text));case"error":return r(o,null,r(t,{color:"red"},"✗ "),r(t,{color:"red"},i.message));default:return r(t,null,"")}}
1
+ import{createElement as r}from"react";import{Box as o,Text as e}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";const i=["已取消执行","策略拒绝执行"];function c(r){const o=r.trimStart();return i.some(r=>o.startsWith(r))}export function HistoryItemView({item:i}){switch(i.kind){case"banner":return r(o,{flexDirection:"column"},...i.lines.map((o,t)=>r(e,{key:String(t)},...o.spans.map((o,t)=>r(e,{key:String(t),...void 0!==o.color?{color:o.color}:{},...!0===o.dim?{dimColor:!0}:{}},o.text)))));case"user":return r(o,null,r(e,{color:"cyan",bold:!0},"▌ "),r(e,{color:"cyan"},i.text));case"assistant":return c(i.text)?r(o,null,r(e,{dimColor:!0},"⊘ "),r(e,{dimColor:!0},i.text.trim())):r(o,{flexDirection:"column"},...t(i.text).map((o,t)=>o.thinking?r(e,{key:String(t),dimColor:!0},o.text):r(n,{key:String(t),text:o.text})));case"tool":{const o=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return r(e,null,r(e,i.ok?{color:"green"}:{color:"red"},i.ok?"✓ ":"✗ "),r(l,{name:i.name}),o.length>0?r(e,{dimColor:!0},o):null)}case"denied":return r(e,{dimColor:!0},`⊘ ${i.name} ${i.label}`);case"cancelled":{const o=i.args.length>0&&"{}"!==i.args?` ${i.args}`:"";return r(e,{color:"yellow"},"■ ",r(l,{name:i.name}),o.length>0?r(e,{dimColor:!0},o):null,r(e,{dimColor:!0}," 已中断"))}case"compaction":return r(e,{dimColor:!0},i.notice);case"notice":return r(o,null,r(e,{color:"yellow"},"⚠ "),r(e,{color:"yellow"},i.text));case"error":return r(o,null,r(e,{color:"red"},"✗ "),r(e,{color:"red"},i.message));default:return r(e,null,"")}}
@@ -0,0 +1,21 @@
1
+ export interface LineBufferState {
2
+ readonly value: string;
3
+ readonly cursor: number;
4
+ readonly goalColumn: number | undefined;
5
+ }
6
+ export declare function createLineBuffer(value: string, cursor?: number): LineBufferState;
7
+ export declare function graphemeAt(value: string, offset: number): string;
8
+ export declare function insertText(state: LineBufferState, text: string): LineBufferState;
9
+ export declare function deleteBackward(state: LineBufferState): LineBufferState;
10
+ export declare function deleteForward(state: LineBufferState): LineBufferState;
11
+ export declare function deleteWordBackward(state: LineBufferState): LineBufferState;
12
+ export declare function killToLineStart(state: LineBufferState): LineBufferState;
13
+ export declare function killToLineEnd(state: LineBufferState): LineBufferState;
14
+ export declare function moveLeft(state: LineBufferState): LineBufferState;
15
+ export declare function moveRight(state: LineBufferState): LineBufferState;
16
+ export declare function moveWordLeft(state: LineBufferState): LineBufferState;
17
+ export declare function moveWordRight(state: LineBufferState): LineBufferState;
18
+ export declare function moveToLineStart(state: LineBufferState): LineBufferState;
19
+ export declare function moveToLineEnd(state: LineBufferState): LineBufferState;
20
+ export declare function moveUp(state: LineBufferState): LineBufferState;
21
+ export declare function moveDown(state: LineBufferState): LineBufferState;
@@ -0,0 +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 +1 @@
1
- import{createElement as n}from"react";import{Box as l,Text as t}from"ink";import{Spinner as e}from"./spinner.js";import{ThinkingText as i}from"./thinking-text.js";import{ToolLabel as o}from"./tool-label.js";export function LiveRegion({live:r}){return n(l,{flexDirection:"column"},r.streamingText.length>0?n(l,{marginTop:1},n(i,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText})):null,r.thinking?n(l,{marginTop:1},n(e,null),n(t,{dimColor:!0}," 思考中…")):null,...r.activeTools.map(i=>n(l,{key:i.toolCallId,marginLeft:2},n(e,null),n(t,null," "),n(o,{name:i.name}),i.args.length>0?n(t,{dimColor:!0},` ${i.args}`):null)),r.compacting?n(l,null,n(e,null),n(t,{dimColor:!0}," 压缩上下文中…")):null)}
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,5 +1,4 @@
1
1
  import type { ReactElement } from "react";
2
- export declare function displayWidth(text: string): number;
3
2
  export declare function Markdown({ text }: {
4
3
  text: string;
5
4
  }): ReactElement;
@@ -1 +1 @@
1
- import{createElement as e}from"react";import{Box as t,Text as r}from"ink";import{marked as n}from"marked";const o={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x27;":"'"};function c(e){return e.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g,e=>o[e]??e)}function a(t,n){return void 0===t?[]:t.map((t,o)=>{const s=`${n}-${String(o)}`;switch(t.type){case"strong":return e(r,{key:s,bold:!0},...a(t.tokens,s));case"em":return e(r,{key:s,italic:!0},...a(t.tokens,s));case"codespan":return e(r,{key:s,color:"yellow"},c(t.text));case"del":return e(r,{key:s,strikethrough:!0},...a(t.tokens,s));case"link":return e(r,{key:s},e(r,{color:"cyan",underline:!0},t.text),e(r,{dimColor:!0},` (${t.href})`));case"br":return"\n";case"text":return void 0!==t.tokens?e(r,{key:s},...a(t.tokens,s)):c(t.text);default:return c(t.raw)}})}const s=[[4352,4447],[9001,9002],[9989,9989],[10060,10062],[11035,11093],[11904,12350],[12353,13311],[13312,19903],[19968,40959],[40960,42191],[44032,55203],[63744,64255],[65040,65049],[65072,65135],[65280,65376],[65504,65510],[126976,129791],[131072,196605],[196608,262141]],i=[[768,879],[8203,8207],[65024,65039]];function u(e,t){return t.some(([t,r])=>e>=t&&e<=r)}export function displayWidth(e){let t=0;for(const r of e){const e=r.codePointAt(0)??0;u(e,i)||(t+=u(e,s)?2:1)}return t}function l(e){return void 0===e?"":e.map(e=>{switch(e.type){case"strong":case"em":case"del":return l(e.tokens);case"codespan":return c(e.text);case"link":return`${e.text} (${e.href})`;case"br":return"\n";case"text":return void 0!==e.tokens?l(e.tokens):c(e.text);default:return c(e.raw)}}).join("")}function k(e){return l(e.tokens).split("\n").reduce((e,t)=>Math.max(e,displayWidth(t)),0)}function d(n,o,c,s){return e(t,{key:c},...n.map((n,i)=>e(t,{key:`${c}-${String(i)}`,width:(o[i]??0)+2},e(r,s?{bold:!0}:{},...a(n.tokens,`${c}-${String(i)}`)))))}function m(n,o){const c=n.header.map((e,t)=>{let r=k(e);for(const e of n.rows){const n=e[t];void 0!==n&&(r=Math.max(r,k(n)))}return r}),a=e(t,{key:`${o}-sep`},...c.map((n,c)=>e(t,{key:`${o}-sep-${String(c)}`,width:n+2},e(r,{dimColor:!0},"─".repeat(n)))));return e(t,{key:o,flexDirection:"column"},d(n.header,c,`${o}-h`,!0),a,...n.rows.map((e,t)=>d(e,c,`${o}-r${String(t)}`,!1)))}function p(n,o){switch(n.type){case"space":return null;case"heading":return e(r,{key:o,bold:!0,color:"cyan"},...a(n.tokens,o));case"paragraph":return e(r,{key:o},...a(n.tokens,o));case"blockquote":return e(t,{key:o},e(r,{color:"gray"},"│ "),e(t,{flexDirection:"column"},...y(n.tokens??[],o)));case"code":return e(t,{key:o,paddingLeft:2},e(r,{dimColor:!0},n.text));case"list":return e(t,{key:o,flexDirection:"column"},...n.items.map((c,a)=>{const s=`${o}-${String(a)}`,i=n.ordered?`${String(Number(n.start||1)+a)}. `:"• ";return e(t,{key:s},e(r,{color:"cyan"},i),e(t,{flexDirection:"column"},...y(c.tokens,s)))}));case"hr":return e(r,{key:o,dimColor:!0},"────────");case"table":return m(n,o);case"text":return void 0!==n.tokens?e(r,{key:o},...a(n.tokens,o)):e(r,{key:o},c(n.text));default:return e(r,{key:o},n.raw)}}function y(r,n){return r.map((e,t)=>p(e,`${n}-${String(t)}`)).filter(e=>null!==e).map((r,o)=>0===o?r:e(t,{key:`${n}-gap-${String(o)}`,marginTop:1},r))}export function Markdown({text:o}){let c;try{c=n.lexer(o)}catch{return e(r,null,o)}return e(t,{flexDirection:"column"},...y(c,"md"))}
1
+ import{createElement as e}from"react";import{Box as t,Text as r}from"ink";import{marked as n}from"marked";import{displayWidth as o}from"./display-width.js";const a={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x27;":"'"};function c(e){return e.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g,e=>a[e]??e)}function s(t,n){return void 0===t?[]:t.map((t,o)=>{const a=`${n}-${String(o)}`;switch(t.type){case"strong":return e(r,{key:a,bold:!0},...s(t.tokens,a));case"em":return e(r,{key:a,italic:!0},...s(t.tokens,a));case"codespan":return e(r,{key:a,color:"yellow"},c(t.text));case"del":return e(r,{key:a,strikethrough:!0},...s(t.tokens,a));case"link":return e(r,{key:a},e(r,{color:"cyan",underline:!0},t.text),e(r,{dimColor:!0},` (${t.href})`));case"br":return"\n";case"text":return void 0!==t.tokens?e(r,{key:a},...s(t.tokens,a)):c(t.text);default:return c(t.raw)}})}function i(e){return void 0===e?"":e.map(e=>{switch(e.type){case"strong":case"em":case"del":return i(e.tokens);case"codespan":return c(e.text);case"link":return`${e.text} (${e.href})`;case"br":return"\n";case"text":return void 0!==e.tokens?i(e.tokens):c(e.text);default:return c(e.raw)}}).join("")}function u(e){return i(e.tokens).split("\n").reduce((e,t)=>Math.max(e,o(t)),0)}function l(n,o,a,c){return e(t,{key:a},...n.map((n,i)=>e(t,{key:`${a}-${String(i)}`,width:(o[i]??0)+2},e(r,c?{bold:!0}:{},...s(n.tokens,`${a}-${String(i)}`)))))}function k(n,o){const a=n.header.map((e,t)=>{let r=u(e);for(const e of n.rows){const n=e[t];void 0!==n&&(r=Math.max(r,u(n)))}return r}),c=e(t,{key:`${o}-sep`},...a.map((n,a)=>e(t,{key:`${o}-sep-${String(a)}`,width:n+2},e(r,{dimColor:!0},"─".repeat(n)))));return e(t,{key:o,flexDirection:"column"},l(n.header,a,`${o}-h`,!0),c,...n.rows.map((e,t)=>l(e,a,`${o}-r${String(t)}`,!1)))}function m(n,o){switch(n.type){case"space":return null;case"heading":return e(r,{key:o,bold:!0,color:"cyan"},...s(n.tokens,o));case"paragraph":return e(r,{key:o},...s(n.tokens,o));case"blockquote":return e(t,{key:o},e(r,{color:"gray"},"│ "),e(t,{flexDirection:"column"},...d(n.tokens??[],o)));case"code":return e(t,{key:o,paddingLeft:2},e(r,{dimColor:!0},n.text));case"list":return e(t,{key:o,flexDirection:"column"},...n.items.map((a,c)=>{const s=`${o}-${String(c)}`,i=n.ordered?`${String(Number(n.start||1)+c)}. `:"• ";return e(t,{key:s},e(r,{color:"cyan"},i),e(t,{flexDirection:"column"},...d(a.tokens,s)))}));case"hr":return e(r,{key:o,dimColor:!0},"────────");case"table":return k(n,o);case"text":return void 0!==n.tokens?e(r,{key:o},...s(n.tokens,o)):e(r,{key:o},c(n.text));default:return e(r,{key:o},n.raw)}}function d(r,n){return r.map((e,t)=>m(e,`${n}-${String(t)}`)).filter(e=>null!==e).map((r,o)=>0===o?r:e(t,{key:`${n}-gap-${String(o)}`,marginTop:1},r))}export function Markdown({text:o}){let a;try{a=n.lexer(o)}catch{return e(r,null,o)}return e(t,{flexDirection:"column"},...d(a,"md"))}
@@ -1,7 +1,7 @@
1
1
  import type { AgentSession } from "@roll-agent/runtime";
2
2
  import type { ThinkingLevel } from "../../../llm/providers.ts";
3
3
  import { type BannerInfo } from "../banner.ts";
4
- export declare const INK_HINTS = "/exit \u9000\u51FA \u00B7 / \u547D\u4EE4 \u00B7 Shift+Enter/Ctrl+J \u6362\u884C \u00B7 Alt+./Alt+, \u8C03\u63A8\u7406 \u00B7 Shift+Tab \u81EA\u52A8\u6279\u51C6";
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";
5
5
  export interface InkReplStore {
6
6
  updateTitle(threadId: string, title: string): void;
7
7
  countMessages(threadId: string): number;
@@ -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 r}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(a,l,m,d){let p=!1,c=!m;const g=t=>{p=!0,c||(l.updateTitle(a.id,o(t)),c=!0)};d.banner||r.info(`多轮对话已就绪(${INK_HINTS})`);const h=d.banner?[{kind:"banner",id:"banner",lines:s(d.banner,process.stdout.columns||80,{hints:INK_HINTS})}]:[],u=!0===process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode;let f;u&&process.stdin.setRawMode(!0);try{f=e(t(i,{session:a,model:d.model,contextWindow:a.getContextWindow(),availableSkills:a.getSkillSummaries(),onUserSubmit:g,onExit:()=>{f.unmount()},initialHistory:[...h,...n(a.getMessages())],...d.initialThinkingLevel?{initialThinkingLevel:d.initialThinkingLevel}:{},...d.onThinkingChange?{onThinkingChange:d.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}})}catch(t){throw u&&process.stdin.setRawMode(!1),t}await f.waitUntilExit(),a.abort(),m&&!p&&0===l.countMessages(a.id)&&l.deleteThread(a.id)}
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 r}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · Esc 中断 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(a,l,m,d){let p=!1,c=!m;const g=t=>{p=!0,c||(l.updateTitle(a.id,o(t)),c=!0)};d.banner||r.info(`多轮对话已就绪(${INK_HINTS})`);const h=d.banner?[{kind:"banner",id:"banner",lines:s(d.banner,process.stdout.columns||80,{hints:INK_HINTS})}]:[],u=!0===process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode;let f;u&&process.stdin.setRawMode(!0);try{f=e(t(i,{session:a,model:d.model,contextWindow:a.getContextWindow(),availableSkills:a.getSkillSummaries(),onUserSubmit:g,onExit:()=>{f.unmount()},initialHistory:[...h,...n(a.getMessages())],...d.initialThinkingLevel?{initialThinkingLevel:d.initialThinkingLevel}:{},...d.onThinkingChange?{onThinkingChange:d.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}})}catch(t){throw u&&process.stdin.setRawMode(!1),t}await f.waitUntilExit(),a.abort(),m&&!p&&0===l.countMessages(a.id)&&l.deleteThread(a.id)}
@@ -5,6 +5,7 @@ export interface ToolRowState {
5
5
  readonly toolCallId: string;
6
6
  readonly name: string;
7
7
  readonly args: string;
8
+ readonly outputTail?: string;
8
9
  }
9
10
  export type HistoryItem = {
10
11
  readonly kind: "banner";
@@ -29,6 +30,11 @@ export type HistoryItem = {
29
30
  readonly id: string;
30
31
  readonly name: string;
31
32
  readonly label: string;
33
+ } | {
34
+ readonly kind: "cancelled";
35
+ readonly id: string;
36
+ readonly name: string;
37
+ readonly args: string;
32
38
  } | {
33
39
  readonly kind: "compaction";
34
40
  readonly id: string;
@@ -1 +1 @@
1
- import{formatToolInput as t}from"../../utils/tool-format.js";import{GLYPHS as e}from"../../utils/glyphs.js";import{endsInsideThink as i}from"./thinking-text.js";const n={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function o(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,i){return{history:i?.history??[],draft:"",live:n,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 r(t){const i="auto"===t.reason?"自动压缩":"手动压缩",n=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`${e.compact} ${i}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${n}`:`${e.compact} ${i}:无需压缩`}const s=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function a(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=a(t);if(void 0!==e)return s.find(([t])=>e.startsWith(t))?.[1]}function u(t,e,i){const n=t.live.activeTools.find(t=>t.toolCallId===i.toolCallId),o=n?.name??`${i.agentName}.${i.toolName}`,r=n?.args??"",s=i.isError?denialLabel(i.output):void 0,a=void 0!==s?{kind:"denied",id:e,name:o,label:s}:{kind:"tool",id:e,name:o,args:r,ok:!i.isError};return{...t,history:[...t.history,a],live:{...t.live,activeTools:t.live.activeTools.filter(t=>t.toolCallId!==i.toolCallId)}}}function l(e,s,a){switch(a.type){case"message-start":return{...e,live:{...e.live,thinking:!0}};case"text-delta":return{...e,live:{...e.live,thinking:!1,producedOutput:!0,streamingText:e.live.streamingText+a.delta}};case"tool-call":{const n=e.live.streamingText.length>0?[{kind:"assistant",id:s,text:o(e.live.streamingText,e.live.thinkTagOpen)}]:[];return{...e,history:[...e.history,...n],live:{...e.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:i(e.live.streamingText,e.live.thinkTagOpen),activeTools:[...e.live.activeTools,{toolCallId:a.toolCallId,name:`${a.agentName}.${a.toolName}`,args:t(a.input)}]}}}case"tool-result":return u(e,s,a);case"confirmation-required":return{...e,phase:"confirm",pendingConfirm:{approvalId:a.approvalId,prompt:buildConfirmPrompt(a),args:t(a.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:s,notice:r(a)}]};case"message-finish":{const t=[];return e.live.streamingText.length>0?t.push({kind:"assistant",id:s,text:o(e.live.streamingText,e.live.thinkTagOpen)}):!e.live.producedOutput&&(a.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:s,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),a.stoppedAtStepLimit&&t.push({kind:"notice",id:`${s}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...e,history:[...e.history,...t],live:{...n},status:{...e.status,turnUsage:a.totalUsage,sessionUsage:a.sessionUsage,contextInputTokens:a.contextInputTokens,outputTokensPerSecond:a.outputTokensPerSecond}}}case"error":return{...e,history:[...e.history,{kind:"error",id:s,message:a.message}],live:{...n}};default:return e}}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:{...n},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:{...n},phase:"busy",pendingConfirm:void 0};case"session-event":return l(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...n}};default:return t}}
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 +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 c,formatTurnUsage as l}from"../../utils/token-format.js";import{displayWidth as u}from"./markdown.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"}},f={off:"off",low:"low",medium:"med",high:"high"},d=["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} ${f[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 ${c(e.usedTokens)}/${c(e.contextWindow)}`,props:k[p(e.percentLeft)]});const v=l(e);if(void 0!==v){const o=[];void 0!==e.inputTokens&&o.push(`↑${c(e.inputTokens)}`),void 0!==e.outputTokens&&o.push(`↓${c(e.outputTokens)}`),s.push({key:"turn",full: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 ${c(e.sessionTokens)}`,compact:`Σ${c(e.sessionTokens)}`,props:{dimColor:!0}});const w=(o,t)=>o.map(o=>({key:o.key,text:o[t],props:o.props})),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 d){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,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)])))}
@@ -11,4 +11,8 @@ export interface TextPromptProps {
11
11
  readonly onSlashComplete: () => void;
12
12
  readonly onSlashRun: () => void;
13
13
  }
14
+ export declare function cursorPositionOf(lines: readonly string[], cursor: number): {
15
+ row: number;
16
+ col: number;
17
+ };
14
18
  export declare function TextPrompt(props: TextPromptProps): ReactElement;
@@ -1 +1 @@
1
- import{createElement as r,useRef as t}from"react";import{Box as e,Text as n,useInput as o,useStdout as i}from"ink";import{GLYPHS as l}from"../../utils/glyphs.js";function u(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function TextPrompt(c){const{value:s,disabled:a,slashActive:d,slashPopupActive:f,autoApprove:m,onChange:p,onSubmit:h}=c,{stdout:v}=i(),b=v.columns||80,S=t(s);S.current=s;const g=r=>{S.current=r,p(r)};o((r,t)=>{if(t.meta&&("."===r||","===r))return;if(u(r))return;if(t.backspace||t.delete)return void g(S.current.slice(0,-1));if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void g(`${S.current}\n`);if(t.return||r.includes("\r")){if(d)return void c.onSlashRun();const t=r.split("\r",1)[0]??"";return void h(S.current+t)}if(f){if(t.upArrow)return void c.onSlashMove(-1);if(t.downArrow)return void c.onSlashMove(1);if(t.tab&&!t.shift)return void c.onSlashComplete()}d&&t.escape?g(""):t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&g(S.current+r)},{isActive:!a});const w=s.split("\n"),A=r(e,{flexDirection:"column"},...w.map((t,o)=>{const i=o===w.length-1,l=0===o?r(n,{color:"green"},"› "):r(n,null," ");return r(e,{key:String(o)},l,r(n,a?{dimColor:!0}:{},t),i&&!a?r(n,null,""):null)})),C=d?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":m?"Shift+Tab 关闭 · Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",E=r(e,{marginLeft:1},...m?[r(n,{color:"yellow"},`${l.auto} auto`),r(n,{dimColor:!0},` · ${C}`)]:[r(n,{dimColor:!0},C)]);return r(e,{flexDirection:"column",width:b},r(e,{borderStyle:"round",borderColor:a?"gray":"cyan",paddingX:2},A),E)}
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)}
@@ -12,6 +12,7 @@ export interface UseSessionResult {
12
12
  readonly state: ChatUiState;
13
13
  readonly submit: (text: string, sendText?: string) => void;
14
14
  readonly compact: () => void;
15
+ readonly cancel: () => void;
15
16
  readonly resolveConfirm: (approved: boolean) => void;
16
17
  readonly setDraft: (value: string) => void;
17
18
  readonly setThinking: (level: ThinkingLevel) => void;
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{useCallback as t,useReducer as n,useRef as i}from"react";import{chatReducer as r,createInitialState as o}from"./state.js";import{log as s}from"../../utils/output.js";const a=32;function c(e){return e instanceof Error?e.message:String(e)}export function useSession(u,p){const[l,m]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),d=p.onThinkingChange,y=i(null),v=i(!1),f=i(!1),g=t(async t=>{let n,i="";const r=()=>{if(void 0!==n&&(clearTimeout(n),n=void 0),i.length>0){const t=i;i="",m({type:"session-event",id:e(),event:{type:"text-delta",delta:t}})}};try{for await(const o of t)if("debug"!==o.type)if("text-delta"!==o.type){if(r(),"confirmation-required"===o.type){if(v.current){u.approve(o.approvalId);continue}const t=new Promise(e=>{y.current=e});m({type:"session-event",id:e(),event:o});const n=await t;y.current=null,n?u.approve(o.approvalId):u.reject(o.approvalId,"用户取消"),m({type:"confirm-resolved"});continue}m({type:"session-event",id:e(),event:o})}else i+=o.delta,void 0===n&&(n=setTimeout(r,a));else s.debug(`chat.${o.stage} ${o.message}`)}catch(t){r(),m({type:"session-event",id:e(),event:{type:"error",stage:"execute",message:c(t)}})}finally{r(),m({type:"turn-end"}),f.current=!1}},[u]),h=t((t,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(e=>{y.current?.(e)},[]),T=t(e=>{m({type:"set-draft",value:e})},[]),b=t(e=>{m({type:"set-thinking",level:e}),d?.(e)},[d]),w=t(e=>{v.current=e,m({type:"set-auto",value:e}),e&&y.current?.(!0)},[]),j=t(()=>{w(!v.current)},[w]),H=t(e=>{m({type:"commit-history",item:e})},[]);return{state:l,submit:h,compact:k,resolveConfirm:x,setDraft:T,setThinking:b,setAutoMode:w,toggleAutoMode:j,commitHistory:H}}
1
+ 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 +1 @@
1
- import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r,mkdirSync as i}from"node:fs";import{execFile as o}from"node:child_process";import{promisify as n}from"node:util";import{inspectAgentEnvRequirements as s}from"../../config/helpers.js";import{loadAgentsConfig as a}from"../../config/loader.js";import{discoverAgent as p}from"../../registry/discovery.js";import{writeRemoteSkillManifest as c}from"../../registry/manifest.js";import{AgentStore as d}from"../../registry/store.js";import{log as l}from"../utils/output.js";import{createInstallCommand as m,detectInstallCommand as g,formatPackageManagerCommand as f,formatPackageManagerError as u,runPackageManager as h}from"../utils/package-manager.js";const y=n(o);function $(e){return e.startsWith("https://")||e.startsWith("http://")||e.startsWith("git@")||e.endsWith(".git")}function v(e){return(e.split("/").pop()??e).replace(/\.git$/,"")}export default e({meta:{description:"注册本地目录、Git 仓库或远程 MCP endpoint"},args:{path:{type:"positional",description:"本地 Agent 目录或 Git 仓库 URL;远程 endpoint 请用 --remote",required:!1},remote:{type:"string",description:"远程 MCP endpoint;需要同时提供 --name 和 --description"},name:{type:"string",description:"远程 Agent 名称(仅 --remote 时使用)"},description:{type:"string",description:"远程 Agent 描述(仅 --remote 时使用)"}},async run({args:e}){const{agentsConfig:o}=a();let n;if(e.remote){if(!e.name||!e.description)return l.error("远程注册需要同时提供 --name 和 --description"),void(process.exitCode=1);n=c({dataDir:o.dataDir,name:e.name,description:e.description,endpoint:e.remote})}else{if(!e.path)return l.error("请提供本地路径、Git URL,或使用 --remote <endpoint> 注册远程 Agent"),void(process.exitCode=1);if($(e.path)){const s=v(e.path),a=t(o.dataDir,"repos",s);if(r(a)){l.info(`仓库目录已存在,拉取最新代码: ${a}`);try{await y("git",["pull"],{cwd:a})}catch(e){return l.error(`git pull 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}}else{l.info(`克隆 ${e.path}...`);const n=t(o.dataDir,"repos");r(n)||i(n,{recursive:!0});try{await y("git",["clone",e.path,a]),l.success("克隆完成")}catch(e){return l.error(`git clone 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}}n=a}else if(n=t(e.path),!r(n))return l.error(`路径不存在: ${n}`),void(process.exitCode=1)}l.info("解析 SKILL.md...");const s=p(n);l.debug(`名称: ${s.skill.name}`),l.debug(`描述: ${s.skill.description}`),l.debug(`传输: ${s.transport.type}`);const L=t(n,"package.json"),A="1"===process.env.ROLL_SKIP_INSTALL;if(e.remote)l.info("远程 Agent 使用本地 manifest,无需安装依赖。");else if(r(L)&&!A){const e=g(n)??m("pnpm");l.info(`安装依赖 (${f(e)})...`);try{await h(e,{cwd:n}),l.success("依赖安装完成")}catch(t){return l.error(`依赖安装失败: ${u(e,t)}`),void(process.exitCode=1)}}else r(L)&&A&&l.warn("检测到 ROLL_SKIP_INSTALL=1,跳过依赖安装。");const j=e.remote?{type:"remote-manifest",endpoint:e.remote}:e.path&&$(e.path)?{type:"git",url:e.path}:{type:"local-path",path:n},w=new d(o.dataDir),C={skill:s.skill,transport:s.transport,runtime:s.runtime,installPath:n,registeredAt:(new Date).toISOString(),status:"idle",source:j,...s.skillBody.length>0?{skillBody:s.skillBody}:{}};try{w.add(C),l.success(`Agent "${s.skill.name}" 注册成功`),k(s.skill.name,s.skill.env,o.env)}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function k(e,t,r){const i=s(e,t,r);if(i)return i.missingRequired.length>0?(l.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),l.info(`运行 \`roll config setup agent ${e}\` 交互式配置。`),void l.info(`运行 \`roll config explain agents.env.${e}\` 查看配置说明。`)):void(i.processEnvOnlyRequired.length>0&&(l.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),l.info(`建议运行 \`roll config setup agent ${e}\` 持久写入 roll.config.yaml。`)))}
1
+ import{defineCommand as e}from"citty";import{resolve as t}from"node:path";import{existsSync as r}from"node:fs";import{inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{loadAgentsConfig as o}from"../../config/loader.js";import{discoverAgent as s}from"../../registry/discovery.js";import{writeRemoteSkillManifest as n}from"../../registry/manifest.js";import{AgentStore as a}from"../../registry/store.js";import{cloneOrPullRepo as p,isGitUrl as m,repoNameFromUrl as c}from"../utils/git-source.js";import{log as d}from"../utils/output.js";import{reportAgentEnvGuidance as l}from"./agent-env-guidance.js";import{createInstallCommand as g,detectInstallCommand as f,formatPackageManagerCommand as u,formatPackageManagerError as y,runPackageManager as h}from"../utils/package-manager.js";export default e({meta:{description:"注册本地目录、Git 仓库或远程 MCP endpoint"},args:{path:{type:"positional",description:"本地 Agent 目录或 Git 仓库 URL;远程 endpoint 请用 --remote",required:!1},remote:{type:"string",description:"远程 MCP endpoint;需要同时提供 --name 和 --description"},name:{type:"string",description:"远程 Agent 名称(仅 --remote 时使用)"},description:{type:"string",description:"远程 Agent 描述(仅 --remote 时使用)"}},async run({args:e}){const{agentsConfig:k}=o();let L;if(e.remote){if(!e.name||!e.description)return d.error("远程注册需要同时提供 --name 和 --description"),void(process.exitCode=1);L=n({dataDir:k.dataDir,name:e.name,description:e.description,endpoint:e.remote})}else{if(!e.path)return d.error("请提供本地路径、Git URL,或使用 --remote <endpoint> 注册远程 Agent"),void(process.exitCode=1);if(m(e.path)){const i=c(e.path),o=t(k.dataDir,"repos",i),s=r(o);s?d.info(`仓库目录已存在,拉取最新代码: ${o}`):d.info(`克隆 ${e.path}...`);try{const{action:t}=await p(e.path,o);"cloned"===t&&d.success("克隆完成")}catch(e){return d.error(`${s?"git pull":"git clone"} 失败: ${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}L=o}else if(L=t(e.path),!r(L))return d.error(`路径不存在: ${L}`),void(process.exitCode=1)}d.info("解析 SKILL.md...");const $=s(L);d.debug(`名称: ${$.skill.name}`),d.debug(`描述: ${$.skill.description}`),d.debug(`传输: ${$.transport.type}`);const j=t(L,"package.json"),v="1"===process.env.ROLL_SKIP_INSTALL;if(e.remote)d.info("远程 Agent 使用本地 manifest,无需安装依赖。");else if(r(j)&&!v){const e=f(L)??g("pnpm");d.info(`安装依赖 (${u(e)})...`);try{await h(e,{cwd:L}),d.success("依赖安装完成")}catch(t){return d.error(`依赖安装失败: ${y(e,t)}`),void(process.exitCode=1)}}else r(j)&&v&&d.warn("检测到 ROLL_SKIP_INSTALL=1,跳过依赖安装。");const A=e.remote?{type:"remote-manifest",endpoint:e.remote}:e.path&&m(e.path)?{type:"git",url:e.path}:{type:"local-path",path:L},C=new a(k.dataDir),S={skill:$.skill,transport:$.transport,runtime:$.runtime,installPath:L,registeredAt:(new Date).toISOString(),status:"idle",source:A,...$.skillBody.length>0?{skillBody:$.skillBody}:{}};try{C.add(S),d.success(`Agent "${$.skill.name}" 注册成功`),l($.skill.name,i($.skill.name,$.skill.env,k.env))}catch(e){d.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});
@@ -0,0 +1,2 @@
1
+ import type { AgentEnvCheckReport } from "../../config/helpers.ts";
2
+ export declare function reportAgentEnvGuidance(agentName: string, envReport: AgentEnvCheckReport | undefined): void;
@@ -0,0 +1 @@
1
+ import{log as n}from"../utils/output.js";export function reportAgentEnvGuidance(e,i){if(i)return i.missingRequired.length>0?(n.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(n=>n.name).join(", ")}`),n.info(`运行 \`roll config setup agent ${e}\` 交互式配置。`),void n.info(`运行 \`roll config explain agents.env.${e}\` 查看配置说明。`)):void(i.processEnvOnlyRequired.length>0&&(n.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(n=>n.name).join(", ")}`),n.info(`建议运行 \`roll config setup agent ${e}\` 持久写入 roll.config.yaml。`)))}
@@ -14,5 +14,10 @@ declare const _default: import("citty").CommandDef<{
14
14
  description: string;
15
15
  default: true;
16
16
  };
17
+ force: {
18
+ type: "boolean";
19
+ description: string;
20
+ default: false;
21
+ };
17
22
  }>;
18
23
  export default _default;
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{existsSync as t,mkdirSync as r,statSync as i}from"node:fs";import{resolve as s}from"node:path";import{getAgentEnv as n,inspectAgentEnvRequirements as o}from"../../config/helpers.js";import{loadAgentsConfig as a,loadConfig as l,loadInstallConfig as p}from"../../config/loader.js";import{discoverAgent as m}from"../../registry/discovery.js";import{startAgent as c,stopAgentGracefully as g,waitForAgentReady as d}from"../../registry/process-manager.js";import{runAgentSetup as u}from"../../registry/runtime-setup.js";import{AgentStore as f}from"../../registry/store.js";import{parsePackageName as y,readInstalledPackageManifest as k,resolveInstalledPackageRoot as h,sanitizeInstallId as w}from"../../registry/source.js";import{log as $}from"../utils/output.js";import{buildNpmRetryPolicy as v,formatPackageManagerError as j,npmInstallNetworkArgs as C,runPackageManagerWithRetry as S}from"../utils/package-manager.js";function b(e){return C({...e.registry?{registry:e.registry}:{},fetchRetries:e.fetchRetries,preferOffline:e.preferOffline})}function x(e){return e.startsWith("git@")||e.startsWith("git+")||e.startsWith("github:")||e.startsWith("gitlab:")||e.startsWith("bitbucket:")||e.endsWith(".git")}export default e({meta:{description:"安装已发布的 Agent npm 包并注册到本地"},args:{package:{type:"positional",description:"npm 包名、版本范围或 .tgz 路径(源码目录请用 roll agent add)",required:!0},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1},start:{type:"boolean",description:"安装后不自动启动 core-managed Agent",default:!0}},async run({args:e}){const{agentsConfig:o}=a();let C;try{C=p().installConfig}catch(e){return $.error(`install 配置无效,已停止安装:${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}const R=e.package;if(x(R))return $.error(`Git URL 请使用 \`roll agent add ${R}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const D=s(R);if(t(D)&&i(D).isDirectory())return $.error(`本地源码目录请使用 \`roll agent add ${R}\` 注册,不要使用 \`roll agent install\``),void(process.exitCode=1);const W=y(R),q=s(o.dataDir,"installed",w(W));t(q)||r(q,{recursive:!0}),C.registry&&$.info(`使用 npm registry: ${C.registry}(roll.config.yaml install.registry)`),$.info(`安装 ${R}...`);const B={command:"npm",args:["install","--prefix",q,R,...b(C)]};try{await S(B,{timeout:C.networkTimeoutMs},{...v(C.fetchRetries),onRetry:({attempt:e,delayMs:t})=>{$.warn(`安装遇到网络问题,${Math.round(t/1e3)}s 后重试(第 ${e+1} 次)...`)}})}catch(e){return $.error(`安装失败: ${j(B,e)}`),void(process.exitCode=1)}const E=h(q,W);if(!t(E))return $.error(`安装完成但未找到包目录: ${E}`),void(process.exitCode=1);const M=k(E);$.info("解析已安装 Agent 的 SKILL.md...");const O=m(E),L=new f(o.dataDir),T={skill:O.skill,transport:O.transport,runtime:O.runtime,installPath:E,registeredAt:(new Date).toISOString(),status:"idle",source:{type:"installed-package",packageName:M?.name??W,packageSpec:R,installDir:q,...M?.version?{installedVersion:M.version}:{}},...O.skillBody.length>0?{skillBody:O.skillBody}:{}};"core-managed"===T.runtime.ownership&&T.runtime.setup?.playwright&&!e["skip-browser-setup"]&&$.info(`即将安装浏览器运行时 (${T.runtime.setup.playwright.browsers.join(", ")}),这可能需要一些时间...`);const I=await u(T,{skipBrowserSetup:e["skip-browser-setup"]});I.ok?I.skipped?$.info(I.message):$.success(I.message):($.warn(`Agent setup 失败:${I.message}`),I.retryCommand&&$.info(`重试命令: ${I.retryCommand}`));const N=L.findByName(O.skill.name);try{const t="core-managed"===N?.runtime.ownership&&"online"===N.status;if("installed-package"===N?.source?.type?L.replace(N.skill.name,T):L.add(T),!I.ok)return L.updateStatus(O.skill.name,"error"),void(process.exitCode=1);if("core-managed"===T.runtime.ownership&&e.start){t&&await g(o.dataDir,T.skill.name),L.updateStatus(T.skill.name,"starting");let e=!1;try{c(T,o.dataDir,n(l().config,T.skill.name)),e=!0,await d(T,{startupTimeoutMs:15e3,probeTimeoutMs:2e3}),L.updateStatus(T.skill.name,"online")}catch(t){return e&&await g(o.dataDir,T.skill.name).catch(()=>{}),L.updateStatus(T.skill.name,"error"),$.error(`Agent "${O.skill.name}" 已安装,但自动启动失败:${t instanceof Error?t.message:String(t)}`),void(process.exitCode=1)}}$.success(`Agent "${O.skill.name}" 安装并注册成功`),A(O.skill.name,O.skill.env,o.env)}catch(e){$.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}});function A(e,t,r){const i=o(e,t,r);if(i)return i.missingRequired.length>0?($.warn(`Agent "${e}" 仍缺少必填环境变量: ${i.missingRequired.map(e=>e.name).join(", ")}`),$.info(`运行 \`roll config setup agent ${e}\` 交互式配置。`),void $.info(`运行 \`roll config explain agents.env.${e}\` 查看配置说明。`)):void(i.processEnvOnlyRequired.length>0&&($.warn(`Agent "${e}" 当前依赖 shell 环境变量: ${i.processEnvOnlyRequired.map(e=>e.name).join(", ")}`),$.info(`建议运行 \`roll config setup agent ${e}\` 持久写入 roll.config.yaml。`)))}
1
+ import{defineCommand as e}from"citty";import{getAgentEnv as t}from"../../config/helpers.js";import{loadAgentsConfig as r,loadConfig as o,loadInstallConfig as s}from"../../config/loader.js";import{catalogPackageSpec as a,findCatalogEntry as n}from"../../registry/catalog.js";import{resolveAgentCatalog as i}from"../../registry/catalog-discovery.js";import{installAgent as p}from"../../registry/install.js";import{log as g}from"../utils/output.js";import{reportAgentEnvGuidance as c}from"./agent-env-guidance.js";function l(e){if("retry"===e.type)return void g.warn(`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`);({step:g.info,info:g.info,warn:g.warn,success:g.success})[e.type](e.message)}function m(e){if("setup"===e.step)return g.warn(`Agent setup 失败:${e.message}`),void(e.retryCommand&&g.info(`重试命令: ${e.retryCommand}`));g.error(e.message)}export default e({meta:{description:"安装已发布的 Agent npm 包并注册到本地"},args:{package:{type:"positional",description:"官方 Agent 短名(roll agent list --available 查看)、npm 包名、版本范围或 .tgz 路径(源码目录请用 roll agent add)",required:!0},"skip-browser-setup":{type:"boolean",description:"跳过 Playwright 浏览器运行时安装/校验",default:!1},start:{type:"boolean",description:"安装后自动启动 core-managed Agent(可用 --no-start 跳过)",default:!0},force:{type:"boolean",description:"确认风险并替换同名的非 npm 来源 Agent",default:!1}},async run({args:e}){const{agentsConfig:f}=r();let d;try{d=s().installConfig}catch(e){return g.error(`install 配置无效,已停止安装:${e instanceof Error?e.message:String(e)}`),void(process.exitCode=1)}const u=await i(void 0,{...d.registry?{registry:d.registry}:{}}),y=n(u,e.package),k=y?a(y.entry,y.versionSpec):e.package;y&&k!==e.package&&g.info(`已识别官方 Agent 短名:${e.package} ${k}`);const v=await p({packageSpec:k,skipBrowserSetup:e["skip-browser-setup"],autoStart:e.start,replaceExisting:e.force,...y?{expectedSkillName:y.entry.skillName}:{}},{agentsConfig:f,installConfig:d,getStartEnv:e=>t(o().config,e),report:l});if(!v.ok)return m(v),void(process.exitCode=1);g.success(`Agent "${v.agent.skill.name}" 安装并注册成功`),c(v.agent.skill.name,v.envReport)}});
@@ -4,5 +4,10 @@ declare const _default: import("citty").CommandDef<{
4
4
  description: string;
5
5
  default: false;
6
6
  };
7
+ available: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: false;
11
+ };
7
12
  }>;
8
13
  export default _default;
@@ -1 +1 @@
1
- import{defineCommand as t}from"citty";import o from"cli-table3";import{loadAgentsConfig as r}from"../../config/loader.js";import{formatAgentSourceType as n,getAgentLocation as e,inferAgentSourceType as s}from"../../registry/source.js";import{AgentStore as a}from"../../registry/store.js";import{formatLocationForDisplay as i,resolveTerminalColumns as l}from"../utils/terminal.js";const c=6,g=2;export default t({meta:{description:"列出所有已注册 Agent"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1}},run({args:t}){const{agentsConfig:c}=r(),g=new a(c.dataDir).list();if(t.json)return void console.log(JSON.stringify(g,null,2));if(0===g.length)return void console.log("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。");const u=m(l()),p=new o({head:["Name","Status","Source","Transport","Location"],colWidths:[...u],truncate:"…",style:{head:["cyan"]}});for(const t of g)p.push([t.skill.name,t.status,n(s(t)),t.transport.type,i(e(t),f(u[4]))]);console.log(p.toString())}});function m(t){const o=t-6,r=o-43-24;if(r>=15)return[24,10,14,19,r];const n=Math.max(16,o-43-15);return[n,10,14,19,o-43-n]}function f(t){return Math.max(1,t-2)}
1
+ import{defineCommand as t}from"citty";import e from"chalk";import n from"cli-table3";import{loadAgentsConfig as o,loadInstallConfig as r}from"../../config/loader.js";import{resolveAgentCatalog as s}from"../../registry/catalog-discovery.js";import{formatAgentSourceType as a,getAgentLocation as i,inferAgentSourceType as l}from"../../registry/source.js";import{AgentStore as c}from"../../registry/store.js";import{inspectCatalogAvailability as d}from"../utils/catalog-status.js";import{formatLocationForDisplay as g,resolveTerminalColumns as u}from"../utils/terminal.js";const m=6,p=2;export default t({meta:{description:"列出所有已注册 Agent"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1},available:{type:"boolean",description:"列出官方 catalog 中可安装的 Agent 及安装状态",default:!1}},async run({args:t}){const{agentsConfig:e}=o(),r=new c(e.dataDir).list();if(t.available)return void await y(r,t.json);if(t.json)return void console.log(JSON.stringify(r,null,2));if(0===r.length)return void console.log("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。");const s=h(u()),d=new n({head:["Name","Status","Source","Transport","Location"],colWidths:[...s],truncate:"…",style:{head:["cyan"]}});for(const t of r)d.push([t.skill.name,t.status,a(l(t)),t.transport.type,g(i(t),v(s[4]))]);console.log(d.toString())}});const f={"not-installed":t=>e.yellow("未安装")+(t.latestVersion?e.dim(` · 最新 v${t.latestVersion}`):""),installed:t=>{const n="installed-package"===t.installedAgent?.source?.type?t.installedAgent.source.installedVersion:void 0,o=e.green("已安装"+(n?` v${n}`:""));return"update-available"===t.update?.status&&t.latestVersion?`${o} ${e.yellow(`⬆ 可更新到 v${t.latestVersion}`)}`:o},"installed-other-source":t=>{const n=t.installedAgent?a(l(t.installedAgent)):"unknown";return e.green("已注册")+e.dim(`(${n} 来源)`)+(t.latestVersion?e.dim(` · npm 最新 v${t.latestVersion}`):"")}};async function y(t,n){let o;try{o=r().installConfig.registry}catch{o=void 0}const a=await s(void 0,{...o?{registry:o}:{}}),i=await d(a,t,{...o?{registry:o}:{}});if(n)return void console.log(JSON.stringify(i,null,2));const l=[];for(const t of i)l.push(`${e.cyan.bold(t.entry.shortName)} ${e.dim(t.entry.packageName)} ${f[t.state](t)}`),l.push(` ${t.entry.description}`),l.push(` ${e.dim("必需环境变量:")} ${t.entry.requiredEnv.length>0?t.entry.requiredEnv.join(", "):e.dim("安装后由 SKILL.md 声明")}`),l.push("");l.push("使用 `roll agent install <name>` 安装官方 Agent。"),console.log(l.join("\n"))}function h(t){const e=t-6,n=e-43-24;if(n>=15)return[24,10,14,19,n];const o=Math.max(16,e-43-15);return[o,10,14,19,e-43-o]}function v(t){return Math.max(1,t-2)}
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{getAgentEnv as t,getMissingAgentEnvRuntimeIssues as o,inspectAgentEnvRequirements as r}from"../../config/helpers.js";import{shouldSkipRuntimeReadinessForTool as n}from"../../config/runtime-env.js";import{createProviderModel as i,resolveLLMCall as a}from"../../llm/providers.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{AgentStore as m}from"../../registry/store.js";import{resolveTransportWithDevSpawnSpec as c}from"../../registry/dev-spawn.js";import{routeWithLLM as u}from"../../router/llm-router.js";import{extractToolInput as d}from"../../tool-runtime/argument-extractor.js";import{formatValidationIssuesMessage as g}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as v}from"../utils/agent-tools.js";import{log as j,redactToolArgsForLog as y}from"../utils/output.js";import{extractTextContent as $,isToolErrorResult as N}from"../utils/tool-results.js";const k=.5;function x(e){console.log(JSON.stringify(e,null,2))}function w(e){const s=$("object"==typeof e.result&&null!==e.result&&"content"in e.result?e.result.content:void 0);for(const e of s)console.log(e)}export default e({meta:{description:"用 LLM 从自然语言中选择 Agent 和 MCP tool"},args:{message:{type:"positional",description:"自然语言消息",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},async run({args:e}){const{config:C}=s(),h=new m(C.agents.dataDir).list();if(0===h.length)return j.error("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。"),void(process.exitCode=1);const M=s=>{if(e.json)x(s);else if("success"===s.status)w(s)},L=C.llm.defaultProvider,S=C.ask.llmModel??C.llm.defaultModel,b=C.llm.providers[L];if(!b)return j.error(`LLM provider "${L}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const{model:A,providerOptions:I}=a(L,S,b.apiKey,"structured-output",b.baseUrl);let O;j.info(`分析意图: "${e.message}"`);try{O=await u(e.message,h,A,I)}catch(e){const s={status:"failed",stage:"route",message:`LLM 路由失败: ${e instanceof Error?e.message:String(e)}`};return j.error(s.message),M(s),void(process.exitCode=1)}j.info(`路由决策: ${O.agentName}.${O.toolName} (置信度: ${String(O.confidence)})`);const T=C.ask.confirmThreshold??k;if(O.confidence<T){const e={status:"needs_confirmation",decision:O,message:`置信度 ${String(O.confidence)} 低于阈值 ${String(T)},跳过执行。 可使用 \`roll run ${O.agentName} ${O.toolName}\` 手动调用。`};return j.warn(e.message),M(e),void(process.exitCode=1)}const J=h.find(e=>e.skill.name===O.agentName);if(!J){const e={status:"failed",stage:"route",decision:O,message:`Agent "${O.agentName}" 未找到(LLM 返回了无效的 Agent 名称)`};return j.error(e.message),M(e),void(process.exitCode=1)}const P=i(L,C.llm.defaultModel,b.apiKey,b.baseUrl),E=new l;let K="connect";try{j.info(`连接 Agent "${J.skill.name}"...`);const s=t(C,J.skill.name),i=c(J),a=await E.connect(J.skill.name,i,J.installPath,{samplingModel:P,...s?{env:s}:{}}),l=v((await a.listTools()).tools),m=l.find(e=>e.name===O.toolName);if(!m){const e={status:"failed",stage:"route",decision:O,message:p(J.skill.name,O.toolName,l)};return j.error(e.message),M(e),void(process.exitCode=1)}K="execute";const u=await d(e.message,m,A,I),k={...O,input:u},x=r(J.skill.name,J.skill.env,C.agents.env),w=n(m.name)?[]:o(x),h=f(m,k.input,{runtimeIssues:w});if(!h.ok){const e={status:"needs_input",decision:k,validationIssues:h.issues,runtimeIssues:h.runtimeIssues,message:g(J.skill.name,O.toolName,h.issues,h.runtimeIssues)};return j.warn(e.message),M(e),void(process.exitCode=1)}j.info(`调用 ${J.skill.name}.${O.toolName}`),j.debug(`调用参数: ${JSON.stringify(y(k.input))}`);const L=await a.callTool({name:O.toolName,arguments:k.input});if(N(L)){const e={status:"failed",stage:"execute",decision:k,message:$(L.content).join("\n")||"Tool 调用失败"};return j.error(e.message),M(e),void(process.exitCode=1)}M({status:"success",decision:k,result:L}),j.success("调用完成")}catch(e){const s={status:"failed",stage:K,decision:O,message:e instanceof Error?e.message:String(e)};j.error(s.message),M(s),process.exitCode=1}finally{await E.disconnectAll()}}});
1
+ import{defineCommand as e}from"citty";import{loadConfig as s}from"../../config/loader.js";import{getAgentEnv as t,getMissingAgentEnvRuntimeIssues as o,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{shouldSkipRuntimeReadinessForTool as i}from"../../config/runtime-env.js";import{resolveLLMCall as r,toSamplingConnectOptions as a}from"../../llm/providers.js";import{McpClientManager as l}from"../../mcp/client-manager.js";import{AgentStore as m}from"../../registry/store.js";import{resolveTransportWithDevSpawnSpec as c}from"../../registry/dev-spawn.js";import{routeWithLLM as u}from"../../router/llm-router.js";import{extractToolInput as d}from"../../tool-runtime/argument-extractor.js";import{formatValidationIssuesMessage as g}from"../../tool-runtime/messages.js";import{preflightToolCall as f}from"../../tool-runtime/preflight.js";import{formatMissingToolMessage as p,normalizeListedTools as v}from"../utils/agent-tools.js";import{log as j,redactToolArgsForLog as y}from"../utils/output.js";import{extractTextContent as $,isToolErrorResult as k}from"../utils/tool-results.js";const N=.5;function x(e){console.log(JSON.stringify(e,null,2))}function w(e){const s=$("object"==typeof e.result&&null!==e.result&&"content"in e.result?e.result.content:void 0);for(const e of s)console.log(e)}export default e({meta:{description:"用 LLM 从自然语言中选择 Agent 和 MCP tool"},args:{message:{type:"positional",description:"自然语言消息",required:!0},json:{type:"boolean",description:"JSON 格式输出",default:!1},verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},async run({args:e}){const{config:h}=s(),C=new m(h.agents.dataDir).list();if(0===C.length)return j.error("暂无已注册的 Agent。可使用 `roll agent add <path>`、`roll agent install <package>` 或 `roll agent add --remote <endpoint>`。"),void(process.exitCode=1);const L=s=>{if(e.json)x(s);else if("success"===s.status)w(s)},M=h.llm.defaultProvider,S=h.ask.llmModel??h.llm.defaultModel,b=h.llm.providers[M];if(!b)return j.error(`LLM provider "${M}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const{model:A,providerOptions:I}=r(M,S,b.apiKey,"structured-output",b.baseUrl);let O;j.info(`分析意图: "${e.message}"`);try{O=await u(e.message,C,A,I)}catch(e){const s={status:"failed",stage:"route",message:`LLM 路由失败: ${e instanceof Error?e.message:String(e)}`};return j.error(s.message),L(s),void(process.exitCode=1)}j.info(`路由决策: ${O.agentName}.${O.toolName} (置信度: ${String(O.confidence)})`);const T=h.ask.confirmThreshold??N;if(O.confidence<T){const e={status:"needs_confirmation",decision:O,message:`置信度 ${String(O.confidence)} 低于阈值 ${String(T)},跳过执行。 可使用 \`roll run ${O.agentName} ${O.toolName}\` 手动调用。`};return j.warn(e.message),L(e),void(process.exitCode=1)}const J=C.find(e=>e.skill.name===O.agentName);if(!J){const e={status:"failed",stage:"route",decision:O,message:`Agent "${O.agentName}" 未找到(LLM 返回了无效的 Agent 名称)`};return j.error(e.message),L(e),void(process.exitCode=1)}const P=r(M,h.llm.defaultModel,b.apiKey,"sampling",b.baseUrl,h.runtime.thinkingLevel),E=new l;let K="connect";try{j.info(`连接 Agent "${J.skill.name}"...`);const s=t(h,J.skill.name),r=c(J),l=await E.connect(J.skill.name,r,J.installPath,{...a(P),...s?{env:s}:{}}),m=v((await l.listTools()).tools),u=m.find(e=>e.name===O.toolName);if(!u){const e={status:"failed",stage:"route",decision:O,message:p(J.skill.name,O.toolName,m)};return j.error(e.message),L(e),void(process.exitCode=1)}K="execute";const N=await d(e.message,u,A,I),x={...O,input:N},w=n(J.skill.name,J.skill.env,h.agents.env),C=i(u.name)?[]:o(w),M=f(u,x.input,{runtimeIssues:C});if(!M.ok){const e={status:"needs_input",decision:x,validationIssues:M.issues,runtimeIssues:M.runtimeIssues,message:g(J.skill.name,O.toolName,M.issues,M.runtimeIssues)};return j.warn(e.message),L(e),void(process.exitCode=1)}j.info(`调用 ${J.skill.name}.${O.toolName}`),j.debug(`调用参数: ${JSON.stringify(y(x.input))}`);const S=await l.callTool({name:O.toolName,arguments:x.input});if(k(S)){const e={status:"failed",stage:"execute",decision:x,message:$(S.content).join("\n")||"Tool 调用失败"};return j.error(e.message),L(e),void(process.exitCode=1)}L({status:"success",decision:x,result:S}),j.success("调用完成")}catch(e){const s={status:"failed",stage:K,decision:O,message:e instanceof Error?e.message:String(e)};j.error(s.message),L(s),process.exitCode=1}finally{await E.disconnectAll()}}});
@@ -1,6 +1,8 @@
1
1
  import type { AgentSession } from "@roll-agent/runtime";
2
2
  import { type ChatConfirm } from "../utils/chat-renderer.ts";
3
3
  import type { ChatCommandResult } from "../../types/chat.ts";
4
+ import type { RollConfig } from "../../config/schema.ts";
5
+ import type { LlmConfigReadiness } from "../../config/helpers.ts";
4
6
  type RuntimeModule = typeof import("@roll-agent/runtime");
5
7
  type ThreadStoreInstance = InstanceType<RuntimeModule["ThreadStore"]>;
6
8
  interface ReplIo {
@@ -8,6 +10,7 @@ interface ReplIo {
8
10
  readonly output: NodeJS.WritableStream;
9
11
  readonly confirm?: ChatConfirm;
10
12
  }
13
+ export declare function resolveChatLlmReadiness(config: RollConfig): LlmConfigReadiness;
11
14
  export declare function runJsonTurn(session: AgentSession, message: string): Promise<ChatCommandResult>;
12
15
  export declare function runRepl(session: AgentSession, store: ThreadStoreInstance, isNewSession: boolean, io?: ReplIo): Promise<void>;
13
16
  declare const _default: import("citty").CommandDef<{
@@ -1 +1 @@
1
- import{defineCommand as e}from"citty";import{loadConfig as t}from"../../config/loader.js";import{resolveLLMCall as o,thinkingProviderOptions as n}from"../../llm/providers.js";import{createInterface as s}from"node:readline/promises";import i from"chalk";import r from"cli-table3";import{isDebugLogEnabled as a,log as l}from"../utils/output.js";import{ChatRenderer as c,clackConfirm as d}from"../utils/chat-renderer.js";import{titleFromMessage as u}from"../chat/title.js";import{buildBannerLines as p,renderBannerText as m}from"../chat/banner.js";import{getCurrentVersion as g}from"../utils/update-checker.js";import{buildSkillInvocationPrompt as f,formatSkillList as h,parseSkillInvocation as v}from"../chat/ink/commands.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})}function k(e){console.log(JSON.stringify(e,null,2))}function b(e){l.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}function S(e){l.warn(`skill 目录加载警告:${e}`)}function x(e,t){const o=v(t,e.getSkillSummaries());return o&&o.prompt.length>0?f(o):t}async function C(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 T(){return import("@roll-agent/runtime")}async function I(e){const t=e.runtime.provider??e.llm.defaultProvider,n=e.runtime.model??e.llm.defaultModel,s=e.llm.providers[t];if(!s)return l.error(`LLM provider "${t}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const i=await T(),{ConversationEngine:r,ThreadStore:c,RuntimeServer:d,createStdioConnection:u}=i,{model:p,providerOptions:m}=o(t,n,s.apiKey,"chat",s.baseUrl,e.runtime.thinkingLevel),g=new c(e.runtime.threadsDir),f=new r({config:e,model:p,store:g,policy:y(i,e),maxSteps:e.runtime.maxSteps,...m?{providerOptions:m}:{},debugEvents:a(),onAgentBootstrapIssue:b,onSkillLibraryIssue:S}),h=u(process.stdin,process.stdout),v=new d(f,h);h.onClose(()=>{v.abortAll(),f.dispose().catch(()=>{}).finally(()=>{g.close(),process.exit(0)})}),l.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function $(e,t){const{ThreadStore:o}=await T(),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 r({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=[],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":n.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":s.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"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,...n.length>0?{stepUsages:n}:{},...a?{totalUsage:a}:{},...l?{sessionUsage:l}:{},...void 0!==u?{contextWindow:u}:{},...void 0!==c?{contextInputTokens:c}:{},...s.length>0?{compactions:s}:{}}}export async function runRepl(e,t,o,n={input:process.stdin,output:process.stdout}){const r=s({input:n.input,output:n.output});r.on("SIGINT",()=>r.close());const a=n.confirm??(async e=>{l.debug("chat.repl input waiting · confirm"),r.pause();const t=await d(e);return l.debug(`chat.repl input received · confirm · approved=${String(t)}`),t}),p=new c(a,e.getContextWindow()),m=e.getSkillSummaries();l.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let g=!o,w=!1;try{for(;;){const o=await C(r,i.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 p.handle(t,e);l.debug("chat.repl manual compact completed");continue}if("/skills"===n){l.info(h(m,(process.stdout.columns||96)-2));continue}const s=v(n,m);if(s&&0===s.prompt.length){l.info("用法: /<skill-name> [/<skill-name> ...] 你的请求");continue}const a=s?f(s):n;g||(t.updateTitle(e.id,u(n)),g=!0),w=!0,l.debug(`chat.repl send start · chars=${String(n.length)}`);for await(const t of e.send(a))await p.handle(t,e);l.debug("chat.repl send completed")}}finally{r.close(),o&&!w&&0===t.countMessages(e.id)&&t.deleteThread(e.id)}}export default e({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}){const{config:s}=t();if(e.server)return void await I(s);if(e.list)return void await $(s,e.json);const i=s.runtime.provider??s.llm.defaultProvider,r=s.runtime.model??s.llm.defaultModel,f=s.llm.providers[i];if(!f)return l.error(`LLM provider "${i}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);if(e.json&&!e.message)return l.error('--json 模式需要消息:roll chat "<message>" --json'),void(process.exitCode=1);const h=await T(),{ConversationEngine:v,ThreadStore:C}=h,{model:N,providerOptions:j}=o(i,r,f.apiKey,"chat",f.baseUrl,s.runtime.thinkingLevel),L=new C(s.runtime.threadsDir),O=new v({config:s,model:N,store:L,policy:y(h,s),maxSteps:s.runtime.maxSteps,...j?{providerOptions:j}:{},debugEvents:a(),onAgentBootstrapIssue:b,onSkillLibraryIssue:S});try{let t;if(e.session)t=await O.resumeSession(e.session);else if(e.last){const e=L.listThreads()[0];if(!e)return l.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);t=await O.resumeSession(e.id)}else t=await O.createSession(e.message?{title:u(e.message)}:{});if(e.json&&e.message){const o=await runJsonTurn(t,x(t,e.message));return k(o),void("completed"!==o.status&&(process.exitCode=1))}if(l.info(`会话 ${t.id}`),s.runtime.compaction.enabled&&void 0===t.getContextWindow()&&l.warn(`未知模型 "${r}" 的 context window,阈值自动压缩不可用。可在 roll.config.yaml 设置 runtime.context-window`),e.message){const o=new c(d,t.getContextWindow());for await(const n of t.send(x(t,e.message)))await o.handle(n,t)}else{const o=!e.session&&!e.last,a=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode),c=await O.getContextSummary(),d={version:g(),model:r,agentCount:c.agentCount,skillCount:c.skillCount};let u=!1;if(a)try{const e=new URL(`../chat/ink/run-ink-repl.${w}`,import.meta.url).href,{runInkRepl:a}=await import(e);await a(t,L,o,{model:r,banner:d,initialThinkingLevel:s.runtime.thinkingLevel,onThinkingChange:e=>t.setProviderOptions(n(i,r,e))}),u=!0}catch(e){l.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}u||(process.stderr.write(`${m(p(d,process.stdout.columns||80))}\n`),await runRepl(t,L,o))}}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await O.dispose(),L.close()}}});
1
+ var e=this&&this.__rewriteRelativeImportExtension||function(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,o,s,n,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(),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{E?.abort(),await U.dispose(),O.close()}}});
@@ -100,6 +100,27 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
100
100
  readonly purpose: "`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。";
101
101
  readonly defaultBehavior: "未配置时回退到 `runtime.approval.default`。";
102
102
  readonly example: "runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto";
103
+ }, {
104
+ readonly path: "runtime.shell.enabled";
105
+ readonly title: "Chat 内建 shell 工具";
106
+ readonly purpose: "开启后 `roll chat` 注册内建 shell 工具:macOS/Linux 为 `roll__bash`,Windows 原生为 `roll__powershell`(需 PowerShell 7+)。命令继承 roll 进程环境变量;默认确认策略可由 `runtime.approval` 及精确 override 覆盖。";
107
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\uFF08\u5173\u95ED\uFF09\u3002Windows \u672A\u68C0\u6D4B\u5230 pwsh 7+ \u65F6\u4F1A\u8DF3\u8FC7\u6CE8\u518C\u3002`;
108
+ readonly example: "runtime:\n shell:\n enabled: true";
109
+ readonly setupCommand: "roll config setup shell";
110
+ }, {
111
+ readonly path: "runtime.shell.auto-approve-safe";
112
+ readonly title: "只读命令免确认";
113
+ readonly purpose: "开启后用内建规则分类器识别 POSIX known-safe 只读命令(如 `ls`、`cat`、`git status`),在 `guarded`/`auto` 策略下免确认执行;dangerous(`rm -rf`、`sudo`)与无法识别的命令默认仍需确认。工具级显式 approval override 优先;Windows PowerShell 当前全部按 unknown 处理。";
114
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\u3002\u5173\u95ED\u540E POSIX \u56DE\u5F52\u9ED8\u8BA4\u9010\u6761\u786E\u8BA4\uFF1BWindows PowerShell \u9ED8\u8BA4\u9010\u6761\u786E\u8BA4\uFF0C\u663E\u5F0F approval override \u53EF\u8986\u76D6\u3002`;
115
+ readonly example: "runtime:\n shell:\n enabled: true\n auto-approve-safe: false";
116
+ readonly setupCommand: "roll config setup shell";
117
+ }, {
118
+ readonly path: "runtime.shell.session.enabled";
119
+ readonly title: "Chat 会话式长命令执行";
120
+ readonly purpose: "开启后在 POSIX shell 后端注册 `roll__exec_command` + `roll__exec_poll` 工具:命令在后台会话执行、跨轮存活,模型轮询进度并读取退出码,适合超过单轮超时的长脚本。Windows 原生 session exec 暂未支持。仅交互 REPL 与 `--server` 长驻模式注册(单条消息 / `--json` 单轮会话随进程结束,不提供该工具);`--server` 下 `exec_command` 需 `runtime.approval.overrides` 里 `roll.exec_command: auto` 显式授权。";
121
+ readonly defaultBehavior: `\u9ED8\u8BA4\u503C\u4E3A \`${string}\`\uFF08\u5173\u95ED\uFF09\u3002\u9700\u540C\u65F6\u5F00\u542F runtime.shell.enabled\uFF1B\u80CC\u666F\u4F1A\u8BDD\u968F roll chat \u8FDB\u7A0B\u9000\u51FA\u800C\u7EC8\u6B62\u3002`;
122
+ readonly example: "runtime:\n shell:\n enabled: true\n session:\n enabled: true\n approval:\n overrides:\n roll.exec_command: auto";
123
+ readonly setupCommand: "roll config setup shell";
103
124
  }, {
104
125
  readonly path: "skills.dirs";
105
126
  readonly title: "Chat 额外 skill 目录";