@roll-agent/core 0.10.1 → 0.12.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.
- package/bin/roll.js +56 -1
- package/dist/cli/chat/ink/app.d.ts +15 -0
- package/dist/cli/chat/ink/app.js +1 -0
- package/dist/cli/chat/ink/commands.d.ts +6 -0
- package/dist/cli/chat/ink/commands.js +1 -0
- package/dist/cli/chat/ink/confirm-select.d.ts +6 -0
- package/dist/cli/chat/ink/confirm-select.js +1 -0
- package/dist/cli/chat/ink/history-from-messages.d.ts +3 -0
- package/dist/cli/chat/ink/history-from-messages.js +1 -0
- package/dist/cli/chat/ink/history-item.d.ts +5 -0
- package/dist/cli/chat/ink/history-item.js +1 -0
- package/dist/cli/chat/ink/live-region.d.ts +5 -0
- package/dist/cli/chat/ink/live-region.js +1 -0
- package/dist/cli/chat/ink/markdown.d.ts +4 -0
- package/dist/cli/chat/ink/markdown.js +1 -0
- package/dist/cli/chat/ink/run-ink-repl.d.ts +13 -0
- package/dist/cli/chat/ink/run-ink-repl.js +1 -0
- package/dist/cli/chat/ink/slash-popup.d.ts +7 -0
- package/dist/cli/chat/ink/slash-popup.js +1 -0
- package/dist/cli/chat/ink/spinner.d.ts +2 -0
- package/dist/cli/chat/ink/spinner.js +1 -0
- package/dist/cli/chat/ink/state.d.ts +95 -0
- package/dist/cli/chat/ink/state.js +1 -0
- package/dist/cli/chat/ink/status-line.d.ts +5 -0
- package/dist/cli/chat/ink/status-line.js +1 -0
- package/dist/cli/chat/ink/text-prompt.d.ts +12 -0
- package/dist/cli/chat/ink/text-prompt.js +1 -0
- package/dist/cli/chat/ink/thinking-text.d.ts +9 -0
- package/dist/cli/chat/ink/thinking-text.js +1 -0
- package/dist/cli/chat/ink/thinking.d.ts +3 -0
- package/dist/cli/chat/ink/thinking.js +1 -0
- package/dist/cli/chat/ink/tool-label.d.ts +4 -0
- package/dist/cli/chat/ink/tool-label.js +1 -0
- package/dist/cli/chat/ink/use-session.d.ts +20 -0
- package/dist/cli/chat/ink/use-session.js +1 -0
- package/dist/cli/chat/title.d.ts +1 -0
- package/dist/cli/chat/title.js +1 -0
- package/dist/cli/commands/browser-stop.d.ts +4 -4
- package/dist/cli/commands/chat.d.ts +27 -0
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/config-guidance.d.ts +12 -0
- package/dist/cli/commands/config-guidance.js +1 -1
- package/dist/cli/utils/chat-renderer.d.ts +21 -0
- package/dist/cli/utils/chat-renderer.js +1 -0
- package/dist/cli/utils/output.d.ts +1 -0
- package/dist/cli/utils/output.js +1 -1
- package/dist/cli/utils/package-manager.d.ts +5 -0
- package/dist/cli/utils/package-manager.js +1 -1
- package/dist/cli/utils/token-format.d.ts +18 -0
- package/dist/cli/utils/token-format.js +1 -0
- package/dist/cli/utils/tool-format.d.ts +1 -0
- package/dist/cli/utils/tool-format.js +1 -0
- package/dist/config/defaults.js +1 -1
- package/dist/config/key-codec.js +1 -1
- package/dist/config/loader.js +1 -1
- package/dist/config/runtime-env.d.ts +16 -16
- package/dist/config/schema.d.ts +246 -22
- package/dist/config/schema.js +1 -1
- package/dist/llm/providers.d.ts +5 -2
- package/dist/llm/providers.js +1 -1
- package/dist/mcp/client-manager.d.ts +2 -0
- package/dist/mcp/client-manager.js +1 -1
- package/dist/types/chat.d.ts +23 -0
- package/package.json +40 -6
package/bin/roll.js
CHANGED
|
@@ -1,2 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
async function sqliteAvailable() {
|
|
8
|
+
try {
|
|
9
|
+
await import("node:sqlite");
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sourceTreeAvailable() {
|
|
17
|
+
return existsSync(resolve(dirname(fileURLToPath(import.meta.url)), "../src/cli/index.ts"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function hasExecFlag(flag) {
|
|
21
|
+
return process.execArgv.includes(flag);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasWarningSuppression() {
|
|
25
|
+
return (
|
|
26
|
+
process.execArgv.includes("--no-warnings") ||
|
|
27
|
+
process.execArgv.includes("--disable-warning=ExperimentalWarning")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveCommandName(argv) {
|
|
32
|
+
return argv.find((arg) => !arg.startsWith("-"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const commandName = resolveCommandName(process.argv.slice(2));
|
|
36
|
+
const shouldEnableSqlite = commandName === "chat";
|
|
37
|
+
const hasSqliteFlag = hasExecFlag("--experimental-sqlite");
|
|
38
|
+
const needsSqliteFlag = shouldEnableSqlite && !hasSqliteFlag && !(await sqliteAvailable());
|
|
39
|
+
const needsTypeStripFlag = sourceTreeAvailable() && !hasExecFlag("--experimental-strip-types");
|
|
40
|
+
const needsExperimentalWarningSuppression =
|
|
41
|
+
shouldEnableSqlite && (needsSqliteFlag || hasSqliteFlag) && !hasWarningSuppression();
|
|
42
|
+
|
|
43
|
+
if (!process.env.ROLL_SQLITE_RESPAWNED && (needsSqliteFlag || needsTypeStripFlag)) {
|
|
44
|
+
const nodeFlags = [
|
|
45
|
+
...(needsExperimentalWarningSuppression ? ["--disable-warning=ExperimentalWarning"] : []),
|
|
46
|
+
...(needsSqliteFlag ? ["--experimental-sqlite"] : []),
|
|
47
|
+
...(needsTypeStripFlag ? ["--experimental-strip-types"] : []),
|
|
48
|
+
];
|
|
49
|
+
const result = spawnSync(
|
|
50
|
+
process.execPath,
|
|
51
|
+
[...nodeFlags, process.argv[1], ...process.argv.slice(2)],
|
|
52
|
+
{ stdio: "inherit", env: { ...process.env, ROLL_SQLITE_RESPAWNED: "1" } },
|
|
53
|
+
);
|
|
54
|
+
process.exit(result.status ?? 0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await import("../dist/cli/index.js");
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { AgentSession } from "@roll-agent/runtime";
|
|
3
|
+
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
4
|
+
import type { HistoryItem } from "./state.ts";
|
|
5
|
+
export interface ChatAppProps {
|
|
6
|
+
readonly session: AgentSession;
|
|
7
|
+
readonly model: string;
|
|
8
|
+
readonly contextWindow: number | undefined;
|
|
9
|
+
readonly initialHistory?: readonly HistoryItem[];
|
|
10
|
+
readonly initialThinkingLevel?: ThinkingLevel;
|
|
11
|
+
readonly onThinkingChange?: (level: ThinkingLevel) => void;
|
|
12
|
+
readonly onUserSubmit: (text: string) => void;
|
|
13
|
+
readonly onExit: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function ChatApp(props: ChatAppProps): ReactElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as i}from"node:crypto";import{createElement as t,useState as n}from"react";import{Box as o,useInput as e}from"ink";import{useSession as m}from"./use-session.js";import{HistoryItemView as s}from"./history-item.js";import{LiveRegion as r}from"./live-region.js";import{StatusLine as a}from"./status-line.js";import{TextPrompt as l}from"./text-prompt.js";import{ConfirmSelect as f}from"./confirm-select.js";import{SlashPopup as h}from"./slash-popup.js";import{filterCommands as p,SLASH_COMMANDS as d}from"./commands.js";import{cycleThinking as c}from"./thinking.js";function u(){return d.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(g){const{session:k,model:v,contextWindow:x,onUserSubmit:j,onExit:C}=g,{state:y,submit:L,compact:T,resolveConfirm:M,setDraft:w,setThinking:S,commitHistory:b}=m(k,{model:v,contextWindow:x,...g.initialHistory?{initialHistory:g.initialHistory}:{},...g.initialThinkingLevel?{initialThinkingLevel:g.initialThinkingLevel}:{},...g.onThinkingChange?{onThinkingChange:g.onThinkingChange}:{}}),[D,H]=n(0),$="idle"===y.phase&&y.draft.startsWith("/"),W=$?p(y.draft):[],A=Math.max(W.length-1,0),E=Math.min(D,A);e((i,t)=>{t.meta&&"."===i?S(c(y.status.thinkingLevel,1)):t.meta&&","===i&&S(c(y.status.thinkingLevel,-1))});const R="confirm"===y.phase&&void 0!==y.pendingConfirm?t(f,{prompt:y.pendingConfirm.prompt,onDecide:M}):t(l,{value:y.draft,disabled:"idle"!==y.phase,slashActive:$,onChange:w,onSubmit:i=>{const t=i.trim();0!==t.length?(j(t),L(t)):w("")},onSlashMove:i=>{H(t=>Math.min(Math.max(Math.min(t,A)+i,0),A))},onSlashComplete:()=>{const i=W[E];i&&(w(`${i.name} `),H(0))},onSlashRun:()=>{const t=y.draft.trim().split(/\s+/,1)[0]??"";(t=>{w("");const n=t.trim().split(/\s+/),o=n[0]??"",e=(n[1]??"").toLowerCase(),m=y.status.thinkingLevel;"/compact"!==o?"/think"!==o?"/effort"!==o?"/help"!==o?"/exit"!==o?b({kind:"notice",id:i(),text:`未知命令 ${o}`}):C():b({kind:"notice",id:i(),text:u()}):"low"===e||"medium"===e||"high"===e?S(e):b({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}):S("off"===e?"off":"on"===e?"off"===m?"medium":m:"off"===m?"medium":"off"):T()})(d.some(i=>i.name===t)?y.draft:W[E]?.name??y.draft)}});return t(o,{flexDirection:"column"},t(o,{flexDirection:"column"},...y.history.map(i=>{const n="user"===i.kind||"assistant"===i.kind,e="tool"===i.kind;return t(o,{key:i.id,marginTop:n?1:0,marginLeft:e?2:0},t(s,{item:i}))})),t(r,{live:y.live}),t(a,{status:y.status}),$?t(h,{matches:W,selected:E}):null,R)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SLASH_COMMANDS=[{name:"/compact",description:"压缩上下文,释放 token"},{name:"/think",description:"开关 thinking/reasoning (on | off)"},{name:"/effort",description:"设置推理努力程度 (low | medium | high)"},{name:"/help",description:"列出可用命令"},{name:"/exit",description:"退出对话"}];export function filterCommands(e){const t=(e.split(/\s+/,1)[0]??"").toLowerCase();return SLASH_COMMANDS.filter(e=>e.name.toLowerCase().startsWith(t))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as e,useState as r}from"react";import{Box as o,Text as n,useInput as t}from"ink";export function ConfirmSelect({prompt:c,onDecide:l}){const[i,s]=r("no");return t((e,r)=>{r.leftArrow||r.rightArrow||r.upArrow||r.downArrow?s(e=>"yes"===e?"no":"yes"):r.return||e.includes("\r")||e.includes("\n")?l("yes"===i):r.escape||"n"===e?l(!1):"y"===e&&l(!0)}),e(o,{flexDirection:"column"},e(n,null,c),e(o,null,e(n,"yes"===i?{color:"green"}:{},("yes"===i?"❯ ":" ")+"Yes"),e(n,"no"===i?{color:"green"}:{},` ${"no"===i?"❯ ":" "}No`)))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SUMMARY_ACK as t,SUMMARY_PREFIX as o}from"@roll-agent/runtime";import{formatToolInput as n}from"../../utils/tool-format.js";const e=new Set(["error-text","error-json","execution-denied"]);function r(t){return"string"==typeof t?t:t.map(t=>"text"===t.type?t.text:"").join("")}function s(t){const o=t.indexOf("__");return o>=0?`${t.slice(0,o)}.${t.slice(o+2)}`:t}export function messagesToHistory(i,c="h"){const l=new Map;for(const t of i)if("tool"===t.role&&"string"!=typeof t.content)for(const o of t.content)if("tool-result"===o.type){const t=o.output.type;l.set(o.toolCallId,!e.has(t))}const a=[];return i.forEach((e,i)=>{const f=`${c}-${String(i)}`;if("user"===e.role){const t=r(e.content);return t.startsWith(o)?void a.push({kind:"compaction",id:f,notice:"🗜 已恢复的上下文摘要"}):void(t.length>0&&a.push({kind:"user",id:f,text:t}))}if("assistant"===e.role){const o=r(e.content);if(o.startsWith(t))return;o.length>0&&a.push({kind:"assistant",id:f,text:o}),"string"!=typeof e.content&&e.content.forEach((t,o)=>{"tool-call"===t.type&&a.push({kind:"tool",id:`${f}-${String(o)}`,name:s(t.toolName),args:n(t.input),ok:l.get(t.toolCallId)??!0})})}}),a}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as e}from"react";import{Box as t,Text as r}from"ink";import{parseThinking as o}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as i}from"./tool-label.js";export function HistoryItemView({item:l}){switch(l.kind){case"user":return e(t,null,e(r,{color:"cyan",bold:!0},"▌ "),e(r,{color:"cyan"},l.text));case"assistant":return e(t,{flexDirection:"column"},...o(l.text).map((t,o)=>t.thinking?e(r,{key:String(o),dimColor:!0},t.text):e(n,{key:String(o),text:t.text})));case"tool":return e(r,null,e(r,l.ok?{color:"green"}:{color:"red"},l.ok?"✓ ":"✗ "),e(i,{name:l.name}));case"compaction":return e(r,{dimColor:!0},l.notice);case"notice":return e(r,{color:"yellow"},`⚠ ${l.text}`);case"error":return e(r,{color:"red"},`✗ ${l.message}`);default:return e(r,null,"")}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as l}from"react";import{Box as n,Text as o}from"ink";import{Spinner as t}from"./spinner.js";import{ThinkingText as e}from"./thinking-text.js";import{ToolLabel as i}from"./tool-label.js";export function LiveRegion({live:r}){return l(n,{flexDirection:"column"},r.streamingText.length>0?l(e,{text:r.streamingText}):null,r.thinking?l(n,{marginTop:1},l(t,null),l(o,{dimColor:!0}," 思考中…")):null,...r.activeTools.map(e=>l(n,{key:e.toolCallId},l(t,null),l(o,null," "),l(i,{name:e.name}),e.args.length>0?l(o,{dimColor:!0},` ${e.args}`):null)),r.compacting?l(n,null,l(t,null),l(o,{dimColor:!0}," 压缩上下文中…")):null)}
|
|
@@ -0,0 +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={"&":"&","<":"<",">":">",""":'"',"'":"'","'":"'"};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 i=`${n}-${String(o)}`;switch(t.type){case"strong":return e(r,{key:i,bold:!0},...a(t.tokens,i));case"em":return e(r,{key:i,italic:!0},...a(t.tokens,i));case"codespan":return e(r,{key:i,color:"yellow"},c(t.text));case"del":return e(r,{key:i,strikethrough:!0},...a(t.tokens,i));case"link":return e(r,{key:i},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:i},...a(t.tokens,i)):c(t.text);default:return c(t.raw)}})}function i(e){let t=0;for(const r of e)t+=(r.codePointAt(0)??0)<128?1:2;return t}function u(n,o,c,i){return e(t,{key:c},...n.map((n,u)=>e(t,{key:`${c}-${String(u)}`,width:(o[u]??0)+2},e(r,i?{bold:!0}:{},...a(n.tokens,`${c}-${String(u)}`)))))}function l(n,o){const c=n.header.map((e,t)=>{let r=i(e.text);for(const e of n.rows)r=Math.max(r,i(e[t]?.text??""));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+1)))));return e(t,{key:o,flexDirection:"column"},u(n.header,c,`${o}-h`,!0),a,...n.rows.map((e,t)=>u(e,c,`${o}-r${String(t)}`,!1)))}function s(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"},...k(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 i=`${o}-${String(a)}`,u=n.ordered?`${String(Number(n.start||1)+a)}. `:"• ";return e(t,{key:i},e(r,{color:"cyan"},u),e(t,{flexDirection:"column"},...k(c.tokens,i)))}));case"hr":return e(r,{key:o,dimColor:!0},"────────");case"table":return l(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 k(e,t){return e.map((e,r)=>s(e,`${t}-${String(r)}`)).filter(e=>null!==e)}export function Markdown({text:o}){let c;try{c=n.lexer(o)}catch{return e(r,null,o)}return e(t,{flexDirection:"column"},...k(c,"md"))}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentSession } from "@roll-agent/runtime";
|
|
2
|
+
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
3
|
+
export interface InkReplStore {
|
|
4
|
+
updateTitle(threadId: string, title: string): void;
|
|
5
|
+
countMessages(threadId: string): number;
|
|
6
|
+
deleteThread(threadId: string): void;
|
|
7
|
+
}
|
|
8
|
+
export interface RunInkReplOptions {
|
|
9
|
+
readonly model: string;
|
|
10
|
+
readonly initialThinkingLevel?: ThinkingLevel;
|
|
11
|
+
readonly onThinkingChange?: (level: ThinkingLevel) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function runInkRepl(session: AgentSession, store: InkReplStore, isNewSession: boolean, options: RunInkReplOptions): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as i}from"react";import{render as t}from"ink";import{ChatApp as e}from"./app.js";import{messagesToHistory as n}from"./history-from-messages.js";import{titleFromMessage as o}from"../title.js";import{log as a}from"../../utils/output.js";export async function runInkRepl(r,s,l,m){let g=!1,d=!l;a.info("多轮对话已就绪(/exit 退出 · / 命令 · Shift+Enter/Ctrl+J 换行 · Alt+./Alt+, 调推理)");const p=t(i(e,{session:r,model:m.model,contextWindow:r.getContextWindow(),onUserSubmit:i=>{g=!0,d||(s.updateTitle(r.id,o(i)),d=!0)},onExit:()=>{p.unmount()},initialHistory:n(r.getMessages()),...m.initialThinkingLevel?{initialThinkingLevel:m.initialThinkingLevel}:{},...m.onThinkingChange?{onThinkingChange:m.onThinkingChange}:{}}),{kittyKeyboard:{mode:"auto",flags:["disambiguateEscapeCodes","reportAlternateKeys"]}});await p.waitUntilExit(),r.abort(),l&&!g&&0===s.countMessages(r.id)&&s.deleteThread(r.id)}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { SlashCommand } from "./commands.ts";
|
|
3
|
+
export interface SlashPopupProps {
|
|
4
|
+
readonly matches: readonly SlashCommand[];
|
|
5
|
+
readonly selected: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function SlashPopup({ matches, selected }: SlashPopupProps): ReactElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as r}from"react";import{Box as o,Text as e}from"ink";const n=8;export function SlashPopup({matches:n,selected:t}){if(0===n.length)return r(o,{borderStyle:"round",borderColor:"gray",paddingX:1},r(e,{dimColor:!0},"无匹配命令"));const d=n.slice(0,8);return r(o,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1},...d.map((n,d)=>{const l=d===t;return r(o,{key:n.name},r(e,l?{color:"cyan",bold:!0}:{dimColor:!0},`${l?"❯ ":" "}${n.name}`),r(e,{dimColor:!0},` ${n.description}`))}))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as r,useEffect as t,useState as n}from"react";import{Text as e}from"ink";const o=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],c=80;export function Spinner(){const[a,l]=n(0);return t(()=>{const r=setInterval(()=>{l(r=>(r+1)%o.length)},c);return()=>{clearInterval(r)}},[]),r(e,{color:"cyan"},o[a]??o[0])}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { SessionEvent, SessionTokenUsage } from "@roll-agent/runtime";
|
|
2
|
+
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
3
|
+
export interface ToolRowState {
|
|
4
|
+
readonly toolCallId: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly args: string;
|
|
7
|
+
}
|
|
8
|
+
export type HistoryItem = {
|
|
9
|
+
readonly kind: "user";
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly text: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "assistant";
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly text: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "tool";
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly args: string;
|
|
21
|
+
readonly ok: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "compaction";
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly notice: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "notice";
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly text: string;
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: "error";
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
};
|
|
35
|
+
export interface LiveState {
|
|
36
|
+
readonly streamingText: string;
|
|
37
|
+
readonly thinking: boolean;
|
|
38
|
+
readonly activeTools: readonly ToolRowState[];
|
|
39
|
+
readonly compacting: boolean;
|
|
40
|
+
readonly producedOutput: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface StatusState {
|
|
43
|
+
readonly model: string;
|
|
44
|
+
readonly contextWindow: number | undefined;
|
|
45
|
+
readonly turnUsage: SessionTokenUsage | undefined;
|
|
46
|
+
readonly sessionUsage: SessionTokenUsage | undefined;
|
|
47
|
+
readonly contextInputTokens: number | undefined;
|
|
48
|
+
readonly thinkingLevel: ThinkingLevel;
|
|
49
|
+
}
|
|
50
|
+
export type ChatPhase = "idle" | "busy" | "confirm";
|
|
51
|
+
export interface PendingConfirm {
|
|
52
|
+
readonly approvalId: string;
|
|
53
|
+
readonly prompt: string;
|
|
54
|
+
}
|
|
55
|
+
export interface ChatUiState {
|
|
56
|
+
readonly history: readonly HistoryItem[];
|
|
57
|
+
readonly draft: string;
|
|
58
|
+
readonly live: LiveState;
|
|
59
|
+
readonly status: StatusState;
|
|
60
|
+
readonly phase: ChatPhase;
|
|
61
|
+
readonly pendingConfirm: PendingConfirm | undefined;
|
|
62
|
+
}
|
|
63
|
+
export type ChatUiAction = {
|
|
64
|
+
readonly type: "submit-user";
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly text: string;
|
|
67
|
+
} | {
|
|
68
|
+
readonly type: "set-draft";
|
|
69
|
+
readonly value: string;
|
|
70
|
+
} | {
|
|
71
|
+
readonly type: "set-thinking";
|
|
72
|
+
readonly level: ThinkingLevel;
|
|
73
|
+
} | {
|
|
74
|
+
readonly type: "commit-history";
|
|
75
|
+
readonly item: HistoryItem;
|
|
76
|
+
} | {
|
|
77
|
+
readonly type: "start-compaction";
|
|
78
|
+
} | {
|
|
79
|
+
readonly type: "session-event";
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly event: SessionEvent;
|
|
82
|
+
} | {
|
|
83
|
+
readonly type: "confirm-resolved";
|
|
84
|
+
} | {
|
|
85
|
+
readonly type: "turn-end";
|
|
86
|
+
};
|
|
87
|
+
export interface InitialStateOptions {
|
|
88
|
+
readonly history?: readonly HistoryItem[];
|
|
89
|
+
readonly thinkingLevel?: ThinkingLevel;
|
|
90
|
+
}
|
|
91
|
+
export declare function createInitialState(model: string, contextWindow: number | undefined, options?: InitialStateOptions): ChatUiState;
|
|
92
|
+
export declare function buildConfirmPrompt(event: Extract<SessionEvent, {
|
|
93
|
+
type: "confirmation-required";
|
|
94
|
+
}>): string;
|
|
95
|
+
export declare function chatReducer(state: ChatUiState, action: ChatUiAction): ChatUiState;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{formatToolInput as t}from"../../utils/tool-format.js";const e={streamingText:"",thinking:!1,activeTools:[],compacting:!1,producedOutput:!1};export function createInitialState(t,i,n){return{history:n?.history??[],draft:"",live:e,status:{model:t,contextWindow:i,turnUsage:void 0,sessionUsage:void 0,contextInputTokens:void 0,thinkingLevel:n?.thinkingLevel??"medium"},phase:"idle",pendingConfirm:void 0}}export function buildConfirmPrompt(t){const e=t.reason?`(${t.reason})`:"";return`执行 ${t.agentName}.${t.toolName}${e}?`}function i(t){const e="auto"===t.reason?"自动压缩":"手动压缩",i=t.truncatedTools?`,精简 ${String(t.truncatedTools)} 个工具结果`:"";return 0!==t.removed||t.truncatedTools?`🗜 ${e}(${t.strategy}):移除 ${String(t.removed)} 条 → 保留 ${String(t.kept)} 条${i}`:`🗜 ${e}:无需压缩`}function n(t,e,i){const n=t.live.activeTools.find(t=>t.toolCallId===i.toolCallId),o=n?.name??`${i.agentName}.${i.toolName}`,s=n?.args??"";return{...t,history:[...t.history,{kind:"tool",id:e,name:o,args:s,ok:!i.isError}],live:{...t.live,activeTools:t.live.activeTools.filter(t=>t.toolCallId!==i.toolCallId)}}}function o(o,s,r){switch(r.type){case"message-start":return{...o,live:{...o.live,thinking:!0}};case"text-delta":return{...o,live:{...o.live,thinking:!1,producedOutput:!0,streamingText:o.live.streamingText+r.delta}};case"tool-call":{const e=o.live.streamingText.length>0?[{kind:"assistant",id:s,text:o.live.streamingText}]:[];return{...o,history:[...o.history,...e],live:{...o.live,thinking:!1,producedOutput:!0,streamingText:"",activeTools:[...o.live.activeTools,{toolCallId:r.toolCallId,name:`${r.agentName}.${r.toolName}`,args:t(r.input)}]}}}case"tool-result":return n(o,s,r);case"confirmation-required":return{...o,phase:"confirm",pendingConfirm:{approvalId:r.approvalId,prompt:buildConfirmPrompt(r)}};case"compaction-start":return{...o,live:{...o.live,compacting:!0}};case"context-compacted":return{...o,live:{...o.live,compacting:!1},history:[...o.history,{kind:"compaction",id:s,notice:i(r)}]};case"message-finish":{const t=[];return o.live.streamingText.length>0?t.push({kind:"assistant",id:s,text:o.live.streamingText}):!o.live.producedOutput&&(r.totalUsage?.outputTokens??0)>0&&t.push({kind:"notice",id:s,text:"模型本轮只返回了 thinking/reasoning,没有生成可见回复"}),r.stoppedAtStepLimit&&t.push({kind:"notice",id:`${s}-step-limit`,text:"已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps"}),{...o,history:[...o.history,...t],live:{...e},status:{...o.status,turnUsage:r.totalUsage,sessionUsage:r.sessionUsage,contextInputTokens:r.contextInputTokens}}}case"error":return{...o,history:[...o.history,{kind:"error",id:s,message:r.message}],live:{...e}};default:return o}}export function chatReducer(t,i){switch(i.type){case"submit-user":return{...t,history:[...t.history,{kind:"user",id:i.id,text:i.text}],draft:"",live:{...e},phase:"busy",pendingConfirm:void 0};case"set-draft":return{...t,draft:i.value};case"set-thinking":return{...t,status:{...t.status,thinkingLevel:i.level}};case"commit-history":return{...t,history:[...t.history,i.item],draft:""};case"start-compaction":return{...t,live:{...e},phase:"busy",pendingConfirm:void 0};case"session-event":return o(t,i.id,i.event);case"confirm-resolved":return{...t,phase:"busy",pendingConfirm:void 0};case"turn-end":return{...t,phase:"idle",live:{...e}};default:return t}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as o}from"react";import{Box as e,Text as n,useStdout as t}from"ink";import{computeUsageParts as s,formatTokens as i}from"../../utils/token-format.js";import{thinkingLabel as r}from"./thinking.js";export function StatusLine({status:k}){const{stdout:d}=t(),u=d.columns??80,c=s(k.turnUsage,k.sessionUsage,k.contextWindow,k.contextInputTokens),m=o(n,"off"===k.thinkingLevel?{key:"think",color:"yellow"}:{key:"think",dimColor:!0},` · ${r(k.thinkingLevel)}`),p=[o(n,{key:"model",color:"magenta"},k.model),m];if(void 0!==c.percentLeft&&void 0!==c.usedTokens&&void 0!==c.contextWindow&&p.push(o(n,{key:"ctx",dimColor:!0},` · ${String(c.percentLeft)}% left (${i(c.usedTokens)}/${i(c.contextWindow)})`)),void 0!==c.inputTokens){const e=void 0!==c.cachedInputTokens?` (+${i(c.cachedInputTokens)} cached)`:"";p.push(o(n,{key:"in",dimColor:!0},` · in ${i(c.inputTokens)}${e}`))}if(void 0!==c.outputTokens){const e=void 0!==c.reasoningTokens?` (+${i(c.reasoningTokens)} reasoning)`:"";p.push(o(n,{key:"out",dimColor:!0},` · out ${i(c.outputTokens)}${e}`))}return void 0!==c.sessionTokens&&p.push(o(n,{key:"session",dimColor:!0},` · session ${i(c.sessionTokens)}`)),o(e,{width:u},o(n,{wrap:"truncate-end"},...p))}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
export interface TextPromptProps {
|
|
3
|
+
readonly value: string;
|
|
4
|
+
readonly disabled: boolean;
|
|
5
|
+
readonly slashActive: boolean;
|
|
6
|
+
readonly onChange: (value: string) => void;
|
|
7
|
+
readonly onSubmit: (value: string) => void;
|
|
8
|
+
readonly onSlashMove: (direction: 1 | -1) => void;
|
|
9
|
+
readonly onSlashComplete: () => void;
|
|
10
|
+
readonly onSlashRun: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function TextPrompt(props: TextPromptProps): ReactElement;
|
|
@@ -0,0 +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";function u(r){return/^\[\?\d+u$/.test(r)||/^\x9b\?\d+u$/.test(r)}export function TextPrompt(l){const{value:c,disabled:s,slashActive:d,onChange:a,onSubmit:f}=l,{stdout:m}=i(),p=m.columns??80,v=t(c);v.current=c;const h=r=>{v.current=r,a(r)};o((r,t)=>{if(t.meta&&("."===r||","===r))return;if(u(r))return;if(t.backspace||t.delete)return void h(v.current.slice(0,-1));if("\n"===r||t.ctrl&&"j"===r||t.return&&(t.shift||t.meta))return void h(`${v.current}\n`);if(t.return||r.includes("\r")){if(d)return void l.onSlashRun();const t=r.split("\r",1)[0]??"";return void f(v.current+t)}if(d){if(t.upArrow)return void l.onSlashMove(-1);if(t.downArrow)return void l.onSlashMove(1);if(t.tab)return void l.onSlashComplete();if(t.escape)return void h("")}t.ctrl||t.escape||t.tab||t.upArrow||t.downArrow||t.leftArrow||t.rightArrow||r.length>0&&h(v.current+r)},{isActive:!s});const b=c.split("\n"),w=r(e,{flexDirection:"column"},...b.map((t,o)=>{const i=o===b.length-1,u=0===o?r(n,{color:"green"},"› "):r(n,null," ");return r(e,{key:String(o)},u,r(n,s?{dimColor:!0}:{},t),i&&!s?r(n,null,"▏"):null)})),g=d?"↑↓ 选择 · Tab 补全 · Enter 执行 · Esc 取消":"Enter 发送 · Shift+Enter/Ctrl+J 换行 · / 命令";return r(e,{flexDirection:"column",width:p},r(e,{borderStyle:"round",borderColor:s?"gray":"cyan",paddingX:1},w),r(n,{dimColor:!0},g))}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
export interface TextSegment {
|
|
3
|
+
readonly text: string;
|
|
4
|
+
readonly thinking: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseThinking(text: string): TextSegment[];
|
|
7
|
+
export declare function ThinkingText({ text }: {
|
|
8
|
+
text: string;
|
|
9
|
+
}): ReactElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as t}from"react";import{Text as n}from"ink";export function parseThinking(t){const n=[],e=/<think>([\s\S]*?)(?:<\/think>|$)/gi;let i=0,r=e.exec(t);for(;null!==r;)r.index>i&&n.push({text:t.slice(i,r.index),thinking:!1}),n.push({text:r[1]??"",thinking:!0}),i=e.lastIndex,r=e.exec(t);return i<t.length&&n.push({text:t.slice(i),thinking:!1}),n.filter(t=>t.text.length>0)}export function ThinkingText({text:e}){const i=parseThinking(e);return 0===i.length?t(n,null,""):t(n,null,...i.map((e,i)=>t(n,{key:String(i),...e.thinking?{dimColor:!0}:{}},e.text)))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=["off","low","medium","high"];export function cycleThinking(t,i){const e=n.indexOf(t),o=Math.min(Math.max(e+i,0),n.length-1);return n[o]??t}export function thinkingLabel(n){return`🧠 ${n}`}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createElement as o}from"react";import{Text as e}from"ink";export function ToolLabel({name:r}){const n=r.indexOf(".");return n<0?o(e,{color:"cyan"},r):o(e,null,o(e,{dimColor:!0},r.slice(0,n+1)),o(e,{color:"cyan"},r.slice(n+1)))}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentSession } from "@roll-agent/runtime";
|
|
2
|
+
import { type ChatUiState, type HistoryItem } from "./state.ts";
|
|
3
|
+
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
4
|
+
export interface UseSessionOptions {
|
|
5
|
+
readonly model: string;
|
|
6
|
+
readonly contextWindow: number | undefined;
|
|
7
|
+
readonly initialHistory?: readonly HistoryItem[];
|
|
8
|
+
readonly initialThinkingLevel?: ThinkingLevel;
|
|
9
|
+
readonly onThinkingChange?: (level: ThinkingLevel) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface UseSessionResult {
|
|
12
|
+
readonly state: ChatUiState;
|
|
13
|
+
readonly submit: (text: string) => void;
|
|
14
|
+
readonly compact: () => void;
|
|
15
|
+
readonly resolveConfirm: (approved: boolean) => void;
|
|
16
|
+
readonly setDraft: (value: string) => void;
|
|
17
|
+
readonly setThinking: (level: ThinkingLevel) => void;
|
|
18
|
+
readonly commitHistory: (item: HistoryItem) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function useSession(session: AgentSession, options: UseSessionOptions): UseSessionResult;
|
|
@@ -0,0 +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(l,p){const[u,m]=n(r,o(p.model,p.contextWindow,{...p.initialHistory?{history:p.initialHistory}:{},...p.initialThinkingLevel?{thinkingLevel:p.initialThinkingLevel}:{}})),y=p.onThinkingChange,d=i(null),v=i(!1),f=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){const t=new Promise(e=>{d.current=e});m({type:"session-event",id:e(),event:o});const n=await t;d.current=null,n?l.approve(o.approvalId):l.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"}),v.current=!1}},[l]);return{state:u,submit:t(t=>{v.current||(v.current=!0,m({type:"submit-user",id:e(),text:t}),f(l.send(t)).catch(()=>{}))},[f,l]),compact:t(()=>{v.current||(v.current=!0,m({type:"start-compaction"}),f(l.compact("manual")).catch(()=>{}))},[f,l]),resolveConfirm:t(e=>{d.current?.(e)},[]),setDraft:t(e=>{m({type:"set-draft",value:e})},[]),setThinking:t(e=>{m({type:"set-thinking",level:e}),y?.(e)},[y]),commitHistory:t(e=>{m({type:"commit-history",item:e})},[])}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function titleFromMessage(message: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function titleFromMessage(e){const t=e.trim().replace(/\s+/g," ");return t.length>40?`${t.slice(0,39)}…`:t}
|
|
@@ -15,13 +15,13 @@ declare const BrowserStopToolResultSchema: z.ZodObject<{
|
|
|
15
15
|
}, "strip", z.ZodTypeAny, {
|
|
16
16
|
status: "stopped" | "failed" | "not_running" | "not_found";
|
|
17
17
|
browserInstance: string;
|
|
18
|
-
message?: string | undefined;
|
|
19
18
|
mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
|
|
19
|
+
message?: string | undefined;
|
|
20
20
|
}, {
|
|
21
21
|
status: "stopped" | "failed" | "not_running" | "not_found";
|
|
22
22
|
browserInstance: string;
|
|
23
|
-
message?: string | undefined;
|
|
24
23
|
mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
|
|
24
|
+
message?: string | undefined;
|
|
25
25
|
}>, "many">;
|
|
26
26
|
}, "strip", z.ZodTypeAny, {
|
|
27
27
|
stopped: number;
|
|
@@ -29,8 +29,8 @@ declare const BrowserStopToolResultSchema: z.ZodObject<{
|
|
|
29
29
|
results: {
|
|
30
30
|
status: "stopped" | "failed" | "not_running" | "not_found";
|
|
31
31
|
browserInstance: string;
|
|
32
|
-
message?: string | undefined;
|
|
33
32
|
mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
|
|
33
|
+
message?: string | undefined;
|
|
34
34
|
}[];
|
|
35
35
|
}, {
|
|
36
36
|
stopped: number;
|
|
@@ -38,8 +38,8 @@ declare const BrowserStopToolResultSchema: z.ZodObject<{
|
|
|
38
38
|
results: {
|
|
39
39
|
status: "stopped" | "failed" | "not_running" | "not_found";
|
|
40
40
|
browserInstance: string;
|
|
41
|
-
message?: string | undefined;
|
|
42
41
|
mode?: "managed-cdp" | "remote-cdp" | "existing-session" | undefined;
|
|
42
|
+
message?: string | undefined;
|
|
43
43
|
}[];
|
|
44
44
|
}>;
|
|
45
45
|
type BrowserStopToolResult = z.infer<typeof BrowserStopToolResultSchema>;
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import type { AgentSession } from "@roll-agent/runtime";
|
|
2
|
+
import { type ChatConfirm } from "../utils/chat-renderer.ts";
|
|
3
|
+
import type { ChatCommandResult } from "../../types/chat.ts";
|
|
4
|
+
type RuntimeModule = typeof import("@roll-agent/runtime");
|
|
5
|
+
type ThreadStoreInstance = InstanceType<RuntimeModule["ThreadStore"]>;
|
|
6
|
+
interface ReplIo {
|
|
7
|
+
readonly input: NodeJS.ReadableStream;
|
|
8
|
+
readonly output: NodeJS.WritableStream;
|
|
9
|
+
readonly confirm?: ChatConfirm;
|
|
10
|
+
}
|
|
11
|
+
export declare function runJsonTurn(session: AgentSession, message: string): Promise<ChatCommandResult>;
|
|
12
|
+
export declare function runRepl(session: AgentSession, store: ThreadStoreInstance, isNewSession: boolean, io?: ReplIo): Promise<void>;
|
|
1
13
|
declare const _default: import("citty").CommandDef<{
|
|
2
14
|
message: {
|
|
3
15
|
type: "positional";
|
|
@@ -8,10 +20,25 @@ declare const _default: import("citty").CommandDef<{
|
|
|
8
20
|
type: "string";
|
|
9
21
|
description: string;
|
|
10
22
|
};
|
|
23
|
+
last: {
|
|
24
|
+
type: "boolean";
|
|
25
|
+
description: string;
|
|
26
|
+
default: false;
|
|
27
|
+
};
|
|
28
|
+
list: {
|
|
29
|
+
type: "boolean";
|
|
30
|
+
description: string;
|
|
31
|
+
default: false;
|
|
32
|
+
};
|
|
11
33
|
json: {
|
|
12
34
|
type: "boolean";
|
|
13
35
|
description: string;
|
|
14
36
|
default: false;
|
|
15
37
|
};
|
|
38
|
+
server: {
|
|
39
|
+
type: "boolean";
|
|
40
|
+
description: string;
|
|
41
|
+
default: false;
|
|
42
|
+
};
|
|
16
43
|
}>;
|
|
17
44
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineCommand as
|
|
1
|
+
import{defineCommand as e}from"citty";import{loadConfig as t}from"../../config/loader.js";import{resolveLLMCall as o,thinkingProviderOptions as s}from"../../llm/providers.js";import{createInterface as n}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";const p=import.meta.url.endsWith(".ts")?"ts":"js";function m(e,t){return new e.ConfigurableToolPolicy({defaultMode:t.runtime.approval.default,overrides:t.runtime.approval.overrides})}function g(e){console.log(JSON.stringify(e,null,2))}function f(e){l.warn(`Agent "${e.agentName}" 启动失败:${e.message}`)}async function h(e,t,o){l.debug(`chat.repl input waiting · ${o}`),e.resume();try{const s=await e.question(t);return l.debug(`chat.repl input received · ${o} · chars=${String(s.length)}`),s}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 v(){return import("@roll-agent/runtime")}async function w(e){const t=e.runtime.provider??e.llm.defaultProvider,s=e.runtime.model??e.llm.defaultModel,n=e.llm.providers[t];if(!n)return l.error(`LLM provider "${t}" 未配置。请检查 roll.config.yaml`),void(process.exitCode=1);const i=await v(),{ConversationEngine:r,ThreadStore:c,RuntimeServer:d,createStdioConnection:u}=i,{model:p,providerOptions:g}=o(t,s,n.apiKey,"chat",n.baseUrl,e.runtime.thinkingLevel),h=new c(e.runtime.threadsDir),w=new r({config:e,model:p,store:h,policy:m(i,e),maxSteps:e.runtime.maxSteps,...g?{providerOptions:g}:{},debugEvents:a(),onAgentBootstrapIssue:f}),y=u(process.stdin,process.stdout),b=new d(w,y);y.onClose(()=>{b.abortAll(),w.dispose().catch(()=>{}).finally(()=>{h.close(),process.exit(0)})}),l.info("roll runtime-server 已启动(stdio JSON-RPC,等待客户端连接)")}async function y(e,t){const{ThreadStore:o}=await v(),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 r({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"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 r=n({input:s.input,output:s.output});r.on("SIGINT",()=>r.close());const a=s.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());l.info("进入多轮对话(输入 exit / quit 或 Ctrl-C 退出,/compact 手动压缩上下文)");let m=!o,g=!1;try{for(;;){const o=await h(r,i.green("› "),"prompt");if(void 0===o)break;const s=o.trim();if(0!==s.length){if("exit"===s||"quit"===s)break;if("/compact"!==s){m||(t.updateTitle(e.id,u(s)),m=!0),g=!0,l.debug(`chat.repl send start · chars=${String(s.length)}`);for await(const t of e.send(s))await p.handle(t,e);l.debug("chat.repl send completed")}else{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")}}}}finally{r.close(),o&&!g&&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:n}=t();if(e.server)return void await w(n);if(e.list)return void await y(n,e.json);const i=n.runtime.provider??n.llm.defaultProvider,r=n.runtime.model??n.llm.defaultModel,h=n.llm.providers[i];if(!h)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 b=await v(),{ConversationEngine:x,ThreadStore:S}=b,{model:T,providerOptions:k}=o(i,r,h.apiKey,"chat",h.baseUrl,n.runtime.thinkingLevel),C=new S(n.runtime.threadsDir),I=new x({config:n,model:T,store:C,policy:m(b,n),maxSteps:n.runtime.maxSteps,...k?{providerOptions:k}:{},debugEvents:a(),onAgentBootstrapIssue:f});try{let t;if(e.session)t=await I.resumeSession(e.session);else if(e.last){const e=C.listThreads()[0];if(!e)return l.error('暂无可继续的会话,先用 `roll chat "<message>"` 开始一个'),void(process.exitCode=1);t=await I.resumeSession(e.id)}else t=await I.createSession(e.message?{title:u(e.message)}:{});if(e.json&&e.message){const o=await runJsonTurn(t,e.message);return g(o),void("completed"!==o.status&&(process.exitCode=1))}if(l.info(`会话 ${t.id}`),n.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 s of t.send(e.message))await o.handle(s,t)}else{const o=!e.session&&!e.last,a=Boolean(process.stdout.isTTY&&process.stdin.isTTY&&"function"==typeof process.stdin.setRawMode);let c=!1;if(a)try{const e=new URL(`../chat/ink/run-ink-repl.${p}`,import.meta.url).href,{runInkRepl:a}=await import(e);await a(t,C,o,{model:r,initialThinkingLevel:n.runtime.thinkingLevel,onThinkingChange:e=>t.setProviderOptions(s(i,r,e))}),c=!0}catch(e){l.warn(`Ink TUI 不可用,回退到基础多轮模式:${e instanceof Error?e.message:String(e)}`)}c||await runRepl(t,C,o)}}catch(e){l.error(e instanceof Error?e.message:String(e)),process.exitCode=1}finally{await I.dispose(),C.close()}}});
|
|
@@ -88,6 +88,18 @@ export declare const CONFIG_GUIDANCE_ENTRIES: readonly [{
|
|
|
88
88
|
readonly purpose: "`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。";
|
|
89
89
|
readonly defaultBehavior: "未配置时使用内置路由默认行为。";
|
|
90
90
|
readonly example: "ask:\n confirm-threshold: 0.5";
|
|
91
|
+
}, {
|
|
92
|
+
readonly path: "runtime.approval.default";
|
|
93
|
+
readonly title: "Chat 工具确认默认策略";
|
|
94
|
+
readonly purpose: "`roll chat` 调用工具时的默认确认策略。`guarded` 使用内置读写启发式,`auto` 默认放行非破坏性工具,`deny` 默认拒绝。";
|
|
95
|
+
readonly defaultBehavior: "默认值为 `auto`。此配置不影响 `roll ask`。" | "默认值为 `guarded`。此配置不影响 `roll ask`。" | "默认值为 `deny`。此配置不影响 `roll ask`。";
|
|
96
|
+
readonly example: "runtime:\n approval:\n default: auto" | "runtime:\n approval:\n default: guarded" | "runtime:\n approval:\n default: deny";
|
|
97
|
+
}, {
|
|
98
|
+
readonly path: "runtime.approval.overrides";
|
|
99
|
+
readonly title: "Chat 工具确认精确覆盖";
|
|
100
|
+
readonly purpose: "`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。";
|
|
101
|
+
readonly defaultBehavior: "未配置时回退到 `runtime.approval.default`。";
|
|
102
|
+
readonly example: "runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto";
|
|
91
103
|
}, {
|
|
92
104
|
readonly path: "browser.default-instance";
|
|
93
105
|
readonly title: "默认浏览器实例";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as t}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=t(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>l(e.path,r))}function l(e,l){const r=t(e.split(".")).join(".");if(r===l)return!0;const a=r.split("."),n=l.split(".");return a.length===n.length&&a.every((e,t)=>e.startsWith("<")||e===n[t])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
|
|
1
|
+
import{DEFAULT_CONFIG as e}from"../../config/defaults.js";import{normalizeUserPath as t}from"../../config/key-codec.js";export const INSTALL_SCENARIOS=["default-network","china-dev","private-registry","advanced"];export const CONFIG_GUIDANCE_ENTRIES=[{path:"llm.default-provider",title:"默认 LLM Provider",purpose:"`roll ask` / `roll chat` 默认使用的模型提供商。",defaultBehavior:`默认值为 \`${e.llm.defaultProvider}\`。`,example:`llm:\n default-provider: ${e.llm.defaultProvider}`,setupCommand:"roll config setup llm"},{path:"llm.default-model",title:"默认 LLM Model",purpose:"`roll ask` / `roll chat` 默认使用的模型名称。",defaultBehavior:`默认值为 \`${e.llm.defaultModel}\`。`,example:`llm:\n default-model: ${e.llm.defaultModel}`,setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.api-key",title:"Provider API Key",purpose:"指定 LLM provider 的 API key。可以写真实 key,也可以写 `${ENV_VAR}` 占位符。",example:"llm:\n providers:\n anthropic:\n api-key: ${ANTHROPIC_API_KEY}",setupCommand:"roll config setup llm"},{path:"llm.providers.<provider>.base-url",title:"Provider Base URL",purpose:"指定 LLM provider 的自定义 API base URL,通常只在代理、兼容网关或私有部署时需要。",defaultBehavior:"不配置时使用 provider SDK 默认地址。",example:"llm:\n providers:\n openai:\n base-url: https://api.openai.com/v1",setupCommand:"roll config setup llm"},{path:"install.registry",title:"npm Registry",purpose:"`roll agent install` / `roll update` 查询和安装 npm 包时使用的 registry。",defaultBehavior:"不配置时走 npm 自身默认源;Roll 不做隐式镜像 fallback。",example:"install:\n registry: https://registry.npmmirror.com",setupCommand:"roll config setup install"},{path:"install.fetch-retries",title:"npm Fetch Retries",purpose:"透传给 npm 的 `--fetch-retries`,同时影响 Roll 层整体网络重试次数。",defaultBehavior:`默认值为 \`${e.install.fetchRetries}\`。`,example:`install:\n fetch-retries: ${e.install.fetchRetries}`,setupCommand:"roll config setup install"},{path:"install.prefer-offline",title:"Prefer Offline",purpose:"安装时是否附加 `--prefer-offline`。",defaultBehavior:`默认值为 \`${e.install.preferOffline}\`,避免更新时复用过期 npm 元数据。`,example:`install:\n prefer-offline: ${e.install.preferOffline}`,setupCommand:"roll config setup install"},{path:"install.network-timeout-ms",title:"Install Network Timeout",purpose:"单次 npm install 命令的超时时间,单位毫秒。",defaultBehavior:`默认值为 \`${e.install.networkTimeoutMs}\`,即 120 秒。`,example:`install:\n network-timeout-ms: ${e.install.networkTimeoutMs}`,setupCommand:"roll config setup install"},{path:"agents.data-dir",title:"Agent 数据目录",purpose:"Roll 持久化已注册 Agent、PID、日志和 runtime sidecar 的目录。",defaultBehavior:`默认值为 \`${e.agents.dataDir}\`。`,example:`agents:\n data-dir: ${e.agents.dataDir}`},{path:"agents.env.<agent-name>",title:"Agent 环境变量",purpose:"按 Agent 名称声明注入到子 Agent 进程的环境变量。",defaultBehavior:"未配置时只会继承当前 shell 环境变量;core-managed Agent 需要重启才会看到新值。",example:"agents:\n env:\n browser-use-agent:\n REPLY_AUTHORITY_URL: https://example.com",setupCommand:"roll config setup agent <agent-name>"},{path:"ask.confirm-threshold",title:"Ask 确认阈值",purpose:"`roll ask` 路由置信度低于该阈值时,倾向要求用户确认。",defaultBehavior:"未配置时使用内置路由默认行为。",example:"ask:\n confirm-threshold: 0.5"},{path:"runtime.approval.default",title:"Chat 工具确认默认策略",purpose:"`roll chat` 调用工具时的默认确认策略。`guarded` 使用内置读写启发式,`auto` 默认放行非破坏性工具,`deny` 默认拒绝。",defaultBehavior:`默认值为 \`${e.runtime.approval.default}\`。此配置不影响 \`roll ask\`。`,example:`runtime:\n approval:\n default: ${e.runtime.approval.default}`},{path:"runtime.approval.overrides",title:"Chat 工具确认精确覆盖",purpose:"`roll chat` 按完整 `agentName.toolName` 覆盖单个工具的确认策略。可选值为 `auto`、`confirm`、`deny`。",defaultBehavior:"未配置时回退到 `runtime.approval.default`。",example:"runtime:\n approval:\n overrides:\n browser-use-agent.zhipin_send_prepared_reply: confirm\n browser-use-agent.browser_status: auto"},{path:"browser.default-instance",title:"默认浏览器实例",purpose:"多浏览器实例配置下,未显式指定 browserInstance 时使用的默认实例。",defaultBehavior:"不配置且只有一个实例时可自动推断;多个实例时工具可能返回 needs_input。",example:"browser:\n default-instance: boss-a"},{path:"browser.instances",title:"浏览器实例声明",purpose:"声明 browser-use-agent 可使用的浏览器 profile、CDP 端口、会话目录和业务归因信息。",defaultBehavior:"不配置时 browser-use-agent 使用 legacy 单实例环境变量路径。",example:"browser:\n instances:\n boss-a:\n platform: zhipin\n mode: managed-cdp\n cdp-port: 9222\n user-data-dir: ~/.roll-agent/browser/boss-a"}];export function listConfigGuidanceEntries(){return CONFIG_GUIDANCE_ENTRIES}export function findConfigGuidance(e){const r=t(e.split(".")).join(".");return CONFIG_GUIDANCE_ENTRIES.find(e=>l(e.path,r))}function l(e,l){const r=t(e.split(".")).join(".");if(r===l)return!0;const a=r.split("."),n=l.split(".");return a.length===n.length&&a.every((e,t)=>e.startsWith("<")||e===n[t])}export function flattenAgentEnvDeclarations(e){return[...(e?.required??[]).map(e=>({...e,required:!0})),...(e?.optional??[]).map(e=>({...e,required:!1}))]}export function isSecretEnvName(e){return/(?:TOKEN|KEY|SECRET|PASSWORD)/iu.test(e)}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SessionEvent } from "@roll-agent/runtime";
|
|
2
|
+
export interface ChatApprover {
|
|
3
|
+
approve(approvalId: string): void;
|
|
4
|
+
reject(approvalId: string, reason?: string): void;
|
|
5
|
+
}
|
|
6
|
+
export type ChatConfirm = (message: string) => Promise<boolean>;
|
|
7
|
+
export declare const clackConfirm: ChatConfirm;
|
|
8
|
+
export declare class ChatRenderer {
|
|
9
|
+
private readonly confirm;
|
|
10
|
+
private readonly contextWindow;
|
|
11
|
+
private readonly spinners;
|
|
12
|
+
private compactionSpinner;
|
|
13
|
+
private messageSpinner;
|
|
14
|
+
private streaming;
|
|
15
|
+
constructor(confirm: ChatConfirm, contextWindow?: number);
|
|
16
|
+
handle(event: SessionEvent, approver: ChatApprover): Promise<void>;
|
|
17
|
+
private stopCompactionSpinner;
|
|
18
|
+
private startMessageSpinner;
|
|
19
|
+
private stopMessageSpinner;
|
|
20
|
+
private flushLine;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import s from"chalk";import{isCancel as e,select as t}from"@clack/prompts";import{createSpinner as n,log as i}from"./output.js";import{computeUsageParts as a,formatUsageLine as o}from"./token-format.js";import{formatToolInput as r}from"./tool-format.js";export const clackConfirm=async s=>{const n=await t({message:s,options:[{value:"yes",label:"Yes"},{value:"no",label:"No"}],initialValue:"no"});return!e(n)&&"yes"===n};function p(s){const e=[`chat.${s.stage}`,s.message];return void 0!==s.elapsedMs&&e.push(`${String(s.elapsedMs)}ms`),void 0!==s.data&&e.push(JSON.stringify(s.data)),e.join(" · ")}export class ChatRenderer{confirm;contextWindow;spinners=new Map;compactionSpinner;messageSpinner;streaming=!1;constructor(s,e){this.confirm=s,this.contextWindow=e}async handle(e,t){switch(e.type){case"debug":i.debug(p(e));break;case"message-start":this.startMessageSpinner();break;case"text-delta":this.stopMessageSpinner(),process.stdout.write(e.delta),this.streaming=!0;break;case"tool-call":{this.stopMessageSpinner(),this.flushLine();const t=n(`${s.cyan(`${e.agentName}.${e.toolName}`)} ${s.gray(r(e.input))}`);t.start(),this.spinners.set(e.toolCallId,t);break}case"tool-result":{const s=this.spinners.get(e.toolCallId);s&&(e.isError?s.fail():s.succeed(),this.spinners.delete(e.toolCallId));break}case"confirmation-required":{this.stopMessageSpinner(),this.flushLine();const s=e.reason?`(${e.reason})`:"";await this.confirm(`执行 ${e.agentName}.${e.toolName}${s}?`)?t.approve(e.approvalId):t.reject(e.approvalId,"用户取消");break}case"compaction-start":this.stopMessageSpinner(),this.flushLine(),this.compactionSpinner=n(s.gray("压缩上下文中…")),this.compactionSpinner.start();break;case"context-compacted":{this.stopCompactionSpinner(),this.flushLine();const t="auto"===e.reason?"自动压缩":"手动压缩",n=e.truncatedTools?`,精简 ${String(e.truncatedTools)} 个工具结果`:"",i=0!==e.removed||e.truncatedTools?`🗜 ${t}(${e.strategy}):移除 ${String(e.removed)} 条 → 保留 ${String(e.kept)} 条${n}`:`🗜 ${t}:无需压缩`;process.stderr.write(`${s.gray(i)}\n`);break}case"error":this.stopMessageSpinner(),this.stopCompactionSpinner(),this.flushLine(),i.error(e.message);break;case"message-finish":{this.stopMessageSpinner(),this.flushLine(),0===e.text.length&&(e.totalUsage?.outputTokens??0)>0&&i.warn("模型本轮只返回了 thinking/reasoning,没有生成可见回复"),e.stoppedAtStepLimit&&i.warn("已达单轮最大工具步数,任务可能未完成 — 继续追问即可接着做,或调高 runtime.max-steps");const t=o(a(e.totalUsage,e.sessionUsage,this.contextWindow,e.contextInputTokens));t&&process.stderr.write(`${s.gray(t)}\n`);break}}}stopCompactionSpinner(){this.compactionSpinner&&(this.compactionSpinner.stop(),this.compactionSpinner=void 0)}startMessageSpinner(){this.stopMessageSpinner(),this.messageSpinner=n(s.gray("思考中…")),this.messageSpinner.start()}stopMessageSpinner(){this.messageSpinner&&(this.messageSpinner.stop(),this.messageSpinner=void 0)}flushLine(){this.streaming&&(process.stdout.write("\n"),this.streaming=!1)}}
|
|
@@ -10,6 +10,7 @@ declare const LOG_LEVELS: {
|
|
|
10
10
|
export type LogLevel = keyof typeof LOG_LEVELS;
|
|
11
11
|
/** 设置全局日志级别 */
|
|
12
12
|
export declare function setLogLevel(level: LogLevel): void;
|
|
13
|
+
export declare function isDebugLogEnabled(): boolean;
|
|
13
14
|
export declare function resolveLogLevelFromArgv(argv: readonly string[]): LogLevel;
|
|
14
15
|
export declare function redactToolArgsForLog(value: unknown): unknown;
|
|
15
16
|
/** 结构化日志输出(写入 stderr,不干扰 stdout 数据流) */
|
package/dist/cli/utils/output.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import r from"chalk";import e from"ora";const o={debug:0,info:1,warn:2,error:3,silent:4},n=new Set(["--verbose","-v"]),t=/signed[-_]?envelope|token|secret|password|cookie|authorization|api[-_]?key/i;let i="info";export function setLogLevel(r){i=r}export function resolveLogLevelFromArgv(r){return r.some(r=>n.has(r))?"debug":"info"}export function redactToolArgsForLog(r){return c(r)}function c(r,e){if(e&&t.test(e))return s(r);if(Array.isArray(r))return r.map(r=>c(r));if(f(r)){const e={};for(const[o,n]of Object.entries(r))e[o]=c(n,o);return e}return r}function s(r){if("string"==typeof r)return`[redacted,len=${r.length}]`;const e=u(r);return void 0===e?"[redacted]":`[redacted,len=${e.length}]`}function u(r){try{return JSON.stringify(r)}catch{return}}function f(r){return"object"==typeof r&&null!==r&&!Array.isArray(r)}function a(r){return o[r]>=o[i]}export const log={debug(e){a("debug")&&console.error(r.gray(`[debug] ${e}`))},info(e){a("info")&&console.error(r.blue("→")+` ${e}`)},success(e){a("info")&&console.error(r.green("✓")+` ${e}`)},warn(e){a("warn")&&console.error(r.yellow("⚠")+` ${e}`)},error(e){a("error")&&console.error(r.red("✗")+` ${e}`)}};export function createSpinner(r){return e({text:r,stream:process.stderr})}
|
|
1
|
+
import r from"chalk";import e from"ora";const o={debug:0,info:1,warn:2,error:3,silent:4},n=new Set(["--verbose","-v"]),t=/signed[-_]?envelope|token|secret|password|cookie|authorization|api[-_]?key/i;let i="info";export function setLogLevel(r){i=r}export function isDebugLogEnabled(){return a("debug")}export function resolveLogLevelFromArgv(r){return r.some(r=>n.has(r))?"debug":"info"}export function redactToolArgsForLog(r){return c(r)}function c(r,e){if(e&&t.test(e))return s(r);if(Array.isArray(r))return r.map(r=>c(r));if(f(r)){const e={};for(const[o,n]of Object.entries(r))e[o]=c(n,o);return e}return r}function s(r){if("string"==typeof r)return`[redacted,len=${r.length}]`;const e=u(r);return void 0===e?"[redacted]":`[redacted,len=${e.length}]`}function u(r){try{return JSON.stringify(r)}catch{return}}function f(r){return"object"==typeof r&&null!==r&&!Array.isArray(r)}function a(r){return o[r]>=o[i]}export const log={debug(e){a("debug")&&console.error(r.gray(`[debug] ${e}`))},info(e){a("info")&&console.error(r.blue("→")+` ${e}`)},success(e){a("info")&&console.error(r.green("✓")+` ${e}`)},warn(e){a("warn")&&console.error(r.yellow("⚠")+` ${e}`)},error(e){a("error")&&console.error(r.red("✗")+` ${e}`)}};export function createSpinner(r){return e({text:r,stream:process.stderr})}
|