@roll-agent/core 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/THIRD_PARTY_NOTICES.txt +112 -0
  2. package/dist/cli/chat/banner.d.ts +13 -0
  3. package/dist/cli/chat/banner.js +1 -1
  4. package/dist/cli/chat/ink/app.d.ts +4 -1
  5. package/dist/cli/chat/ink/app.js +1 -1
  6. package/dist/cli/chat/ink/banner-view.d.ts +15 -0
  7. package/dist/cli/chat/ink/banner-view.js +1 -0
  8. package/dist/cli/chat/ink/commands.d.ts +4 -12
  9. package/dist/cli/chat/ink/commands.js +1 -1
  10. package/dist/cli/chat/ink/history-from-messages.js +1 -1
  11. package/dist/cli/chat/ink/history-item.js +1 -1
  12. package/dist/cli/chat/ink/input-history.d.ts +2 -0
  13. package/dist/cli/chat/ink/input-history.js +1 -0
  14. package/dist/cli/chat/ink/line-buffer.d.ts +2 -0
  15. package/dist/cli/chat/ink/line-buffer.js +1 -1
  16. package/dist/cli/chat/ink/live-region.d.ts +2 -0
  17. package/dist/cli/chat/ink/live-region.js +1 -1
  18. package/dist/cli/chat/ink/reasoning-block.d.ts +4 -0
  19. package/dist/cli/chat/ink/reasoning-block.js +1 -0
  20. package/dist/cli/chat/ink/run-ink-repl.js +1 -1
  21. package/dist/cli/chat/ink/spinner.d.ts +3 -1
  22. package/dist/cli/chat/ink/spinner.js +1 -1
  23. package/dist/cli/chat/ink/state.d.ts +26 -3
  24. package/dist/cli/chat/ink/state.js +1 -1
  25. package/dist/cli/chat/ink/text-prompt.d.ts +3 -1
  26. package/dist/cli/chat/ink/text-prompt.js +1 -1
  27. package/dist/cli/chat/ink/turn-activity.d.ts +19 -0
  28. package/dist/cli/chat/ink/turn-activity.js +1 -0
  29. package/dist/cli/chat/ink/turn-status-line.d.ts +14 -0
  30. package/dist/cli/chat/ink/turn-status-line.js +1 -0
  31. package/dist/cli/chat/ink/use-session.js +1 -1
  32. package/dist/cli/commands/chat.d.ts +28 -0
  33. package/dist/cli/commands/chat.js +1 -1
  34. package/dist/cli/commands/config-explain.js +1 -1
  35. package/dist/cli/commands/config-setup.js +1 -1
  36. package/dist/cli/commands/config.js +1 -1
  37. package/dist/cli/commands/doctor.js +1 -1
  38. package/dist/cli/commands/ui.d.ts +30 -0
  39. package/dist/cli/commands/ui.js +1 -0
  40. package/dist/cli/index.js +1 -1
  41. package/dist/cli/utils/chat-renderer.js +1 -1
  42. package/dist/cli/utils/current-cli-shim.d.ts +14 -0
  43. package/dist/cli/utils/current-cli-shim.js +1 -0
  44. package/dist/cli/utils/debug-format.d.ts +4 -0
  45. package/dist/cli/utils/debug-format.js +1 -0
  46. package/dist/config/application-service.d.ts +90 -0
  47. package/dist/config/application-service.js +1 -0
  48. package/dist/config/catalog.d.ts +76 -0
  49. package/dist/config/catalog.js +1 -0
  50. package/dist/config/defaults.d.ts +4 -3
  51. package/dist/config/defaults.js +1 -1
  52. package/dist/config/document-store.d.ts +70 -0
  53. package/dist/config/document-store.js +1 -0
  54. package/dist/config/guidance.d.ts +571 -0
  55. package/dist/config/guidance.js +1 -0
  56. package/dist/config/index.d.ts +6 -0
  57. package/dist/config/index.js +1 -1
  58. package/dist/config/key-codec.d.ts +24 -3
  59. package/dist/config/key-codec.js +1 -1
  60. package/dist/config/migration.js +1 -1
  61. package/dist/config/schema.d.ts +52 -7
  62. package/dist/config/schema.js +1 -1
  63. package/dist/config/secret-policy.d.ts +20 -0
  64. package/dist/config/secret-policy.js +1 -0
  65. package/dist/llm/providers.d.ts +8 -4
  66. package/dist/llm/providers.js +1 -1
  67. package/dist/registry/agent-lifecycle.d.ts +97 -0
  68. package/dist/registry/agent-lifecycle.js +1 -0
  69. package/dist/registry/discovery.js +1 -1
  70. package/dist/registry/install.js +1 -1
  71. package/dist/registry/process-identity.d.ts +14 -0
  72. package/dist/registry/process-identity.js +1 -0
  73. package/dist/registry/process-manager.d.ts +28 -7
  74. package/dist/registry/process-manager.js +1 -1
  75. package/dist/registry/store.js +1 -1
  76. package/dist/skills/invocation.d.ts +27 -0
  77. package/dist/skills/invocation.js +1 -0
  78. package/dist/types/agent.d.ts +6 -0
  79. package/dist/types/agent.js +1 -1
  80. package/dist/types/chat.d.ts +3 -0
  81. package/dist/ui/config-controller.d.ts +15 -0
  82. package/dist/ui/config-controller.js +1 -0
  83. package/dist/ui/contracts.d.ts +49 -0
  84. package/dist/ui/contracts.js +1 -0
  85. package/dist/ui/index.d.ts +8 -0
  86. package/dist/ui/index.js +1 -0
  87. package/dist/ui/runtime-controller.d.ts +38 -0
  88. package/dist/ui/runtime-controller.js +1 -0
  89. package/dist/ui/server.d.ts +21 -0
  90. package/dist/ui/server.js +1 -0
  91. package/dist/ui/static-assets.d.ts +2 -0
  92. package/dist/ui/static-assets.js +1 -0
  93. package/dist/ui-assets/assets/app.css +1 -0
  94. package/dist/ui-assets/assets/app.js +53 -0
  95. package/dist/ui-assets/assets/roll-mark-wXIkY5y0.svg +5 -0
  96. package/dist/ui-assets/index.html +17 -0
  97. package/package.json +16 -8
  98. package/dist/cli/commands/config-guidance.d.ts +0 -148
  99. package/dist/cli/commands/config-guidance.js +0 -1
@@ -0,0 +1,112 @@
1
+ THIRD-PARTY SOFTWARE NOTICES
2
+
3
+ The compiled Roll local configuration UI includes or is produced with the
4
+ third-party software identified below. These notices apply only to those
5
+ third-party components and do not state a license for Roll itself.
6
+
7
+ -------------------------------------------------------------------------------
8
+ React 19.2.7
9
+ React DOM 19.2.7
10
+ Scheduler 0.27.0
11
+ -------------------------------------------------------------------------------
12
+
13
+ MIT License
14
+
15
+ Copyright (c) Meta Platforms, Inc. and affiliates.
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining a copy
18
+ of this software and associated documentation files (the "Software"), to deal
19
+ in the Software without restriction, including without limitation the rights
20
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
+ copies of the Software, and to permit persons to whom the Software is
22
+ furnished to do so, subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all
25
+ copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
+ SOFTWARE.
34
+
35
+ -------------------------------------------------------------------------------
36
+ Tailwind CSS 4.3.2
37
+ @tailwindcss/vite 4.3.2
38
+ -------------------------------------------------------------------------------
39
+
40
+ MIT License
41
+
42
+ Copyright (c) Tailwind Labs, Inc.
43
+
44
+ Permission is hereby granted, free of charge, to any person obtaining a copy
45
+ of this software and associated documentation files (the "Software"), to deal
46
+ in the Software without restriction, including without limitation the rights
47
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
48
+ copies of the Software, and to permit persons to whom the Software is
49
+ furnished to do so, subject to the following conditions:
50
+
51
+ The above copyright notice and this permission notice shall be included in all
52
+ copies or substantial portions of the Software.
53
+
54
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
55
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
56
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
57
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
58
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
59
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
60
+ SOFTWARE.
61
+
62
+ -------------------------------------------------------------------------------
63
+ Vite 6.4.3
64
+ -------------------------------------------------------------------------------
65
+
66
+ MIT License
67
+
68
+ Copyright (c) 2019-present, VoidZero Inc. and Vite contributors
69
+
70
+ Permission is hereby granted, free of charge, to any person obtaining a copy
71
+ of this software and associated documentation files (the "Software"), to deal
72
+ in the Software without restriction, including without limitation the rights
73
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
74
+ copies of the Software, and to permit persons to whom the Software is
75
+ furnished to do so, subject to the following conditions:
76
+
77
+ The above copyright notice and this permission notice shall be included in all
78
+ copies or substantial portions of the Software.
79
+
80
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
81
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
82
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
83
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
84
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
85
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
86
+ SOFTWARE.
87
+
88
+ -------------------------------------------------------------------------------
89
+ @vitejs/plugin-react 4.7.0
90
+ -------------------------------------------------------------------------------
91
+
92
+ MIT License
93
+
94
+ Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
95
+
96
+ Permission is hereby granted, free of charge, to any person obtaining a copy
97
+ of this software and associated documentation files (the "Software"), to deal
98
+ in the Software without restriction, including without limitation the rights
99
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
100
+ copies of the Software, and to permit persons to whom the Software is
101
+ furnished to do so, subject to the following conditions:
102
+
103
+ The above copyright notice and this permission notice shall be included in all
104
+ copies or substantial portions of the Software.
105
+
106
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
107
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
108
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
109
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
110
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
111
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
112
+ SOFTWARE.
@@ -18,4 +18,17 @@ export interface BuildBannerOptions {
18
18
  }
19
19
  export declare function bannerTextLine(text: string, style?: Omit<BannerSpan, "text">): BannerLine;
20
20
  export declare function buildBannerLines(info: BannerInfo, width: number, options?: BuildBannerOptions): readonly BannerLine[];
21
+ /** Leading logo rows: single colored span, not dim (excludes blank / info / hints). */
22
+ export declare function isLogoBannerLine(line: BannerLine): boolean;
23
+ export declare function splitBannerLogoLines(lines: readonly BannerLine[]): {
24
+ readonly logo: readonly BannerLine[];
25
+ readonly rest: readonly BannerLine[];
26
+ };
27
+ export declare const REVEAL_EDGE_COLOR = "#f0f9ff";
28
+ /**
29
+ * Left-to-right reveal for logo rows. `progress` in [0, 1]; layout width preserved via
30
+ * spaces. The 1–2 columns at the reveal frontier render in a bright edge color and settle
31
+ * to the row's palette color behind it; at progress >= 1 the edge is gone.
32
+ */
33
+ export declare function revealLogoLines(logoLines: readonly BannerLine[], progress: number): readonly BannerLine[];
21
34
  export declare function renderBannerText(lines: readonly BannerLine[]): string;
@@ -1 +1 @@
1
- import n from"chalk";import t from"is-unicode-supported";const e="Roll Agent",_="#e879f9",o=["██████╗ ██████╗ ██╗ ██╗","██╔══██╗██╔═══██╗██║ ██║","██████╔╝██║ ██║██║ ██║","██╔══██╗██║ ██║██║ ██║","██║ ██║╚██████╔╝███████╗███████╗","╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝"],r=[" ____ ____ __ __"," / __ \\/ __ \\/ / / /"," / /_/ / / / / / / /"," / _, _/ /_/ / /___/ /___","/_/ |_|\\____/_____/_____/"],i=["#22d3ee","#4ac1f0","#71aff2","#999df5","#c08bf7","#e879f9"],s=["#22d3ee","#54bdf1","#85a6f4","#b790f6","#e879f9"],u=36,f=28;export function bannerTextLine(n,t={}){return{spans:[{text:n,...t}]}}function a(n,t){return n.map((n,e)=>({spans:[{text:n,color:t[e]??t[t.length-1]??_}]}))}function c(n,t){return t&&n>=u?a(o,i):n>=f?a(r,s):[]}function l(n){const t=[`v${n.version}`,n.model];return n.agentCount>0&&t.push(`${String(n.agentCount)} agents`),n.skillCount>0&&t.push(`${String(n.skillCount)} skills`),{spans:[{text:e,color:_},{text:` ${t.join(" · ")}`,dim:!0}]}}export function buildBannerLines(n,e,_={}){const o=c(e,_.unicode??t()),r=[...o,...o.length>0?[bannerTextLine(" ")]:[],l(n)];return void 0!==_.hints&&r.push(bannerTextLine(_.hints,{dim:!0})),r}function p(t){let e=n;return void 0!==t.color&&(e=e.hex(t.color)),!0===t.dim&&(e=e.dim),e(t.text)}export function renderBannerText(n){return n.map(n=>n.spans.map(p).join("")).join("\n")}
1
+ import n from"chalk";import t from"is-unicode-supported";const e="Roll Agent",o="#e879f9",r=["██████╗ ██████╗ ██╗ ██╗","██╔══██╗██╔═══██╗██║ ██║","██████╔╝██║ ██║██║ ██║","██╔══██╗██║ ██║██║ ██║","██║ ██║╚██████╔╝███████╗███████╗","╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝"],i=[" ____ ____ __ __"," / __ \\/ __ \\/ / / /"," / /_/ / / / / / / /"," / _, _/ /_/ / /___/ /___","/_/ |_|\\____/_____/_____/"],s=["#22d3ee","#4ac1f0","#71aff2","#999df5","#c08bf7","#e879f9"],_=["#22d3ee","#54bdf1","#85a6f4","#b790f6","#e879f9"],u=36,a=28;export function bannerTextLine(n,t={}){return{spans:[{text:n,...t}]}}function f(n,t){return n.map((n,e)=>({spans:[{text:n,color:t[e]??t[t.length-1]??o}]}))}function l(n,t){return t&&n>=u?f(r,s):n>=a?f(i,_):[]}function c(n){const t=[`v${n.version}`,n.model];return n.agentCount>0&&t.push(`${String(n.agentCount)} agents`),n.skillCount>0&&t.push(`${String(n.skillCount)} skills`),{spans:[{text:e,color:o},{text:` ${t.join(" · ")}`,dim:!0}]}}export function buildBannerLines(n,e,o={}){const r=l(e,o.unicode??t()),i=[...r,...r.length>0?[bannerTextLine(" ")]:[],c(n)];return void 0!==o.hints&&i.push(bannerTextLine(o.hints,{dim:!0})),i}export function isLogoBannerLine(n){if(1!==n.spans.length)return!1;const t=n.spans[0];return void 0!==t&&void 0!==t.color&&!0!==t.dim}export function splitBannerLogoLines(n){let t=0;for(;t<n.length&&void 0!==n[t]&&isLogoBannerLine(n[t]);)t+=1;return{logo:n.slice(0,t),rest:n.slice(t)}}function p(n){return n.spans.map(n=>n.text).join("")}export const REVEAL_EDGE_COLOR="#f0f9ff";const h=2;function x(n,t){const e=n.spans[0];if(void 0===e)return n;const o=Array.from(e.text),r=Math.min(t,o.length),i=Math.min(Math.max(0,t-2),o.length),s=[];return i>0&&s.push({...e,text:o.slice(0,i).join("")}),r>i&&s.push({...e,text:o.slice(i,r).join(""),color:"#f0f9ff"}),o.length>r&&s.push({text:" ".repeat(o.length-r)}),{spans:s.length>0?s:[{text:""}]}}export function revealLogoLines(n,t){if(0===n.length)return n;const e=t<=0?0:t>=1?1:t;if(e>=1)return n;const o=Math.max(0,...n.map(n=>Array.from(p(n)).length)),r=Math.floor(e*o);return n.map(n=>x(n,r))}function g(t){let e=n;return void 0!==t.color&&(e=e.hex(t.color)),!0===t.dim&&(e=e.dim),e(t.text)}export function renderBannerText(n){return n.map(n=>n.spans.map(g).join("")).join("\n")}
@@ -1,13 +1,16 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { AgentSession } from "@roll-agent/runtime";
3
3
  import type { ThinkingLevel } from "../../../llm/providers.ts";
4
- import type { HistoryItem } from "./state.ts";
4
+ import { type HistoryItem } from "./state.ts";
5
5
  import { type SlashSkillSummary } from "./commands.ts";
6
+ import { type BannerLine } from "../banner.ts";
6
7
  export interface ChatAppProps {
7
8
  readonly session: AgentSession;
8
9
  readonly model: string;
9
10
  readonly contextWindow: number | undefined;
10
11
  readonly initialHistory?: readonly HistoryItem[];
12
+ /** Banner played as an entrance animation in the dynamic region, then committed to Static. */
13
+ readonly animatedBanner?: readonly BannerLine[];
11
14
  readonly initialThinkingLevel?: ThinkingLevel;
12
15
  readonly availableSkills?: readonly SlashSkillSummary[];
13
16
  readonly onThinkingChange?: (level: ThinkingLevel) => void;
@@ -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 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
+ import{randomUUID as i}from"node:crypto";import{createElement as t,useCallback as n,useMemo as o,useRef as e,useState as r}from"react";import{Box as s,Static as a,useInput as m}from"ink";import{useSession as l}from"./use-session.js";import{CHAT_PHASES as d}from"./state.js";import{HistoryItemView as p}from"./history-item.js";import{LiveRegion as f}from"./live-region.js";import{StatusLine as c}from"./status-line.js";import{TextPrompt as u}from"./text-prompt.js";import{ConfirmSelect as h}from"./confirm-select.js";import{SlashPopup as g}from"./slash-popup.js";import{buildSkillListLines as v,filterSlashEntries as k,parseSkillInvocation as j,SLASH_COMMANDS as y}from"./commands.js";import{bannerTextLine as b}from"../banner.js";import{BannerHistoryView as x}from"./banner-view.js";import{cycleThinking as C}from"./thinking.js";import{appendInputHistory as L}from"./input-history.js";import{resolveTurnActivity as T}from"./turn-activity.js";import{TurnStatusLine as M}from"./turn-status-line.js";function A(){return y.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(H){const{session:S,model:$,contextWindow:w,onUserSubmit:W,onExit:D}=H,B=H.availableSkills??[],[E,P]=r(()=>(H.initialHistory??[]).reduce((i,t)=>"user"===t.kind?L(i,t.text):i,[])),R=n(i=>{P(t=>L(t,i))},[]),{state:U,submit:q,compact:z,cancel:F,resolveConfirm:G,setDraft:I,setThinking:J,setAutoMode:K,toggleAutoMode:N,commitHistory:O}=l(S,{model:$,contextWindow:w,...H.initialHistory?{initialHistory:H.initialHistory}:{},...H.initialThinkingLevel?{initialThinkingLevel:H.initialThinkingLevel}:{},...H.onThinkingChange?{onThinkingChange:H.onThinkingChange}:{}}),Q=o(()=>[...U.history],[U.history]),[V,X]=r(!1),Y=e(H.animatedBanner),Z=n(()=>{const i=Y.current;void 0!==i&&(Y.current=void 0,O({kind:"banner",id:"banner",lines:i})),X(!0)},[O]),_=V?void 0:H.animatedBanner,[ii,ti]=r(0),ni=U.phase===d.idle&&U.draft.startsWith("/"),oi=ni&&!0===U.draft.split(/\s+/).at(-1)?.startsWith("/"),ei=oi?k(U.draft,B):[],ri=Math.max(ei.length-1,0),si=Math.min(ii,ri);m((i,t)=>{U.phase===d.busy&&t.escape&&!t.meta?F():t.tab&&t.shift?N():t.meta&&"."===i?J(C(U.status.thinkingLevel,1)):t.meta&&","===i&&J(C(U.status.thinkingLevel,-1))});const ai=i=>{const t=i.trim();0!==t.length?(R(t),Z(),W(t),q(t)):I("")},mi=U.phase===d.confirm&&void 0!==U.pendingConfirm?t(h,{prompt:U.pendingConfirm.prompt,args:U.pendingConfirm.args,onDecide:G}):t(u,{value:U.draft,inputHistory:E,disabled:U.phase!==d.idle,...U.phase===d.cancelling?{disabledHint:"中断请求已发送,等待当前活动退出…"}:{},slashActive:ni,slashPopupActive:oi,autoApprove:U.status.autoApprove,onChange:I,onSubmit:ai,onSlashMove:i=>{ti(t=>Math.min(Math.max(Math.min(t,ri)+i,0),ri))},onSlashComplete:()=>{const i=ei[si];if(i){const t=U.draft.split(/\s+/);t[t.length-1]=i.name,I(`${t.join(" ")} `),ti(0)}},onSlashRun:t=>{const n=t.trim().split(/\s+/,1)[0]??"",o=y.some(i=>i.name===n),e=ei[si];if(!o&&"skill"===e?.kind){const i=U.draft.split(/\s+/);return i[i.length-1]=e.name,I(`${i.join(" ")} `),void ti(0)}(t=>{Z(),I("");const n=t.trim();R(n);const o=n.split(/\s+/),e=o[0]??"",r=(o[1]??"").toLowerCase(),s=U.status.thinkingLevel;if("/compact"===e)return void z();if("/think"===e)return void J("off"===r?"off":"on"===r?"off"===s?"medium":s:"off"===s?"medium":"off");if("/effort"===e)return void("low"===r||"medium"===r||"high"===r?J(r):O({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===e)return void("on"===r?K(!0):"off"===r?K(!1):N());if("/skills"===e){const t=(process.stdout.columns||80)-2,[n,...o]=v(B,t);return void O({kind:"banner",id:i(),lines:[b(n??""),...o.map(i=>b(i,{dim:!0}))]})}if("/help"===e)return void O({kind:"notice",id:i(),text:A()});if("/exit"===e)return void D();const a=j(n,B);if(a)return 0===a.prompt.length?void I(`${a.skills.map(i=>`/${i.name}`).join(" ")} `):void ai(n);O({kind:"notice",id:i(),text:`未知命令 ${e}`})})(o?t:e?.name??t)}}),li=T(U);return t(s,{flexDirection:"column"},t(a,{items:Q,children:(i,n)=>{const o=Q[n-1],e="user"===i.kind||"assistant"===i.kind||"reasoning"===i.kind||"reasoning"===o?.kind,r="tool"===i.kind||"denied"===i.kind||"cancelled"===i.kind;return t(s,{key:i.id,marginTop:e?1:0,marginLeft:r?3:1},t(p,{item:i}))}}),void 0!==_?t(s,{marginLeft:1},t(x,{lines:_,onSettled:Z})):null,t(s,{marginLeft:1},t(f,{live:U.live})),t(s,{marginTop:void 0===li?0:1},void 0===li?t(c,{status:U.status}):t(M,{activity:li})),ni?t(g,{matches:ei,selected:si}):null,mi)}
@@ -0,0 +1,15 @@
1
+ import type { ReactElement } from "react";
2
+ import { type BannerLine } from "../banner.ts";
3
+ export declare function BannerLinesView({ lines, }: {
4
+ readonly lines: readonly BannerLine[];
5
+ }): ReactElement;
6
+ /**
7
+ * Animated banner entrance. Must be rendered OUTSIDE Ink's Static region (Static prints
8
+ * each item exactly once, so interval-driven re-renders are never repainted there).
9
+ * Calls `onSettled` once the animation completes (immediately when there is no logo),
10
+ * letting the parent commit the final banner into Static.
11
+ */
12
+ export declare function BannerHistoryView({ lines, onSettled, }: {
13
+ readonly lines: readonly BannerLine[];
14
+ readonly onSettled?: () => void;
15
+ }): ReactElement;
@@ -0,0 +1 @@
1
+ import{createElement as n,useEffect as e,useRef as r,useState as t}from"react";import{Box as i,Text as o}from"ink";import{revealLogoLines as l,splitBannerLogoLines as s}from"../banner.js";const a=50,c=22,m=2,u={spans:[{text:" "}]};function f(n){return 1-(1-n)**3}export function BannerLinesView({lines:e}){return n(i,{flexDirection:"column"},...e.map((e,r)=>n(o,{key:String(r)},...e.spans.map((e,r)=>n(o,{key:String(r),...void 0!==e.color?{color:e.color}:{},...!0===e.dim?{dimColor:!0}:{}},e.text)))))}export function BannerHistoryView({lines:i,onSettled:o}){const{logo:m,rest:p}=s(i),d=m.length>0,v=c+2*p.length,[g,x]=t(0),w=r(o);if(w.current=o,e(()=>{if(!d)return void w.current?.();let n=0;const e=setInterval(()=>{if(n+=1,n>=v)return clearInterval(e),x(v),void w.current?.();x(n)},a);return()=>{clearInterval(e)}},[d,v]),!d||g>=v)return n(BannerLinesView,{lines:i});const B=f(Math.min(1,g/c)),V=p.map((n,e)=>g>=c+2*e?n:u);return n(BannerLinesView,{lines:[...l(m,B),...V]})}
@@ -1,13 +1,12 @@
1
+ import { findSkillBySlashName, parseSkillInvocation, type SkillInvocation, type SkillInvocationSummary } from "../../../skills/invocation.ts";
2
+ export { findSkillBySlashName, parseSkillInvocation };
3
+ export type { SkillInvocation };
1
4
  export interface SlashCommand {
2
5
  readonly kind: "command";
3
6
  readonly name: string;
4
7
  readonly description: string;
5
8
  }
6
- export interface SlashSkillSummary {
7
- readonly name: string;
8
- readonly description: string;
9
- readonly source: string;
10
- }
9
+ export type SlashSkillSummary = SkillInvocationSummary;
11
10
  export interface SlashSkillEntry {
12
11
  readonly kind: "skill";
13
12
  readonly name: string;
@@ -23,12 +22,5 @@ export declare function buildSkillEntries(skills: readonly SlashSkillSummary[]):
23
22
  export declare function currentSlashToken(input: string): string;
24
23
  export declare function filterSlashEntries(input: string, skills?: readonly SlashSkillSummary[]): SlashEntry[];
25
24
  export declare function filterCommands(input: string): SlashCommand[];
26
- export declare function findSkillBySlashName(token: string, skills: readonly SlashSkillSummary[]): SlashSkillSummary | undefined;
27
- export interface SkillInvocation {
28
- readonly skills: readonly SlashSkillSummary[];
29
- readonly prompt: string;
30
- }
31
- export declare function parseSkillInvocation(input: string, skills: readonly SlashSkillSummary[]): SkillInvocation | undefined;
32
- export declare function buildSkillInvocationPrompt(invocation: SkillInvocation): string;
33
25
  export declare function buildSkillListLines(skills: readonly SlashSkillSummary[], width?: number): string[];
34
26
  export declare function formatSkillList(skills: readonly SlashSkillSummary[], width?: number): string;
@@ -1 +1 @@
1
- import{SKILL_TOOL_ID as n}from"../../../skills/library.js";import{displayWidth as e}from"./display-width.js";const t=new Intl.Segmenter(void 0,{granularity:"grapheme"}),i="…";export const SLASH_COMMANDS=[{kind:"command",name:"/compact",description:"压缩上下文,释放 token"},{kind:"command",name:"/think",description:"开关 thinking/reasoning (on | off)"},{kind:"command",name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{kind:"command",name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{kind:"command",name:"/skills",description:"列出可加载的 SKILL"},{kind:"command",name:"/help",description:"列出可用命令"},{kind:"command",name:"/exit",description:"退出对话"}];function r(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,r){if(r<=0)return"";if(e(n)<=r)return n;const o=r-e(i);let a="",s=0;for(const{segment:i}of t.segment(n)){const n=e(i);if(s+n>o)break;a+=i,s+=n}return`${a}${i}`}export function skillSlashName(n){return`/${n}`}export function buildSkillEntries(n){return[...n].filter(n=>!SLASH_COMMANDS.some(e=>e.name===skillSlashName(n.name))).sort((n,e)=>n.name.localeCompare(e.name)).map(n=>{const e=r(n.description);return{kind:"skill",name:skillSlashName(n.name),skillName:n.name,source:n.source,description:e.length>0?`${n.source} · ${e}`:n.source}})}export function currentSlashToken(n){const e=n.split(/\s+/),t=e.at(-1)??"";return t.startsWith("/")?t:e[0]??""}export function filterSlashEntries(n,e=[]){const t=currentSlashToken(n).toLowerCase();return[...SLASH_COMMANDS,...buildSkillEntries(e)].filter(n=>n.name.toLowerCase().startsWith(t))}export function filterCommands(n){const e=currentSlashToken(n).toLowerCase();return SLASH_COMMANDS.filter(n=>n.name.toLowerCase().startsWith(e))}export function findSkillBySlashName(n,e){const t=n.toLowerCase();return e.find(n=>skillSlashName(n.name).toLowerCase()===t)}export function parseSkillInvocation(n,e){let t=n.trimStart();const i=[],r=new Set;for(;;){const n=/^(\/\S+)(\s*)/.exec(t);if(!n)break;const o=findSkillBySlashName(n[1]??"",e);if(!o)break;r.has(o.name)||(i.push(o),r.add(o.name)),t=t.slice(n[0].length)}if(0!==i.length)return{skills:i,prompt:t.trim()}}export function buildSkillInvocationPrompt(e){const t=e.skills.map(n=>`- ${n.name}`).join("\n");return[`请先调用 \`${n}\` 工具加载以下 skill,并严格按它们的说明处理后续请求:`,t,"","用户请求:",e.prompt].join("\n")}const o=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const t=[...n].sort((n,e)=>n.name.localeCompare(e.name)),i=Math.min(Math.max(...t.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(t.length)} 个)· 用法: /<skill-name> 你的请求`,...t.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(i)} [${n.source}] ${r(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
1
+ import{findSkillBySlashName as n,parseSkillInvocation as e}from"../../../skills/invocation.js";import{displayWidth as t}from"./display-width.js";export{n as findSkillBySlashName,e as parseSkillInvocation};const i=new Intl.Segmenter(void 0,{granularity:"grapheme"}),r="…";export const SLASH_COMMANDS=[{kind:"command",name:"/compact",description:"压缩上下文,释放 token"},{kind:"command",name:"/think",description:"开关 thinking/reasoning (on | off)"},{kind:"command",name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{kind:"command",name:"/auto",description:"开关自动批准工具调用 (on | off),Shift+Tab 快捷切换"},{kind:"command",name:"/skills",description:"列出可加载的 SKILL"},{kind:"command",name:"/help",description:"列出可用命令"},{kind:"command",name:"/exit",description:"退出对话"}];function o(n){return n.replace(/\s+/g," ").trim()}export function truncateDisplay(n,e){if(e<=0)return"";if(t(n)<=e)return n;const o=e-t(r);let a="",s=0;for(const{segment:e}of i.segment(n)){const n=t(e);if(s+n>o)break;a+=e,s+=n}return`${a}${r}`}export function skillSlashName(n){return`/${n}`}export function buildSkillEntries(n){return[...n].filter(n=>!SLASH_COMMANDS.some(e=>e.name===skillSlashName(n.name))).sort((n,e)=>n.name.localeCompare(e.name)).map(n=>{const e=o(n.description);return{kind:"skill",name:skillSlashName(n.name),skillName:n.name,source:n.source,description:e.length>0?`${n.source} · ${e}`:n.source}})}export function currentSlashToken(n){const e=n.split(/\s+/),t=e.at(-1)??"";return t.startsWith("/")?t:e[0]??""}export function filterSlashEntries(n,e=[]){const t=currentSlashToken(n).toLowerCase();return[...SLASH_COMMANDS,...buildSkillEntries(e)].filter(n=>n.name.toLowerCase().startsWith(t))}export function filterCommands(n){const e=currentSlashToken(n).toLowerCase();return SLASH_COMMANDS.filter(n=>n.name.toLowerCase().startsWith(e))}const a=96;export function buildSkillListLines(n,e=96){if(0===n.length)return["当前没有可加载的 SKILL。"];const t=[...n].sort((n,e)=>n.name.localeCompare(e.name)),i=Math.min(Math.max(...t.map(n=>skillSlashName(n.name).length)),28);return[`可加载 SKILL(${String(t.length)} 个)· 用法: /<skill-name> 你的请求`,...t.map(n=>truncateDisplay(` ${skillSlashName(n.name).padEnd(i)} [${n.source}] ${o(n.description)}`,Math.max(e,40)))]}export function formatSkillList(n,e=96){return buildSkillListLines(n,e).join("\n")}
@@ -1 +1 @@
1
- import{SUMMARY_ACK as t,SUMMARY_PREFIX as o}from"@roll-agent/runtime";import{formatToolInput as n}from"../../utils/tool-format.js";import{GLYPHS as e}from"../../utils/glyphs.js";const r=new Set(["error-text","error-json","execution-denied"]);function s(t){return"string"==typeof t?t:t.map(t=>"text"===t.type?t.text:"").join("")}function i(t){const o=t.indexOf("__");return o>=0?`${t.slice(0,o)}.${t.slice(o+2)}`:t}export function messagesToHistory(c,l="h"){const a=new Map;for(const t of c)if("tool"===t.role&&"string"!=typeof t.content)for(const o of t.content)if("tool-result"===o.type){const t=o.output.type;a.set(o.toolCallId,!r.has(t))}const f=[];return c.forEach((r,c)=>{const p=`${l}-${String(c)}`;if("user"===r.role){const t=s(r.content);return t.startsWith(o)?void f.push({kind:"compaction",id:p,notice:`${e.compact} 已恢复的上下文摘要`}):void(t.length>0&&f.push({kind:"user",id:p,text:t}))}if("assistant"===r.role){const o=s(r.content);if(o.startsWith(t))return;o.length>0&&f.push({kind:"assistant",id:p,text:o}),"string"!=typeof r.content&&r.content.forEach((t,o)=>{"tool-call"===t.type&&f.push({kind:"tool",id:`${p}-${String(o)}`,name:i(t.toolName),args:n(t.input),ok:a.get(t.toolCallId)??!0})})}}),f}
1
+ import{readTurnCancellationReason as t,SUMMARY_ACK as o,SUMMARY_PREFIX as n}from"@roll-agent/runtime";import{formatToolInput as e}from"../../utils/tool-format.js";import{GLYPHS as r}from"../../utils/glyphs.js";const s=new Set(["error-text","error-json","execution-denied"]);function i(t){return"string"==typeof t?t:t.map(t=>"text"===t.type?t.text:"").join("")}function c(t){const o=t.indexOf("__");return o>=0?`${t.slice(0,o)}.${t.slice(o+2)}`:t}export function messagesToHistory(l,a="h"){const f=new Map;for(const t of l)if("tool"===t.role&&"string"!=typeof t.content)for(const o of t.content)if("tool-result"===o.type){const t=o.output.type;f.set(o.toolCallId,!s.has(t))}const u=[];return l.forEach((s,l)=>{const p=`${a}-${String(l)}`;if("user"===s.role){const t=i(s.content);return t.startsWith(n)?void u.push({kind:"compaction",id:p,notice:`${r.compact} 已恢复的上下文摘要`}):void(t.length>0&&u.push({kind:"user",id:p,text:t}))}if("assistant"===s.role){const n=i(s.content);if(n.startsWith(o))return;if(n.length>0){const o=t(s);u.push(o?{kind:"turn-cancelled",id:p,text:n,reason:o}:{kind:"assistant",id:p,text:n})}"string"!=typeof s.content&&s.content.forEach((t,o)=>{"tool-call"===t.type&&u.push({kind:"tool",id:`${p}-${String(o)}`,name:c(t.toolName),args:e(t.input),ok:f.get(t.toolCallId)??!0})})}}),u}
@@ -1 +1 @@
1
- import{createElement as r}from"react";import{Box as 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,"")}}
1
+ import{createElement as r}from"react";import{Box as e,Text as t}from"ink";import{parseThinking as o}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as l}from"./tool-label.js";import{BannerLinesView as i}from"./banner-view.js";import{ReasoningBlock as s}from"./reasoning-block.js";const a=["已取消执行","策略拒绝执行"];function c(r){const e=r.trimStart();return a.some(r=>e.startsWith(r))}export function HistoryItemView({item:a}){switch(a.kind){case"banner":return r(i,{lines:a.lines});case"user":return r(e,null,r(t,{color:"cyan",bold:!0},"▌ "),r(t,{color:"cyan"},a.text));case"assistant":return c(a.text)?r(e,null,r(t,{dimColor:!0},"⊘ "),r(t,{dimColor:!0},a.text.trim())):r(e,{flexDirection:"column"},...o(a.text).map((e,o)=>e.thinking?r(t,{key:String(o),dimColor:!0},e.text):r(n,{key:String(o),text:e.text})));case"reasoning":return r(s,{text:a.text});case"tool":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(t,null,r(t,a.ok?{color:"green"}:{color:"red"},a.ok?"✓ ":"✗ "),r(l,{name:a.name}),e.length>0?r(t,{dimColor:!0},e):null)}case"denied":return r(t,{dimColor:!0},`⊘ ${a.name} ${a.label}`);case"cancelled":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(t,{color:"yellow"},"■ ",r(l,{name:a.name}),e.length>0?r(t,{dimColor:!0},e):null,r(t,{dimColor:!0}," 已中断"))}case"compaction":return r(t,{dimColor:!0},a.notice);case"turn-cancelled":{const o="user"===a.reason?{prefix:"■",textProps:{dimColor:!0}}:"timeout"===a.reason?{prefix:"⚠",textProps:{color:"yellow"}}:{prefix:"✗",textProps:{color:"red"}};return r(e,{alignItems:"flex-start"},r(e,{width:2,flexShrink:0},r(t,o.textProps,o.prefix)),r(e,{flexGrow:1,flexShrink:1},r(t,{...o.textProps,wrap:"wrap"},a.text)))}case"notice":return r(e,null,r(t,{color:"yellow"},"⚠ "),r(t,{color:"yellow"},a.text));case"error":return r(e,null,r(t,{color:"red"},"✗ "),r(t,{color:"red"},a.message));default:return r(t,null,"")}}
@@ -0,0 +1,2 @@
1
+ export declare const INPUT_HISTORY_LIMIT = 50;
2
+ export declare function appendInputHistory(history: readonly string[], raw: string): readonly string[];
@@ -0,0 +1 @@
1
+ export const INPUT_HISTORY_LIMIT=50;export function appendInputHistory(t,n){const r=n.trim();if(0===r.length)return t;return[...t.filter(t=>t!==r).slice(-49),r]}
@@ -19,3 +19,5 @@ export declare function moveToLineStart(state: LineBufferState): LineBufferState
19
19
  export declare function moveToLineEnd(state: LineBufferState): LineBufferState;
20
20
  export declare function moveUp(state: LineBufferState): LineBufferState;
21
21
  export declare function moveDown(state: LineBufferState): LineBufferState;
22
+ export declare function moveVisualUp(state: LineBufferState, columns: number): LineBufferState;
23
+ export declare function moveVisualDown(state: LineBufferState, columns: number): LineBufferState;
@@ -1 +1 @@
1
- import{displayWidth as r}from"./display-width.js";const e=new Intl.Segmenter(void 0,{granularity:"grapheme"}),n=new Intl.Segmenter(void 0,{granularity:"word"});function t(r,e){return 0===e?0:r.lastIndexOf("\n",e-1)+1}function o(r,e){const n=t(r,e),o=r.indexOf("\n",e);return{start:n,end:-1===o?r.length:o}}function u(r){const[n]=e.segment(r);return n?.segment}function l(r,e){if(e>=r.length)return r.length;const n=u(r.slice(e));return void 0===n?r.length:e+n.length}function c(r,n){if(n<=0)return 0;const o=t(r,n);if(n===o)return o-1;let u=o;for(const t of e.segment(r.slice(o,n)))u=o+t.index;return u}function a(r,n,t){const o=Math.max(0,Math.min(n,r.length));if(0===o)return 0;if(o===r.length)return r.length;const u=e.segment(r).containing(o);return void 0===u||u.index===o?o:"backward"===t?u.index:u.index+u.segment.length}function i(r,e,n,t=""){const o=r.value.slice(0,e)+t+r.value.slice(n);return{value:o,cursor:a(o,e+t.length,"forward"),goalColumn:void 0}}function s(r){return!0===r.isWordLike}function v(r,e){let t=0;for(const o of n.segment(r.slice(0,e)))s(o)&&(t=o.index);return t}function f(r,e){for(const t of n.segment(r.slice(e)))if(s(t))return e+t.index+t.segment.length;return r.length}function g(e,n){return r(e.slice(t(e,n),n))}function d(n,t,o,u){let l=t,c=0;for(const a of e.segment(n.slice(t,o))){const e=r(a.segment);if(c+e>u)break;c+=e,l=t+a.index+a.segment.length}return l}function m(r){return void 0===r.goalColumn?r:{value:r.value,cursor:r.cursor,goalColumn:void 0}}export function createLineBuffer(r,e){return{value:r,cursor:a(r,Math.max(0,Math.min(e??r.length,r.length)),"backward"),goalColumn:void 0}}export function graphemeAt(r,e){return e<0||e>=r.length?"":u(r.slice(e))??""}export function insertText(r,e){return 0===e.length?m(r):i(r,r.cursor,r.cursor,e)}export function deleteBackward(r){if(0===r.cursor)return m(r);return i(r,c(r.value,r.cursor),r.cursor)}export function deleteForward(r){if(r.cursor>=r.value.length)return m(r);const e=l(r.value,r.cursor);return i(r,r.cursor,e)}export function deleteWordBackward(r){if(0===r.cursor)return m(r);return i(r,v(r.value,r.cursor),r.cursor)}export function killToLineStart(r){const{start:e}=o(r.value,r.cursor);return e===r.cursor?m(r):i(r,e,r.cursor)}export function killToLineEnd(r){const{end:e}=o(r.value,r.cursor);return r.cursor===e?e===r.value.length?m(r):i(r,r.cursor,e+1):i(r,r.cursor,e)}export function moveLeft(r){return 0===r.cursor?m(r):{value:r.value,cursor:c(r.value,r.cursor),goalColumn:void 0}}export function moveRight(r){return r.cursor>=r.value.length?m(r):{value:r.value,cursor:l(r.value,r.cursor),goalColumn:void 0}}export function moveWordLeft(r){return{value:r.value,cursor:v(r.value,r.cursor),goalColumn:void 0}}export function moveWordRight(r){return{value:r.value,cursor:f(r.value,r.cursor),goalColumn:void 0}}export function moveToLineStart(r){return{value:r.value,cursor:o(r.value,r.cursor).start,goalColumn:void 0}}export function moveToLineEnd(r){return{value:r.value,cursor:o(r.value,r.cursor).end,goalColumn:void 0}}export function moveUp(r){const{start:e}=o(r.value,r.cursor);if(0===e)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e-1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveDown(r){const{end:e}=o(r.value,r.cursor);if(e===r.value.length)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e+1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}
1
+ import{displayWidth as r}from"./display-width.js";const e=new Intl.Segmenter(void 0,{granularity:"grapheme"}),n=new Intl.Segmenter(void 0,{granularity:"word"});function t(r,e){return 0===e?0:r.lastIndexOf("\n",e-1)+1}function o(r,e){const n=t(r,e),o=r.indexOf("\n",e);return{start:n,end:-1===o?r.length:o}}function u(r){const[n]=e.segment(r);return n?.segment}function l(r,e){if(e>=r.length)return r.length;const n=u(r.slice(e));return void 0===n?r.length:e+n.length}function s(r,n){if(n<=0)return 0;const o=t(r,n);if(n===o)return o-1;let u=o;for(const t of e.segment(r.slice(o,n)))u=o+t.index;return u}function c(r,n,t){const o=Math.max(0,Math.min(n,r.length));if(0===o)return 0;if(o===r.length)return r.length;const u=e.segment(r).containing(o);return void 0===u||u.index===o?o:"backward"===t?u.index:u.index+u.segment.length}function a(r,e,n,t=""){const o=r.value.slice(0,e)+t+r.value.slice(n);return{value:o,cursor:c(o,e+t.length,"forward"),goalColumn:void 0}}function i(r){return!0===r.isWordLike}function f(r,e){let t=0;for(const o of n.segment(r.slice(0,e)))i(o)&&(t=o.index);return t}function v(r,e){for(const t of n.segment(r.slice(e)))if(i(t))return e+t.index+t.segment.length;return r.length}function g(e,n){return r(e.slice(t(e,n),n))}function d(n,t,o,u){let l=t,s=0;for(const c of e.segment(n.slice(t,o))){const e=r(c.segment);if(s+e>u)break;s+=e,l=t+c.index+c.segment.length}return l}function m(n,t,o){const u=Math.max(1,Math.floor(t)),l=[];let s=0;for(;s<=n.length;){const t=n.indexOf("\n",s),c=-1===t?n.length:t;let a=s,i=0;for(const t of e.segment(n.slice(s,c))){const e=s+t.index,n=r(t.segment);i>0&&i+n>u&&(l.push({start:a,end:e,softWrap:!0}),a=e,i=0),i+=n}const f=o===c&&i>=u;if(l.push({start:a,end:c,softWrap:f}),f&&l.push({start:c,end:c,softWrap:!1}),-1===t)break;s=t+1}return l}function h(r,e){for(const[n,t]of r.entries())if(e<t.end||e===t.end&&!t.softWrap)return n;return Math.max(0,r.length-1)}function x(e,n,t){const o=m(e.value,t,e.cursor),u=h(o,e.cursor),l=o[u],c=o[u+n];if(void 0===l||void 0===c)return e;const a=e.goalColumn??r(e.value.slice(l.start,e.cursor));let i=d(e.value,c.start,c.end,a);return c.softWrap&&i===c.end&&c.start<c.end&&(i=s(e.value,c.end)),{value:e.value,cursor:i,goalColumn:a}}function p(r){return void 0===r.goalColumn?r:{value:r.value,cursor:r.cursor,goalColumn:void 0}}export function createLineBuffer(r,e){return{value:r,cursor:c(r,Math.max(0,Math.min(e??r.length,r.length)),"backward"),goalColumn:void 0}}export function graphemeAt(r,e){return e<0||e>=r.length?"":u(r.slice(e))??""}export function insertText(r,e){return 0===e.length?p(r):a(r,r.cursor,r.cursor,e)}export function deleteBackward(r){if(0===r.cursor)return p(r);return a(r,s(r.value,r.cursor),r.cursor)}export function deleteForward(r){if(r.cursor>=r.value.length)return p(r);const e=l(r.value,r.cursor);return a(r,r.cursor,e)}export function deleteWordBackward(r){if(0===r.cursor)return p(r);return a(r,f(r.value,r.cursor),r.cursor)}export function killToLineStart(r){const{start:e}=o(r.value,r.cursor);return e===r.cursor?p(r):a(r,e,r.cursor)}export function killToLineEnd(r){const{end:e}=o(r.value,r.cursor);return r.cursor===e?e===r.value.length?p(r):a(r,r.cursor,e+1):a(r,r.cursor,e)}export function moveLeft(r){return 0===r.cursor?p(r):{value:r.value,cursor:s(r.value,r.cursor),goalColumn:void 0}}export function moveRight(r){return r.cursor>=r.value.length?p(r):{value:r.value,cursor:l(r.value,r.cursor),goalColumn:void 0}}export function moveWordLeft(r){return{value:r.value,cursor:f(r.value,r.cursor),goalColumn:void 0}}export function moveWordRight(r){return{value:r.value,cursor:v(r.value,r.cursor),goalColumn:void 0}}export function moveToLineStart(r){return{value:r.value,cursor:o(r.value,r.cursor).start,goalColumn:void 0}}export function moveToLineEnd(r){return{value:r.value,cursor:o(r.value,r.cursor).end,goalColumn:void 0}}export function moveUp(r){const{start:e}=o(r.value,r.cursor);if(0===e)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e-1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveDown(r){const{end:e}=o(r.value,r.cursor);if(e===r.value.length)return r;const n=r.goalColumn??g(r.value,r.cursor),t=o(r.value,e+1);return{value:r.value,cursor:d(r.value,t.start,t.end,n),goalColumn:n}}export function moveVisualUp(r,e){return x(r,-1,e)}export function moveVisualDown(r,e){return x(r,1,e)}
@@ -1,5 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { LiveState } from "./state.ts";
3
+ /** Live 推理只保留最近几行做滚动预览;完整文本在 reasoning 结束时照常落入 history。 */
4
+ export declare function reasoningTail(text: string): string;
3
5
  export declare function LiveRegion({ live }: {
4
6
  live: LiveState;
5
7
  }): ReactElement;
@@ -1 +1 @@
1
- import{createElement as l}from"react";import{Box as n,Text as t}from"ink";import{Spinner as i}from"./spinner.js";import{ThinkingText as o}from"./thinking-text.js";import{ToolLabel as e}from"./tool-label.js";const r=3;function m(l){return void 0===l?[]:l.split("\n").map(l=>l.trimEnd()).filter(l=>l.length>0).slice(-3)}export function LiveRegion({live:r}){return l(n,{flexDirection:"column"},r.streamingText.length>0?l(n,{marginTop:1},l(o,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText})):null,r.thinking?l(n,{marginTop:1},l(i,null),l(t,{dimColor:!0}," 思考中…")):null,...r.activeTools.map(o=>l(n,{key:o.toolCallId,marginLeft:2,flexDirection:"column"},l(n,null,l(i,null),l(t,null," "),l(e,{name:o.name}),o.args.length>0?l(t,{dimColor:!0},` ${o.args}`):null),...m(o.outputTail).map((n,i)=>l(t,{key:`${o.toolCallId}-tail-${String(i)}`,dimColor:!0},` ${n}`)))),r.compacting?l(n,null,l(i,null),l(t,{dimColor:!0}," 压缩上下文中…")):null)}
1
+ import{createElement as n}from"react";import{Box as t,Text as e}from"ink";import{ThinkingText as i}from"./thinking-text.js";import{ToolLabel as o}from"./tool-label.js";import{ReasoningBlock as l}from"./reasoning-block.js";const r=3,a=3;export function reasoningTail(n){const t=n.split("\n").map(n=>n.trimEnd()).filter(n=>n.length>0),e=t.slice(-3);return t.length>e.length?["…",...e].join("\n"):e.join("\n")}function m(n){return void 0===n?[]:n.split("\n").map(n=>n.trimEnd()).filter(n=>n.length>0).slice(-3)}export function LiveRegion({live:r}){const a=reasoningTail(r.reasoningText);return n(t,{flexDirection:"column"},a.length>0?n(t,{marginTop:1},n(l,{text:a})):null,r.streamingText.length>0?n(t,{marginTop:1},n(i,{text:r.thinkTagOpen?`<think>${r.streamingText}`:r.streamingText})):null,...r.activeTools.map(i=>n(t,{key:i.toolCallId,marginTop:1,marginLeft:2,flexDirection:"column"},n(t,null,n(e,{color:"cyan"},"· "),n(o,{name:i.name}),i.args.length>0?n(e,{dimColor:!0},` ${i.args}`):null),...m(i.outputTail).map((t,o)=>n(e,{key:`${i.toolCallId}-tail-${String(o)}`,dimColor:!0},` ${t}`)))))}
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function ReasoningBlock({ text }: {
3
+ text: string;
4
+ }): ReactElement;
@@ -0,0 +1 @@
1
+ import{createElement as o}from"react";import{Box as r,Text as e}from"ink";export function ReasoningBlock({text:t}){return o(r,{flexDirection:"column"},o(r,null,o(e,{color:"magenta"},"◇"),o(e,{dimColor:!0}," 推理过程")),o(r,{borderStyle:"single",borderColor:"gray",borderTop:!1,borderRight:!1,borderBottom:!1,paddingLeft:1},o(e,{dimColor:!0},t)))}
@@ -1 +1 @@
1
- import{createElement as t}from"react";import{render as e}from"ink";import{ChatApp as i}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{buildBannerLines as s}from"../banner.js";import{log as a}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · Esc 中断 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(r,l,m,d){let p=!1,c=!m;const g=t=>{p=!0,c||(l.updateTitle(r.id,o(t)),c=!0)};d.banner||a.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:r,model:d.model,contextWindow:r.getContextWindow(),availableSkills:r.getSkillSummaries(),onUserSubmit:g,onExit:()=>{f.unmount()},initialHistory:[...h,...n(r.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(),await r.close(),m&&!p&&0===l.countMessages(r.id)&&l.deleteThread(r.id)}
1
+ import{createElement as t}from"react";import{render as e}from"ink";import{ChatApp as i}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{buildBannerLines as s}from"../banner.js";import{log as a}from"../../utils/output.js";export const INK_HINTS="/exit 退出 · Esc 中断 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理 · Shift+Tab 自动批准";export async function runInkRepl(r,l,d,m){let p=!1,c=!d;const g=t=>{p=!0,c||(l.updateTitle(r.id,o(t)),c=!0)};m.banner||a.info(`多轮对话已就绪(${INK_HINTS})`);const h=n(r.getMessages()),u=m.banner?s(m.banner,process.stdout.columns||80,{hints:INK_HINTS}):void 0,f=void 0!==u&&0===h.length,T=void 0===u||f?[]:[{kind:"banner",id:"banner",lines:u}],k=!0===process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode;let b;k&&process.stdin.setRawMode(!0);try{b=e(t(i,{session:r,model:m.model,contextWindow:r.getContextWindow(),availableSkills:r.getSkillSummaries(),onUserSubmit:g,onExit:()=>{b.unmount()},initialHistory:[...T,...h],...f&&void 0!==u?{animatedBanner:u}:{},...m.initialThinkingLevel?{initialThinkingLevel:m.initialThinkingLevel}:{},...m.onThinkingChange?{onThinkingChange:m.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}})}catch(t){throw k&&process.stdin.setRawMode(!1),t}await b.waitUntilExit(),await r.close(),d&&!p&&0===l.countMessages(r.id)&&l.deleteThread(r.id)}
@@ -1,2 +1,4 @@
1
1
  import type { ReactElement } from "react";
2
- export declare function Spinner(): ReactElement;
2
+ export declare function Spinner({ color }: {
3
+ readonly color?: string;
4
+ }): ReactElement;
@@ -1 +1 @@
1
- import{createElement as r,useEffect as t,useState as e}from"react";import{Text as n}from"ink";import o from"is-unicode-supported";const c=o()?["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]:["-","\\","|","/"],i=80;export function Spinner(){const[o,a]=e(0);return t(()=>{const r=setInterval(()=>{a(r=>(r+1)%c.length)},i);return()=>{clearInterval(r)}},[]),r(n,{color:"cyan"},c[o]??c[0]??"-")}
1
+ import{createElement as r,useEffect as t,useState as o}from"react";import{Text as e}from"ink";import n from"is-unicode-supported";const c=n()?["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]:["-","\\","|","/"],i=80;export function Spinner({color:n="cyan"}){const[a,l]=o(0);return t(()=>{const r=setInterval(()=>{l(r=>(r+1)%c.length)},i);return()=>{clearInterval(r)}},[]),r(e,{color:n},c[a]??c[0]??"-")}
@@ -1,4 +1,4 @@
1
- import type { SessionEvent, SessionTokenUsage } from "@roll-agent/runtime";
1
+ import { type SessionEvent, type SessionTokenUsage } from "@roll-agent/runtime";
2
2
  import type { ThinkingLevel } from "../../../llm/providers.ts";
3
3
  import type { BannerLine } from "../banner.ts";
4
4
  export interface ToolRowState {
@@ -7,6 +7,9 @@ export interface ToolRowState {
7
7
  readonly args: string;
8
8
  readonly outputTail?: string;
9
9
  }
10
+ type TurnCancelledReason = Extract<SessionEvent, {
11
+ type: "turn-cancelled";
12
+ }>["reason"];
10
13
  export type HistoryItem = {
11
14
  readonly kind: "banner";
12
15
  readonly id: string;
@@ -19,6 +22,10 @@ export type HistoryItem = {
19
22
  readonly kind: "assistant";
20
23
  readonly id: string;
21
24
  readonly text: string;
25
+ } | {
26
+ readonly kind: "reasoning";
27
+ readonly id: string;
28
+ readonly text: string;
22
29
  } | {
23
30
  readonly kind: "tool";
24
31
  readonly id: string;
@@ -39,6 +46,11 @@ export type HistoryItem = {
39
46
  readonly kind: "compaction";
40
47
  readonly id: string;
41
48
  readonly notice: string;
49
+ } | {
50
+ readonly kind: "turn-cancelled";
51
+ readonly id: string;
52
+ readonly text: string;
53
+ readonly reason: TurnCancelledReason;
42
54
  } | {
43
55
  readonly kind: "notice";
44
56
  readonly id: string;
@@ -50,7 +62,9 @@ export type HistoryItem = {
50
62
  };
51
63
  export interface LiveState {
52
64
  readonly streamingText: string;
53
- readonly thinking: boolean;
65
+ readonly reasoningId: string | undefined;
66
+ readonly reasoningText: string;
67
+ readonly reasoningActive: boolean;
54
68
  readonly thinkTagOpen: boolean;
55
69
  readonly activeTools: readonly ToolRowState[];
56
70
  readonly compacting: boolean;
@@ -66,7 +80,13 @@ export interface StatusState {
66
80
  readonly thinkingLevel: ThinkingLevel;
67
81
  readonly autoApprove: boolean;
68
82
  }
69
- export type ChatPhase = "idle" | "busy" | "confirm";
83
+ export declare const CHAT_PHASES: {
84
+ readonly idle: "idle";
85
+ readonly busy: "busy";
86
+ readonly confirm: "confirm";
87
+ readonly cancelling: "cancelling";
88
+ };
89
+ export type ChatPhase = (typeof CHAT_PHASES)[keyof typeof CHAT_PHASES];
70
90
  export interface PendingConfirm {
71
91
  readonly approvalId: string;
72
92
  readonly prompt: string;
@@ -104,6 +124,8 @@ export type ChatUiAction = {
104
124
  readonly event: SessionEvent;
105
125
  } | {
106
126
  readonly type: "confirm-resolved";
127
+ } | {
128
+ readonly type: "cancel-requested";
107
129
  } | {
108
130
  readonly type: "turn-end";
109
131
  };
@@ -117,3 +139,4 @@ export declare function buildConfirmPrompt(event: Extract<SessionEvent, {
117
139
  }>): string;
118
140
  export declare function denialLabel(output: unknown): string | undefined;
119
141
  export declare function chatReducer(state: ChatUiState, action: ChatUiAction): ChatUiState;
142
+ export {};
@@ -1 +1 @@
1
- import{formatToolInput as t,formatApprovalDetails as e}from"../../utils/tool-format.js";import{GLYPHS as i}from"../../utils/glyphs.js";import{endsInsideThink as n}from"./thinking-text.js";const o=2e3,r={streamingText:"",thinking:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function s(t,e){return e?`<think>${t}`:t}export function createInitialState(t,e,i){return{history:i?.history??[],draft:"",live:r,status:{model:t,contextWindow:e,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,outputTokensPerSecond:void 0,thinkingLevel:i?.thinkingLevel??"medium",autoApprove:!1},phase:"idle",pendingConfirm:void 0}}export function buildConfirmPrompt(t){const e=t.reason?`(${t.reason})`:"";return`执行 ${t.agentName}.${t.toolName}${e}?`}function a(t){const e="auto"===t.reason?"自动压缩":"手动压缩",n=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`${i.compact} ${e}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${n}`:`${i.compact} ${e}:无需压缩`}const l=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function u(t){return"string"==typeof t?t:"object"==typeof t&&null!==t&&"output"in t&&"string"==typeof t.output?t.output:void 0}export function denialLabel(t){const e=u(t);if(void 0!==e)return l.find(([t])=>e.startsWith(t))?.[1]}function c(t,e,i){const n=t.live.activeTools.find(t=>t.toolCallId===i.toolCallId),o=n?.name??`${i.agentName}.${i.toolName}`,r=n?.args??"",s=i.isError?denialLabel(i.output):void 0,a=void 0!==s?{kind:"denied",id:e,name:o,label:s}:{kind:"tool",id:e,name:o,args:r,ok:!i.isError};return{...t,history:[...t.history,a],live:{...t.live,activeTools:t.live.activeTools.filter(t=>t.toolCallId!==i.toolCallId)}}}function d(i,l,u){switch(u.type){case"message-start":return{...i,live:{...i.live,thinking:!0}};case"text-delta":return{...i,live:{...i.live,thinking:!1,producedOutput:!0,streamingText:i.live.streamingText+u.delta}};case"tool-call":{const e=i.live.streamingText.length>0?[{kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}]:[];return{...i,history:[...i.history,...e],live:{...i.live,thinking:!1,producedOutput:!0,streamingText:"",thinkTagOpen:n(i.live.streamingText,i.live.thinkTagOpen),activeTools:[...i.live.activeTools,{toolCallId:u.toolCallId,name:`${u.agentName}.${u.toolName}`,args:t(u.input)}]}}}case"tool-output-delta":{const t=i.live.activeTools.map(t=>{if(t.toolCallId!==u.toolCallId)return t;const e=`${t.outputTail??""}${u.delta}`,i=e.length>o?e.slice(e.length-o):e;return{...t,outputTail:i}});return{...i,live:{...i.live,activeTools:t}}}case"tool-result":return c(i,l,u);case"confirmation-required":return{...i,phase:"confirm",pendingConfirm:{approvalId:u.approvalId,prompt:buildConfirmPrompt(u),args:e(u.input)}};case"compaction-start":return{...i,live:{...i.live,compacting:!0}};case"context-compacted":return{...i,live:{...i.live,compacting:!1},history:[...i.history,{kind:"compaction",id:l,notice:a(u)}]};case"message-finish":{const t=[];return i.live.streamingText.length>0?t.push({kind:"assistant",id:l,text:s(i.live.streamingText,i.live.thinkTagOpen)}):!i.live.producedOutput&&(u.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:l,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),u.stoppedAtStepLimit&&t.push({kind:"notice",id:`${l}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...i,history:[...i.history,...t],live:{...r},status:{...i.status,turnUsage:u.totalUsage,sessionUsage:u.sessionUsage,contextInputTokens:u.contextInputTokens,outputTokensPerSecond:u.outputTokensPerSecond}}}case"turn-cancelled":{const t=i.live.activeTools.map((t,e)=>({kind:"cancelled",id:`${l}-tool-${String(e)}`,name:t.name,args:t.args}));return{...i,history:[...i.history,...t,{kind:"notice",id:l,text:u.message}],live:{...r}}}case"error":return{...i,history:[...i.history,{kind:"error",id:l,message:u.message}],live:{...r}};default:return i}}export function chatReducer(t,e){switch(e.type){case"submit-user":return{...t,history:[...t.history,{kind:"user",id:e.id,text:e.text}],draft:"",live:{...r},phase:"busy",pendingConfirm:void 0};case"set-draft":return{...t,draft:e.value};case"set-thinking":return{...t,status:{...t.status,thinkingLevel:e.level}};case"set-auto":return{...t,status:{...t.status,autoApprove:e.value}};case"commit-history":return{...t,history:[...t.history,e.item],draft:""};case"start-compaction":return{...t,live:{...r},phase:"busy",pendingConfirm:void 0};case"session-event":return d(t,e.id,e.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...r}};default:return t}}
1
+ import{TOOL_OUTCOME_KINDS as e}from"@roll-agent/runtime";import{formatToolInput as t,formatApprovalDetails as n}from"../../utils/tool-format.js";import{GLYPHS as i}from"../../utils/glyphs.js";import{endsInsideThink as o}from"./thinking-text.js";const r=2e3;export const CHAT_PHASES={idle:"idle",busy:"busy",confirm:"confirm",cancelling:"cancelling"};const s={streamingText:"",reasoningId:void 0,reasoningText:"",reasoningActive:!1,thinkTagOpen:!1,activeTools:[],compacting:!1,producedOutput:!1};function a(e,t){return t?`<think>${e}`:e}export function createInitialState(e,t,n){return{history:n?.history??[],draft:"",live:s,status:{model:e,contextWindow:t,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,outputTokensPerSecond:void 0,thinkingLevel:n?.thinkingLevel??"medium",autoApprove:!1},phase:"idle",pendingConfirm:void 0}}export function buildConfirmPrompt(e){const t=e.reason?`(${e.reason})`:"";return`执行 ${e.agentName}.${e.toolName}${t}?`}function l(e){const t="auto"===e.reason?"自动压缩":"手动压缩",n=e.truncatedTools?`,精简 ${String(e.truncatedTools)} 个工具结果`:"",o=e.checkpointGeneration?`,checkpoint #${String(e.checkpointGeneration)}${e.checkpointSummaryStatus&&"valid"!==e.checkpointSummaryStatus?`(${e.checkpointSummaryStatus})`:""}`:"";return 0!==e.removed||e.truncatedTools?`${i.compact} ${t}(${e.strategy}):移除 ${String(e.removed)} 条 → 保留 ${String(e.kept)} 条${n}${o}`:`${i.compact} ${t}:无需压缩`}const c=[["已取消执行","已取消"],["策略拒绝执行","策略拒绝"]];function u(e){return"string"==typeof e?e:"object"==typeof e&&null!==e&&"output"in e&&"string"==typeof e.output?e.output:void 0}export function denialLabel(e){const t=u(e);if(void 0!==t)return c.find(([e])=>t.startsWith(e))?.[1]}function d(t,n,i){const o=t.live.activeTools.find(e=>e.toolCallId===i.toolCallId),r=o?.name??`${i.agentName}.${i.toolName}`,s=o?.args??"",a=i.outcome?.kind===e.userRejected?"已取消":i.outcome?.kind===e.policyDenied?"策略拒绝":void 0===i.outcome&&i.isError?denialLabel(i.output):void 0,l=void 0!==a?{kind:"denied",id:n,name:r,label:a}:{kind:"tool",id:n,name:r,args:s,ok:!i.isError};return{...t,history:[...t.history,l],live:{...t.live,activeTools:t.live.activeTools.filter(e=>e.toolCallId!==i.toolCallId)}}}function g(e,t){return 0===e.live.streamingText.length?e:{...e,history:[...e.history,{kind:"assistant",id:t,text:a(e.live.streamingText,e.live.thinkTagOpen)}],live:{...e.live,streamingText:"",thinkTagOpen:o(e.live.streamingText,e.live.thinkTagOpen)}}}function p(e,t){const n=e.live.reasoningText.trim().length>0?[...e.history,{kind:"reasoning",id:t,text:e.live.reasoningText}]:e.history;return{...e,history:n,live:{...e.live,reasoningId:void 0,reasoningText:"",reasoningActive:!1}}}function v(e,t,n){const i=g(p(e,`${t}-previous-reasoning`),t);return{...i,live:{...i.live,reasoningId:n,reasoningText:"",reasoningActive:!0}}}function m(e,i,o){switch(o.type){case"message-start":default:return e;case"reasoning-start":return v(e,i,o.reasoningId);case"reasoning-delta":{const t=e.live.reasoningId===o.reasoningId?e:v(e,i,o.reasoningId);return{...t,live:{...t.live,reasoningText:t.live.reasoningText+o.delta,reasoningActive:!0}}}case"reasoning-end":return e.live.reasoningId===o.reasoningId?p(e,`${i}-reasoning`):e;case"text-delta":{const t=p(e,`${i}-reasoning`);return{...t,live:{...t.live,producedOutput:!0,streamingText:t.live.streamingText+o.delta}}}case"tool-call":{const n=g(p(e,`${i}-reasoning`),i);return{...n,live:{...n.live,producedOutput:!0,activeTools:[...n.live.activeTools,{toolCallId:o.toolCallId,name:`${o.agentName}.${o.toolName}`,args:t(o.input)}]}}}case"tool-output-delta":{const t=e.live.activeTools.map(e=>{if(e.toolCallId!==o.toolCallId)return e;const t=`${e.outputTail??""}${o.delta}`,n=t.length>r?t.slice(t.length-r):t;return{...e,outputTail:n}});return{...e,live:{...e.live,activeTools:t}}}case"tool-result":return d(e,i,o);case"confirmation-required":return{...e,phase:"confirm",pendingConfirm:{approvalId:o.approvalId,prompt:buildConfirmPrompt(o),args:n(o.input)}};case"compaction-start":return{...e,live:{...e.live,compacting:!0}};case"context-compacted":return{...e,live:{...e.live,compacting:!1},history:[...e.history,{kind:"compaction",id:i,notice:l(o)}]};case"message-finish":{const t=p(e,`${i}-reasoning`),n=[];return t.live.streamingText.length>0?n.push({kind:"assistant",id:i,text:a(t.live.streamingText,t.live.thinkTagOpen)}):!t.live.producedOutput&&(o.totalUsage?.outputTokens??0)>0&&n.push({kind:"notice",id:i,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),o.stoppedAtStepLimit&&n.push({kind:"notice",id:`${i}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...t,history:[...t.history,...n],live:{...s},status:{...t.status,turnUsage:o.totalUsage,sessionUsage:o.sessionUsage,contextInputTokens:o.contextInputTokens,outputTokensPerSecond:o.outputTokensPerSecond}}}case"turn-cancelled":{const t=e.live.activeTools.map((e,t)=>({kind:"cancelled",id:`${i}-tool-${String(t)}`,name:e.name,args:e.args}));return{...e,history:[...e.history,...t,{kind:"turn-cancelled",id:i,text:o.message,reason:o.reason}],live:{...s}}}case"error":return{...e,history:[...e.history,{kind:"error",id:i,message:o.message}],live:{...s}}}}export function chatReducer(e,t){switch(t.type){case"submit-user":return{...e,history:[...e.history,{kind:"user",id:t.id,text:t.text}],draft:"",live:{...s},phase:"busy",pendingConfirm:void 0};case"set-draft":return{...e,draft:t.value};case"set-thinking":return{...e,status:{...e.status,thinkingLevel:t.level}};case"set-auto":return{...e,status:{...e.status,autoApprove:t.value}};case"commit-history":return{...e,history:[...e.history,t.item],draft:""};case"start-compaction":return{...e,live:{...s,compacting:!0},phase:"busy",pendingConfirm:void 0};case"session-event":return m(e,t.id,t.event);case"confirm-resolved":return{...e,phase:"busy",pendingConfirm:void 0};case"cancel-requested":return{...e,phase:"cancelling",pendingConfirm:void 0};case"turn-end":return{...e,phase:"idle",live:{...s}};default:return e}}
@@ -1,7 +1,9 @@
1
1
  import type { ReactElement } from "react";
2
2
  export interface TextPromptProps {
3
3
  readonly value: string;
4
+ readonly inputHistory: readonly string[];
4
5
  readonly disabled: boolean;
6
+ readonly disabledHint?: string;
5
7
  readonly slashActive: boolean;
6
8
  readonly slashPopupActive: boolean;
7
9
  readonly autoApprove: boolean;
@@ -9,7 +11,7 @@ export interface TextPromptProps {
9
11
  readonly onSubmit: (value: string) => void;
10
12
  readonly onSlashMove: (direction: 1 | -1) => void;
11
13
  readonly onSlashComplete: () => void;
12
- readonly onSlashRun: () => void;
14
+ readonly onSlashRun: (value: string) => void;
13
15
  }
14
16
  export declare function cursorPositionOf(lines: readonly string[], cursor: number): {
15
17
  row: number;
@@ -1 +1 @@
1
- import{createElement as r,useLayoutEffect as t,useRef as e,useState as n}from"react";import{Box as o,Text as l,useInput as i,usePaste as u,useStdout as c}from"ink";import{GLYPHS as s}from"../../utils/glyphs.js";import{applyEditorCommand as f,resolveEditorCommand as a}from"./editor-keymap.js";import{createLineBuffer as d,graphemeAt as h,insertText as m}from"./line-buffer.js";function p(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function cursorPositionOf(r,t){let e=t;for(const[t,n]of r.entries()){if(e<=n.length)return{row:t,col:e};e-=n.length+1}const n=r.length-1;return{row:n,col:r[n]?.length??0}}export function TextPrompt(v){const{value:g,disabled:w,slashActive:S,slashPopupActive:b,autoApprove:A,onChange:y,onSubmit:C}=v,{stdout:E}=c(),x=E.columns||80,[k]=n(()=>d(g)),j=e(k),[,P]=n(0);t(()=>{j.current.value!==g&&(j.current=d(g),P(r=>r+1))},[g]);const T=r=>{const t=j.current.value!==r.value;j.current=r,P(r=>r+1),t&&y(r.value)};i((r,t)=>{if(t.meta&&("."===r||","===r))return;if(p(r))return;if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void T(m(j.current,"\n"));if(t.return||r.includes("\r")){if(S)return void v.onSlashRun();const t=r.split("\r",1)[0]??"",e=t.length>0?m(j.current,t):j.current;return void C(e.value)}if(b){if(t.upArrow)return void v.onSlashMove(-1);if(t.downArrow)return void v.onSlashMove(1);if(t.tab&&!t.shift)return void v.onSlashComplete()}if(S&&t.escape)return void T(d(""));const e=a(r,t);void 0===e?t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&T(m(j.current,r)):T(f(e,j.current))},{isActive:!w}),u(r=>{T(m(j.current,r.replace(/\r\n?/g,"\n")))},{isActive:!w});const $=j.current,D=$.value.split("\n"),J=cursorPositionOf(D,$.cursor),M=r(o,{flexDirection:"column"},...D.map((t,e)=>{const n=0===e?r(l,{color:"green"},"› "):r(l,null," ");if(w)return r(o,{key:String(e)},n,r(l,{dimColor:!0},t));if(e!==J.row)return r(o,{key:String(e)},n,r(l,null,t));const i=h(t,J.col),u=t.slice(0,J.col),c=t.slice(J.col+i.length);return r(o,{key:String(e)},n,u.length>0?r(l,null,u):null,r(l,{inverse:!0},""===i?" ":i),c.length>0?r(l,null,c):null)})),O=w?"Esc 中断本轮":S?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":A?"Shift+Tab 关闭 · Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",L=r(o,{marginLeft:1},...A?[r(l,{color:"yellow"},`${s.auto} auto`),r(l,{dimColor:!0},` · ${O}`)]:[r(l,{dimColor:!0},O)]);return r(o,{flexDirection:"column",width:x},r(o,{borderStyle:"round",borderColor:w?"yellow":"cyan",paddingX:2},M),L)}
1
+ import{createElement as r,useLayoutEffect as t,useRef as e,useState as n}from"react";import{Box as o,Text as i,useInput as u,usePaste as c,useStdout as l}from"ink";import{GLYPHS as s}from"../../utils/glyphs.js";import{applyEditorCommand as a,resolveEditorCommand as d}from"./editor-keymap.js";import{createLineBuffer as f,graphemeAt as v,insertText as h,moveVisualDown as p,moveVisualUp as m}from"./line-buffer.js";const w=2,g=4,A=2;function S(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function cursorPositionOf(r,t){let e=t;for(const[t,n]of r.entries()){if(e<=n.length)return{row:t,col:e};e-=n.length+1}const n=r.length-1;return{row:n,col:r[n]?.length??0}}export function TextPrompt(w){const{value:g,inputHistory:A,disabled:b,slashActive:y,slashPopupActive:C,autoApprove:x,onChange:E,onSubmit:k}=w,{stdout:j}=l(),P=j.columns||80,T=Math.max(1,P-2-4-2),[$]=n(()=>f(g)),H=e($),M=e(void 0),D=e(""),[,J]=n(0);t(()=>{H.current.value!==g&&(H.current=f(g),M.current=void 0,D.current="",J(r=>r+1))},[g]);const O=r=>{const t=H.current.value!==r.value;H.current=r,J(r=>r+1),t&&E(r.value)},L=()=>{M.current=void 0,D.current=""};u((r,t)=>{if(t.meta&&("."===r||","===r))return;if(S(r))return;if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return L(),void O(h(H.current,"\n"));if(t.return||r.includes("\r")){L();const t=r.split("\r",1)[0]??"",e=t.length>0?h(H.current,t):H.current;return y||e.value.startsWith("/")?void w.onSlashRun(e.value):void k(e.value)}if(t.upArrow||t.downArrow){const r=void 0!==M.current,e=t.upArrow&&0===H.current.value.length;if((r||e)&&(r=>{const t=M.current;if(void 0===t){if(1===r||H.current.value.length>0||0===A.length)return!1;const t=A.length-1,e=A[t];return void 0!==e&&(D.current=H.current.value,M.current=t,O(f(e)),!0)}const e=t+r;if(e<0)return!0;if(e>=A.length){const r=D.current;return L(),O(f(r)),!0}const n=A[e];return void 0===n?(L(),!1):(M.current=e,O(f(n)),!0)})(t.upArrow?-1:1))return}if(C){if(t.upArrow)return void w.onSlashMove(-1);if(t.downArrow)return void w.onSlashMove(1);if(t.tab&&!t.shift)return void w.onSlashComplete()}if(y&&t.escape)return L(),void O(f(""));const e=d(r,t);if(void 0!==e){L();const r="move-up"===e?m(H.current,T):"move-down"===e?p(H.current,T):a(e,H.current);return void O(r)}t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&(L(),O(h(H.current,r)))},{isActive:!b}),c(r=>{L(),O(h(H.current,r.replace(/\r\n?/g,"\n")))},{isActive:!b});const R=H.current,W=R.value.split("\n"),X=cursorPositionOf(W,R.cursor),q=r(o,{flexDirection:"column"},...W.map((t,e)=>{const n=r(o,{width:2,flexShrink:0},0===e?r(i,b?{dimColor:!0}:{color:"green"},"› "):r(i,null," "));if(b)return r(o,{key:String(e)},n,r(i,{dimColor:!0,wrap:"hard"},t));if(e!==X.row)return r(o,{key:String(e)},n,r(i,{wrap:"hard"},t));const u=v(t,X.col),c=t.slice(0,X.col),l=t.slice(X.col+u.length);return r(o,{key:String(e)},n,r(i,{wrap:"hard"},c,r(i,{inverse:!0},""===u?" ":u),l))})),z=b?w.disabledHint??"Esc 中断本轮":y?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":x?"Shift+Tab 关闭 · Enter 发送 · 空输入 ↑ 历史 · Shift+Enter/Ctrl+J 换行 · / 命令":"Enter 发送 · 空输入 ↑ 历史 · Shift+Enter/Ctrl+J 换行 · / 命令 · Shift+Tab 自动批准",B=b&&void 0!==w.disabledHint?{color:"yellow",dimColor:!0}:{dimColor:!0},F=r(o,{marginLeft:1},...x?[r(i,{color:"yellow"},`${s.auto} auto`),r(i,B,` · ${z}`)]:[r(i,B,z)]);return r(o,{flexDirection:"column",width:P},r(o,{borderStyle:"round",borderColor:b?"gray":"cyan",paddingX:2},q),F)}
@@ -0,0 +1,19 @@
1
+ import { type ChatUiState } from "./state.ts";
2
+ export declare const TURN_ACTIVITY_KINDS: {
3
+ readonly waitingModel: "waiting-model";
4
+ readonly reasoning: "reasoning";
5
+ readonly replying: "replying";
6
+ readonly tool: "tool";
7
+ readonly compacting: "compacting";
8
+ readonly waitingUser: "waiting-user";
9
+ readonly cancelling: "cancelling";
10
+ };
11
+ export type TurnActivityKind = (typeof TURN_ACTIVITY_KINDS)[keyof typeof TURN_ACTIVITY_KINDS];
12
+ export interface TurnActivity {
13
+ readonly kind: TurnActivityKind;
14
+ readonly key: string;
15
+ readonly label: string;
16
+ readonly animated: boolean;
17
+ readonly showPhaseElapsed: boolean;
18
+ }
19
+ export declare function resolveTurnActivity(state: ChatUiState): TurnActivity | undefined;
@@ -0,0 +1 @@
1
+ import{CHAT_PHASES as e}from"./state.js";import{endsInsideThink as i}from"./thinking-text.js";export const TURN_ACTIVITY_KINDS={waitingModel:"waiting-model",reasoning:"reasoning",replying:"replying",tool:"tool",compacting:"compacting",waitingUser:"waiting-user",cancelling:"cancelling"};function n(e,i,n){return{kind:e,key:n?.key??e,label:i,animated:n?.animated??!0,showPhaseElapsed:n?.timed??!0}}export function resolveTurnActivity(t){if(t.phase===e.idle)return;if(t.phase===e.cancelling)return n(TURN_ACTIVITY_KINDS.cancelling,"正在中断…");if(t.phase===e.confirm)return n(TURN_ACTIVITY_KINDS.waitingUser,"等待你确认…",{key:`waiting-user:${t.pendingConfirm?.approvalId??"unknown"}`,animated:!1,timed:!1});if(t.live.compacting)return n(TURN_ACTIVITY_KINDS.compacting,"压缩上下文中…");const o=t.live.activeTools[0];if(void 0!==o){const e=t.live.activeTools.length-1>0?` 等 ${String(t.live.activeTools.length)} 项`:"";return n(TURN_ACTIVITY_KINDS.tool,`执行 ${o.name}${e}…`,{key:`tool:${t.live.activeTools.map(e=>e.toolCallId).join(",")}`})}return t.live.reasoningActive?n(TURN_ACTIVITY_KINDS.reasoning,"思考中…"):t.live.streamingText.length>0&&i(t.live.streamingText,t.live.thinkTagOpen)?n(TURN_ACTIVITY_KINDS.reasoning,"思考中…",{key:"inline-reasoning"}):t.live.streamingText.length>0?n(TURN_ACTIVITY_KINDS.replying,"回复中…"):n(TURN_ACTIVITY_KINDS.waitingModel,"等待模型响应…")}
@@ -0,0 +1,14 @@
1
+ import type { ReactElement } from "react";
2
+ import { type TurnActivity } from "./turn-activity.ts";
3
+ export declare function formatElapsed(elapsedMs: number): string;
4
+ export interface TurnStatusLayout {
5
+ readonly label: string;
6
+ readonly phaseTime: string | undefined;
7
+ readonly spacer: string;
8
+ readonly turnTime: string;
9
+ }
10
+ export declare function composeTurnStatusLayout(activity: TurnActivity, phaseElapsedMs: number, turnElapsedMs: number, width: number): TurnStatusLayout;
11
+ export declare function composeTurnStatusLine(activity: TurnActivity, phaseElapsedMs: number, turnElapsedMs: number, width: number): string;
12
+ export declare function TurnStatusLine({ activity }: {
13
+ activity: TurnActivity;
14
+ }): ReactElement;