@roll-agent/core 0.18.0 → 0.19.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/THIRD_PARTY_NOTICES.txt +112 -0
- package/dist/cli/chat/banner.d.ts +13 -0
- package/dist/cli/chat/banner.js +1 -1
- package/dist/cli/chat/ink/app.d.ts +3 -0
- package/dist/cli/chat/ink/app.js +1 -1
- package/dist/cli/chat/ink/banner-view.d.ts +15 -0
- package/dist/cli/chat/ink/banner-view.js +1 -0
- package/dist/cli/chat/ink/history-item.js +1 -1
- package/dist/cli/chat/ink/run-ink-repl.js +1 -1
- package/dist/cli/commands/config-explain.js +1 -1
- package/dist/cli/commands/config-setup.js +1 -1
- package/dist/cli/commands/config.js +1 -1
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/ui.d.ts +30 -0
- package/dist/cli/commands/ui.js +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/config/application-service.d.ts +90 -0
- package/dist/config/application-service.js +1 -0
- package/dist/config/catalog.d.ts +76 -0
- package/dist/config/catalog.js +1 -0
- package/dist/config/defaults.d.ts +2 -2
- package/dist/config/defaults.js +1 -1
- package/dist/config/document-store.d.ts +70 -0
- package/dist/config/document-store.js +1 -0
- package/dist/config/guidance.d.ts +549 -0
- package/dist/config/guidance.js +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.js +1 -1
- package/dist/config/key-codec.d.ts +24 -3
- package/dist/config/key-codec.js +1 -1
- package/dist/config/migration.js +1 -1
- package/dist/config/secret-policy.d.ts +20 -0
- package/dist/config/secret-policy.js +1 -0
- package/dist/llm/providers.d.ts +2 -0
- package/dist/llm/providers.js +1 -1
- package/dist/registry/agent-lifecycle.d.ts +97 -0
- package/dist/registry/agent-lifecycle.js +1 -0
- package/dist/registry/discovery.js +1 -1
- package/dist/registry/process-identity.d.ts +14 -0
- package/dist/registry/process-identity.js +1 -0
- package/dist/registry/process-manager.d.ts +28 -7
- package/dist/registry/process-manager.js +1 -1
- package/dist/registry/store.js +1 -1
- package/dist/types/agent.d.ts +6 -0
- package/dist/types/agent.js +1 -1
- package/dist/ui/config-controller.d.ts +15 -0
- package/dist/ui/config-controller.js +1 -0
- package/dist/ui/contracts.d.ts +49 -0
- package/dist/ui/contracts.js +1 -0
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/runtime-controller.d.ts +38 -0
- package/dist/ui/runtime-controller.js +1 -0
- package/dist/ui/server.d.ts +21 -0
- package/dist/ui/server.js +1 -0
- package/dist/ui/static-assets.d.ts +2 -0
- package/dist/ui/static-assets.js +1 -0
- package/dist/ui-assets/assets/app.css +1 -0
- package/dist/ui-assets/assets/app.js +53 -0
- package/dist/ui-assets/assets/roll-mark-wXIkY5y0.svg +5 -0
- package/dist/ui-assets/index.html +17 -0
- package/package.json +15 -8
- package/dist/cli/commands/config-guidance.d.ts +0 -148
- 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;
|
package/dist/cli/chat/banner.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import n from"chalk";import t from"is-unicode-supported";const e="Roll Agent",
|
|
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")}
|
|
@@ -3,11 +3,14 @@ import type { AgentSession } from "@roll-agent/runtime";
|
|
|
3
3
|
import type { ThinkingLevel } from "../../../llm/providers.ts";
|
|
4
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;
|
package/dist/cli/chat/ink/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as i}from"node:crypto";import{createElement as t,
|
|
1
|
+
import{randomUUID as i}from"node:crypto";import{createElement as t,useCallback as n,useMemo as e,useRef as o,useState as r}from"react";import{Box as s,Static as a,useInput as m}from"ink";import{useSession as l}from"./use-session.js";import{HistoryItemView as d}from"./history-item.js";import{LiveRegion as f}from"./live-region.js";import{StatusLine as p}from"./status-line.js";import{TextPrompt as h}from"./text-prompt.js";import{ConfirmSelect as c}from"./confirm-select.js";import{SlashPopup as u}from"./slash-popup.js";import{buildSkillInvocationPrompt as g,buildSkillListLines as v,filterSlashEntries as k,parseSkillInvocation as j,SLASH_COMMANDS as b}from"./commands.js";import{bannerTextLine as x}from"../banner.js";import{BannerHistoryView as C}from"./banner-view.js";import{cycleThinking as y}from"./thinking.js";function L(){return b.map(i=>`${i.name} — ${i.description}`).join("\n")}export function ChatApp(M){const{session:T,model:A,contextWindow:S,onUserSubmit:$,onExit:w}=M,H=M.availableSkills??[],{state:W,submit:D,compact:B,cancel:E,resolveConfirm:P,setDraft:R,setThinking:U,setAutoMode:q,toggleAutoMode:z,commitHistory:F}=l(T,{model:A,contextWindow:S,...M.initialHistory?{initialHistory:M.initialHistory}:{},...M.initialThinkingLevel?{initialThinkingLevel:M.initialThinkingLevel}:{},...M.onThinkingChange?{onThinkingChange:M.onThinkingChange}:{}}),G=e(()=>[...W.history],[W.history]),[I,J]=r(!1),K=o(M.animatedBanner),N=n(()=>{const i=K.current;void 0!==i&&(K.current=void 0,F({kind:"banner",id:"banner",lines:i})),J(!0)},[F]),O=I?void 0:M.animatedBanner,[Q,V]=r(0),X="idle"===W.phase&&W.draft.startsWith("/"),Y=X&&!0===W.draft.split(/\s+/).at(-1)?.startsWith("/"),Z=Y?k(W.draft,H):[],_=Math.max(Z.length-1,0),ii=Math.min(Q,_);m((i,t)=>{"busy"===W.phase&&t.escape&&!t.meta?E():t.tab&&t.shift?z():t.meta&&"."===i?U(y(W.status.thinkingLevel,1)):t.meta&&","===i&&U(y(W.status.thinkingLevel,-1))});const ti=(i,t)=>{const n=i.trim();0!==n.length?(N(),$(n),D(n,t)):R("")},ni="confirm"===W.phase&&void 0!==W.pendingConfirm?t(c,{prompt:W.pendingConfirm.prompt,args:W.pendingConfirm.args,onDecide:P}):t(h,{value:W.draft,disabled:"idle"!==W.phase,slashActive:X,slashPopupActive:Y,autoApprove:W.status.autoApprove,onChange:R,onSubmit:ti,onSlashMove:i=>{V(t=>Math.min(Math.max(Math.min(t,_)+i,0),_))},onSlashComplete:()=>{const i=Z[ii];if(i){const t=W.draft.split(/\s+/);t[t.length-1]=i.name,R(`${t.join(" ")} `),V(0)}},onSlashRun:()=>{const t=W.draft.trim().split(/\s+/,1)[0]??"",n=b.some(i=>i.name===t),e=Z[ii];if(!n&&"skill"===e?.kind){const i=W.draft.split(/\s+/);return i[i.length-1]=e.name,R(`${i.join(" ")} `),void V(0)}(t=>{N(),R("");const n=t.trim(),e=n.split(/\s+/),o=e[0]??"",r=(e[1]??"").toLowerCase(),s=W.status.thinkingLevel;if("/compact"===o)return void B();if("/think"===o)return void U("off"===r?"off":"on"===r?"off"===s?"medium":s:"off"===s?"medium":"off");if("/effort"===o)return void("low"===r||"medium"===r||"high"===r?U(r):F({kind:"notice",id:i(),text:"用法: /effort low | medium | high"}));if("/auto"===o)return void("on"===r?q(!0):"off"===r?q(!1):z());if("/skills"===o){const t=(process.stdout.columns||80)-2,[n,...e]=v(H,t);return void F({kind:"banner",id:i(),lines:[x(n??""),...e.map(i=>x(i,{dim:!0}))]})}if("/help"===o)return void F({kind:"notice",id:i(),text:L()});if("/exit"===o)return void w();const a=j(n,H);if(a)return 0===a.prompt.length?void R(`${a.skills.map(i=>`/${i.name}`).join(" ")} `):void ti(n,g(a));F({kind:"notice",id:i(),text:`未知命令 ${o}`})})(n?W.draft:e?.name??W.draft)}});return t(s,{flexDirection:"column"},t(a,{items:G,children:i=>{const n="user"===i.kind||"assistant"===i.kind,e="tool"===i.kind||"denied"===i.kind||"cancelled"===i.kind;return t(s,{key:i.id,marginTop:n?1:0,marginLeft:e?3:1},t(d,{item:i}))}}),void 0!==O?t(s,{marginLeft:1},t(C,{lines:O,onSettled:N})):null,t(s,{marginLeft:1},t(f,{live:W.live})),t(p,{status:W.status}),X?t(u,{matches:Z,selected:ii}):null,ni)}
|
|
@@ -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 +1 @@
|
|
|
1
|
-
import{createElement as r}from"react";import{Box as
|
|
1
|
+
import{createElement as r}from"react";import{Box as e,Text as o}from"ink";import{parseThinking as t}from"./thinking-text.js";import{Markdown as n}from"./markdown.js";import{ToolLabel as l}from"./tool-label.js";import{BannerLinesView as i}from"./banner-view.js";const a=["已取消执行","策略拒绝执行"];function c(r){const e=r.trimStart();return a.some(r=>e.startsWith(r))}export function HistoryItemView({item:a}){switch(a.kind){case"banner":return r(i,{lines:a.lines});case"user":return r(e,null,r(o,{color:"cyan",bold:!0},"▌ "),r(o,{color:"cyan"},a.text));case"assistant":return c(a.text)?r(e,null,r(o,{dimColor:!0},"⊘ "),r(o,{dimColor:!0},a.text.trim())):r(e,{flexDirection:"column"},...t(a.text).map((e,t)=>e.thinking?r(o,{key:String(t),dimColor:!0},e.text):r(n,{key:String(t),text:e.text})));case"tool":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(o,null,r(o,a.ok?{color:"green"}:{color:"red"},a.ok?"✓ ":"✗ "),r(l,{name:a.name}),e.length>0?r(o,{dimColor:!0},e):null)}case"denied":return r(o,{dimColor:!0},`⊘ ${a.name} ${a.label}`);case"cancelled":{const e=a.args.length>0&&"{}"!==a.args?` ${a.args}`:"";return r(o,{color:"yellow"},"■ ",r(l,{name:a.name}),e.length>0?r(o,{dimColor:!0},e):null,r(o,{dimColor:!0}," 已中断"))}case"compaction":return r(o,{dimColor:!0},a.notice);case"notice":return r(e,null,r(o,{color:"yellow"},"⚠ "),r(o,{color:"yellow"},a.text));case"error":return r(e,null,r(o,{color:"red"},"✗ "),r(o,{color:"red"},a.message));default:return r(o,null,"")}}
|
|
@@ -1 +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
|
|
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 +1 @@
|
|
|
1
|
-
import{getAgentEnvFromAgentsConfig as e,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{loadAgentsConfig as o}from"../../config/loader.js";import{normalizeUserPath as t}from"../../config/key-codec.js";import{AgentStore as l}from"../../registry/store.js";import{findConfigGuidance as s,flattenAgentEnvDeclarations as r,listConfigGuidanceEntries as i}from"
|
|
1
|
+
import{getAgentEnvFromAgentsConfig as e,inspectAgentEnvRequirements as n}from"../../config/helpers.js";import{loadAgentsConfig as o}from"../../config/loader.js";import{normalizeUserPath as t}from"../../config/key-codec.js";import{AgentStore as l}from"../../registry/store.js";import{findConfigGuidance as s,flattenAgentEnvDeclarations as r,listConfigGuidanceEntries as i}from"../../config/guidance.js";export function explainConfig(e){if(void 0===e){console.log("可解释的配置路径:\n");for(const e of i())console.log(`- ${e.path}: ${e.title}`);return console.log("\n示例:"),console.log(" roll config explain install.registry"),void console.log(" roll config explain agents.env.browser-use-agent.REPLY_AUTHORITY_URL")}const n=g(e);if(n)return void console.log(n);const o=s(e);if(!o)return console.error(`✗ 未找到配置说明: ${e}`),console.error(" 运行 `roll config explain` 查看可用路径。"),void(process.exitCode=1);console.log(`${o.title}\n`),console.log(`路径: ${o.path}`),console.log(`用途: ${o.purpose}`),o.defaultBehavior&&console.log(`默认行为: ${o.defaultBehavior}`),o.example&&console.log(`示例:\n${o.example}`),o.setupCommand&&console.log(`配置向导: ${o.setupCommand}`)}function g(s){const i=t(s.split("."));if("agents"!==i[0]||"env"!==i[1])return;const g=i[2];if(void 0===g)return;const c=i[3],{agentsConfig:a}=o(),f=new l(a.dataDir).findByName(g);if(!f)return`Agent 环境变量\n\nAgent "${g}" 未注册。\n配置向导: roll config setup agent ${g}`;const u=r(f.skill.env);if(0===u.length)return`Agent 环境变量\n\nAgent "${g}" 未声明环境变量需求。`;if(void 0===c){const e=n(f.skill.name,f.skill.env,a.env),o=["Agent 环境变量","",`路径: agents.env.${g}`,`用途: 注入到 Agent "${g}" 进程的环境变量。`,`配置向导: roll config setup agent ${g}`,"","变量:"];for(const n of e?.items??u)o.push(`- ${n.name}: ${n.required?"必填":"可选"}${"source"in n?`,来源 ${n.source}`:""}`);return o.join("\n")}const p=u.find(e=>e.name===c);if(!p)return`Agent 环境变量\n\nAgent "${g}" 未声明环境变量 ${c}。\n配置向导: roll config setup agent ${g}`;const $=n(f.skill.name,f.skill.env,a.env),m=$?.items.find(e=>e.name===c),d=e(a,f.skill.name),v=[`Agent 环境变量: ${c}`,"",`路径: agents.env.${g}.${c}`,`Agent: ${g}`,"类型: "+(p.required?"必填":"可选")];return p.purpose&&v.push(`用途: ${p.purpose}`),p.default&&v.push(`默认值: ${p.default}`),p.example&&v.push(`示例: ${p.example}`),m&&v.push(`当前来源: ${m.source}`),d?.[c]&&v.push("当前 YAML: 已配置"),v.push(`配置向导: roll config setup agent ${g}`),v.join("\n")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t,writeFileSync as r}from"node:fs";import{homedir as n}from"node:os";import{resolve as a}from"node:path";import{stringify as i}from"yaml";import{DEFAULT_CONFIG as o,DEFAULT_LLM_MODELS as l,DEFAULT_LLM_PROVIDER as s,LLM_PROVIDER_OPTIONS as c}from"../../config/defaults.js";import{inspectAgentEnvRequirements as u}from"../../config/helpers.js";import{loadAgentsConfig as m,parseConfigDocument as f,resolveConfigPath as d,validateConfigText as g}from"../../config/loader.js";import{encodePathToYaml as p}from"../../config/key-codec.js";import{detectKnownConfigMigrations as h,formatConfigMigrationError as v}from"../../config/migration.js";import{AgentStore as w}from"../../registry/store.js";import{flattenAgentEnvDeclarations as y,isSecretEnvName as k}from"./config-guidance.js";import{ConfigSetupCancelledError as $,clackPromptAdapter as b}from"./config-prompts.js";const A=/\$\{[^}]+\}/u;export async function runConfigSetup(e,t,r=b){try{r.intro("Roll 配置向导"),q();switch(await x(e,r)){case"llm":r.outro(await setupLlm(r));break;case"install":r.outro(await setupInstall(r));break;case"agent":r.outro(await setupAgentEnv(t,r));break;case"shell":r.outro(await setupShell(r))}}catch(e){if(e instanceof $)return void(process.exitCode=1);throw e}}async function x(e,t){if(void 0===e)return t.select({message:"要配置哪一类内容?",options:[{value:"llm",label:"LLM",hint:"默认模型和 provider key"},{value:"install",label:"Install network",hint:"npm registry、重试和超时"},{value:"agent",label:"Agent env",hint:"配置某个 Agent 需要的环境变量"},{value:"shell",label:"Chat shell 工具",hint:"roll chat 内建本机命令执行能力(默认关闭)"}]});if("llm"===e||"install"===e||"agent"===e||"shell"===e)return e;if("bash"===e)return"shell";throw new Error(`未知 setup 模块: ${e}。可用: llm, install, agent, shell`)}export async function setupLlm(e){q();const t=await e.select({message:"选择默认 LLM provider",options:c.map(e=>({value:e,label:e,hint:l[e]})),initialValue:s}),r=await e.text({message:"默认 model",defaultValue:l[t],required:!0}),n=await e.password({message:`${t} API key(可直接输入真实 key,也可输入 \${ENV_VAR})`,required:!0}),a=await e.text({message:"自定义 base-url(可选,留空则不写)",required:!1}),i=q();return N(i.document,["llm","defaultProvider"],t),N(i.document,["llm","defaultModel"],r),N(i.document,["llm","providers",t,"apiKey"],n),a.trim().length>0?N(i.document,["llm","providers",t,"baseUrl"],a.trim()):O(i.document,["llm","providers",t,"baseUrl"]),R(i),C(e,`${t} API key`,n),`已配置 LLM: ${t}/${r}(写入 ${i.configPath})`}export async function setupInstall(e){q();const t=await e.select({message:"当前安装/更新网络更接近哪种场景?",options:[{value:"default-network",label:"默认网络 / 海外网络",hint:"不设置 registry"},{value:"china-dev",label:"国内开发机",hint:"使用 https://registry.npmmirror.com"},{value:"private-registry",label:"企业内网 / 私有 registry"},{value:"advanced",label:"高级自定义"}]}),r=await S(t,e),n=q();switch(r.scenario){case"default-network":P(n.document),O(n.document,["install","registry"]);break;case"china-dev":P(n.document),N(n.document,["install","registry"],"https://registry.npmmirror.com");break;case"private-registry":P(n.document),N(n.document,["install","registry"],r.registry);break;case"advanced":{const{registry:e,fetchRetries:t,preferOffline:a,networkTimeoutMs:i}=r.values;N(n.document,["install","fetchRetries"],t),N(n.document,["install","preferOffline"],a),N(n.document,["install","networkTimeoutMs"],i),e.length>0?N(n.document,["install","registry"],e):O(n.document,["install","registry"]);break}}return R(n),`已配置 install 网络参数(写入 ${n.configPath})`}export async function setupShell(e,t=process.platform){q();const r=await e.confirm({message:"启用 roll chat 内建 shell 工具(允许模型在本机执行命令;默认确认策略受 runtime.approval 及显式 override 控制)?",initialValue:!1});let n=!0,a=!1;const i="win32"===t;r&&i&&e.warn("Windows PowerShell 命令当前全部按 unknown 处理,默认每条命令都需确认;显式 approval override(roll.powershell: auto、roll.exec_command: auto)可覆盖对应工具的默认策略。"),r&&!i&&(n=await e.confirm({message:"安全只读命令(ls/grep 等)自动放行,无需逐条确认?",initialValue:!0})),r&&(a=await e.confirm({message:"启用长跑命令会话(session exec,供 dev server 等常驻进程使用)?",initialValue:!1}));const o=q();N(o.document,["runtime","shell","enabled"],r),r&&!i&&N(o.document,["runtime","shell","autoApproveSafe"],n),r&&N(o.document,["runtime","shell","session","enabled"],a),R(o);return`已${r?"启用":"禁用"} chat shell 工具${r&&i?";Windows PowerShell 默认逐条确认(显式 approval override 可覆盖)":""}(写入 ${o.configPath})`}export async function setupBash(e){return setupShell(e)}async function S(e,t){switch(e){case"default-network":case"china-dev":return{scenario:e};case"private-registry":return{scenario:e,registry:(await t.text({message:"私有 registry URL",placeholder:"https://registry.example.com",required:!0})).trim()};case"advanced":return{scenario:e,values:await V(t)}}}async function V(e){const t=await e.text({message:"npm registry(留空则不写)",placeholder:"https://registry.npmmirror.com"}),r=await e.text({message:"fetch-retries",defaultValue:"3",required:!0,validate:T(0,10)}),n=await e.confirm({message:"是否启用 prefer-offline?",initialValue:!1}),a=await e.text({message:"network-timeout-ms",defaultValue:"120000",required:!0,validate:T(1e4,Number.MAX_SAFE_INTEGER)});return{registry:t.trim(),fetchRetries:Number.parseInt(r,10),preferOffline:n,networkTimeoutMs:Number.parseInt(a,10)}}export async function setupAgentEnv(e,t){q();const{agentsConfig:r}=m(),n=new w(r.dataDir),a=await E(n.list(),e,t),i=u(a.skill.name,a.skill.env,r.env);if(!i)throw new Error(`Agent "${a.skill.name}" 未声明环境变量需求。`);const o={...r.env?.[a.skill.name]??{}},l=y(a.skill.env),s=new Map(i.items.map(e=>[e.name,e]));for(const e of l.filter(e=>e.required)){const r=s.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}if(await t.confirm({message:"是否配置可选环境变量?",initialValue:!1}))for(const e of l.filter(e=>!e.required)){const r=s.get(e.name),n=await j({declaration:e,yamlValue:o[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(o[e.name]=n)}const c=q();N(c.document,["agents","env",a.skill.name],o),R(c);for(const[e,r]of Object.entries(o))k(e)&&C(t,e,r);return D(t,a),`已配置 Agent 环境变量: ${a.skill.name}(写入 ${c.configPath})`}async function E(e,t,r){if(void 0!==t){const r=e.find(e=>e.skill.name===t);if(!r)throw new Error(`Agent "${t}" 未找到`);if(!M(r))throw new Error(`Agent "${t}" 未声明环境变量需求。`);return r}const n=e.filter(M);if(0===n.length)throw new Error("没有已注册且声明环境变量需求的 Agent。");const a=await r.select({message:"选择要配置的 Agent",options:n.map(e=>({value:e.skill.name,label:e.skill.name,hint:e.skill.description}))}),i=n.find(e=>e.skill.name===a);if(!i)throw new Error(`Agent "${a}" 未找到`);return i}function M(e){return y(e.skill.env).length>0}async function j(e,t){const{declaration:r,processEnvValue:n,source:a,yamlValue:i}=e,o="agents.env"===a&&_(i),l=[r.required?"必填":"可选",o?"回车保留当前值":void 0].filter(e=>void 0!==e).join(","),s=`${r.name}(${l})`,c=[r.purpose?`用途: ${r.purpose}`:void 0,r.example?`示例: ${r.example}`:void 0,r.default?`默认: ${r.default}`:void 0,o?"当前 YAML 已配置,回车保留当前值。":void 0,void 0===i||o?void 0:"当前 YAML 值为空或占位符未解析,会被视为缺失。","process.env"===a?"当前来源: 当前 shell 临时环境变量,尚未持久写入 YAML。":void 0].filter(e=>void 0!==e).join("\n");if(c.length>0&&t.info(c),"process.env"===a&&"string"==typeof n&&n.length>0){if(await t.confirm({message:`是否将 ${r.name} 的当前 shell 值写入 roll.config.yaml?`,initialValue:!0}))return n}const u=r.required&&!o&&void 0===r.default&&"process.env"!==a,m={message:s,...o&&void 0!==i?{defaultValue:i}:{},...void 0!==r.example?{placeholder:r.example}:{},required:u},f=k(r.name)?await t.password({message:s,required:u}):await t.text(m);return 0===f.trim().length?o?i:void 0:f}function P(e){N(e,["install","fetchRetries"],o.install.fetchRetries),N(e,["install","preferOffline"],o.install.preferOffline),N(e,["install","networkTimeoutMs"],o.install.networkTimeoutMs)}function q(){const r=d(),i=r??a(n(),"roll.config.yaml");if(void 0===r||!e(i))return{configPath:i,existed:!1,document:L()};const o=t(i,"utf-8"),l=f(o,i),s=h(l);if(s.needsMigration)throw new Error(v(i,s));return{configPath:i,existed:!0,raw:o,document:l}}function L(){return{llm:{"default-provider":o.llm.defaultProvider,"default-model":o.llm.defaultModel,providers:{}},ask:{},agents:{"data-dir":o.agents.dataDir}}}function R(e){const t=i(e.document,{lineWidth:0});g(t,e.configPath),e.existed&&void 0!==e.raw&&r(I(e.configPath),e.raw,"utf-8"),r(e.configPath,t,"utf-8")}function I(e){const t=new Date;return`${e}.bak.${[t.getFullYear().toString().padStart(4,"0"),(t.getMonth()+1).toString().padStart(2,"0"),t.getDate().toString().padStart(2,"0"),"-",t.getHours().toString().padStart(2,"0"),t.getMinutes().toString().padStart(2,"0"),t.getSeconds().toString().padStart(2,"0")].join("")}`}function N(e,t,r){const n=p(t),a=n[n.length-1];if(void 0===a)throw new Error("配置路径不能为空");let i=e;for(const e of n.slice(0,-1)){const t=i[e];("object"!=typeof t||null===t||Array.isArray(t))&&(i[e]={}),i=i[e]}i[a]=r}function O(e,t){const r=p(t),n=r[r.length-1];if(void 0===n)return;let a=e;for(const e of r.slice(0,-1)){if("object"!=typeof a||null===a||Array.isArray(a))return;a=a[e]}"object"!=typeof a||null===a||Array.isArray(a)||delete a[n]}function T(e,t){return r=>{if(!/^\d+$/u.test(r))return"请输入整数";const n=Number.parseInt(r,10);return n<e||n>t?`请输入 ${String(e)} 到 ${String(t)} 之间的整数`:void 0}}function _(e){return"string"==typeof e&&e.length>0&&!A.test(e)}function C(e,t,r){0===r.trim().length||A.test(r)||e.warn(`${t} 将以明文写入 roll.config.yaml,建议改用 \${ENV_VAR} 引用并避免把配置文件提交到版本库。`)}function D(e,t){const r=t.skill.name;switch(t.runtime.ownership){case"core-managed":e.info(`需重启后生效:roll agent stop ${r} && roll agent start ${r}`);break;case"external-managed":e.info(`请重启你自行管理的 "${r}" 进程以加载新环境变量。`);break;case"on-demand":e.info(`下次 roll run / roll ask 调用 "${r}" 时自动生效。`)}}
|
|
1
|
+
import{isDeepStrictEqual as e}from"node:util";import{ConfigApplicationService as t,createConfigPatches as r}from"../../config/application-service.js";import{ConfigRevisionConflictError as n}from"../../config/document-store.js";import{DEFAULT_CONFIG as i,DEFAULT_LLM_MODELS as s,DEFAULT_LLM_PROVIDER as o,LLM_PROVIDER_OPTIONS as a}from"../../config/defaults.js";import{inspectAgentEnvRequirements as l}from"../../config/helpers.js";import{decodeFromYaml as c}from"../../config/key-codec.js";import{loadAgentsConfig as u}from"../../config/loader.js";import{AgentStore as f}from"../../registry/store.js";import{flattenAgentEnvDeclarations as m,isSecretEnvDeclaration as d}from"../../config/guidance.js";import{ConfigSetupCancelledError as p,clackPromptAdapter as g}from"./config-prompts.js";const v=/\$\{[^}]+\}/u;export async function runConfigSetup(e,t,r=g){try{r.intro("Roll 配置向导"),x();switch(await h(e,r)){case"llm":r.outro(await setupLlm(r));break;case"install":r.outro(await setupInstall(r));break;case"agent":r.outro(await setupAgentEnv(t,r));break;case"shell":r.outro(await setupShell(r))}}catch(e){if(e instanceof p)return void(process.exitCode=1);throw e}}async function h(e,t){if(void 0===e)return t.select({message:"要配置哪一类内容?",options:[{value:"llm",label:"LLM",hint:"默认模型和 provider key"},{value:"install",label:"Install network",hint:"npm registry、重试和超时"},{value:"agent",label:"Agent env",hint:"配置某个 Agent 需要的环境变量"},{value:"shell",label:"Chat shell 工具",hint:"roll chat 内建本机命令执行能力(默认关闭)"}]});if("llm"===e||"install"===e||"agent"===e||"shell"===e)return e;if("bash"===e)return"shell";throw new Error(`未知 setup 模块: ${e}。可用: llm, install, agent, shell`)}export async function setupLlm(e){const t=x(),r=await e.select({message:"选择默认 LLM provider",options:a.map(e=>({value:e,label:e,hint:s[e]})),initialValue:o}),n=await e.text({message:"默认 model",defaultValue:s[r],required:!0}),i=await e.password({message:`${r} API key(可直接输入真实 key,也可输入 \${ENV_VAR})`,required:!0}),l=await e.text({message:"自定义 base-url(可选,留空则不写)",required:!1});return M(t.document,["llm","defaultProvider"],r),M(t.document,["llm","defaultModel"],n),M(t.document,["llm","providers",r,"apiKey"],i),l.trim().length>0?M(t.document,["llm","providers",r,"baseUrl"],l.trim()):q(t.document,["llm","providers",r,"baseUrl"]),j(t),T(e,`${r} API key`,i),`已配置 LLM: ${r}/${n}(写入 ${t.configPath})`}export async function setupInstall(e){const t=x(),r=await e.select({message:"当前安装/更新网络更接近哪种场景?",options:[{value:"default-network",label:"默认网络 / 海外网络",hint:"不设置 registry"},{value:"china-dev",label:"国内开发机",hint:"使用 https://registry.npmmirror.com"},{value:"private-registry",label:"企业内网 / 私有 registry"},{value:"advanced",label:"高级自定义"}]}),n=await w(r,e);switch(n.scenario){case"default-network":A(t.document),q(t.document,["install","registry"]);break;case"china-dev":A(t.document),M(t.document,["install","registry"],"https://registry.npmmirror.com");break;case"private-registry":A(t.document),M(t.document,["install","registry"],n.registry);break;case"advanced":{const{registry:e,fetchRetries:r,preferOffline:i,networkTimeoutMs:s}=n.values;M(t.document,["install","fetchRetries"],r),M(t.document,["install","preferOffline"],i),M(t.document,["install","networkTimeoutMs"],s),e.length>0?M(t.document,["install","registry"],e):q(t.document,["install","registry"]);break}}return j(t),`已配置 install 网络参数(写入 ${t.configPath})`}export async function setupShell(e,t=process.platform){const r=x(),n=await e.confirm({message:"启用 roll chat 内建 shell 工具(允许模型在本机执行命令;默认确认策略受 runtime.approval 及显式 override 控制)?",initialValue:!1});let i=!0,s=!1;const o="win32"===t;n&&o&&e.warn("Windows PowerShell 命令当前全部按 unknown 处理,默认每条命令都需确认;显式 approval override(roll.powershell: auto、roll.exec_command: auto)可覆盖对应工具的默认策略。"),n&&!o&&(i=await e.confirm({message:"安全只读命令(ls/grep 等)自动放行,无需逐条确认?",initialValue:!0})),n&&(s=await e.confirm({message:"启用长跑命令会话(session exec,供 dev server 等常驻进程使用)?",initialValue:!1})),M(r.document,["runtime","shell","enabled"],n),n&&!o&&M(r.document,["runtime","shell","autoApproveSafe"],i),n&&M(r.document,["runtime","shell","session","enabled"],s),j(r);return`已${n?"启用":"禁用"} chat shell 工具${n&&o?";Windows PowerShell 默认逐条确认(显式 approval override 可覆盖)":""}(写入 ${r.configPath})`}export async function setupBash(e){return setupShell(e)}async function w(e,t){switch(e){case"default-network":case"china-dev":return{scenario:e};case"private-registry":return{scenario:e,registry:(await t.text({message:"私有 registry URL",placeholder:"https://registry.example.com",required:!0})).trim()};case"advanced":return{scenario:e,values:await y(t)}}}async function y(e){const t=await e.text({message:"npm registry(留空则不写)",placeholder:"https://registry.npmmirror.com"}),r=await e.text({message:"fetch-retries",defaultValue:"3",required:!0,validate:I(0,10)}),n=await e.confirm({message:"是否启用 prefer-offline?",initialValue:!1}),i=await e.text({message:"network-timeout-ms",defaultValue:"120000",required:!0,validate:I(1e4,Number.MAX_SAFE_INTEGER)});return{registry:t.trim(),fetchRetries:Number.parseInt(r,10),preferOffline:n,networkTimeoutMs:Number.parseInt(i,10)}}export async function setupAgentEnv(e,t){const r=x(),{agentsConfig:n}=u(),i=new f(n.dataDir),s=await b(i.list(),e,t),o=l(s.skill.name,s.skill.env,n.env);if(!o)throw new Error(`Agent "${s.skill.name}" 未声明环境变量需求。`);const a={...L(r.document,s.skill.name)},c=m(s.skill.env),p=new Map(o.items.map(e=>[e.name,e]));for(const e of c.filter(e=>e.required)){const r=p.get(e.name),n=await $({declaration:e,yamlValue:a[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(a[e.name]=n)}if(await t.confirm({message:"是否配置可选环境变量?",initialValue:!1}))for(const e of c.filter(e=>!e.required)){const r=p.get(e.name),n=await $({declaration:e,yamlValue:a[e.name],source:r?.source,processEnvValue:process.env[e.name]},t);void 0!==n&&(a[e.name]=n)}M(r.document,["agents","env",s.skill.name],a),j(r);for(const[e,r]of Object.entries(a)){const n=c.find(t=>t.name===e);void 0!==n&&d(n)&&T(t,e,r)}return _(t,s),`已配置 Agent 环境变量: ${s.skill.name}(写入 ${r.configPath})`}async function b(e,t,r){if(void 0!==t){const r=e.find(e=>e.skill.name===t);if(!r)throw new Error(`Agent "${t}" 未找到`);if(!k(r))throw new Error(`Agent "${t}" 未声明环境变量需求。`);return r}const n=e.filter(k);if(0===n.length)throw new Error("没有已注册且声明环境变量需求的 Agent。");const i=await r.select({message:"选择要配置的 Agent",options:n.map(e=>({value:e.skill.name,label:e.skill.name,hint:e.skill.description}))}),s=n.find(e=>e.skill.name===i);if(!s)throw new Error(`Agent "${i}" 未找到`);return s}function k(e){return m(e.skill.env).length>0}async function $(e,t){const{declaration:r,processEnvValue:n,source:i,yamlValue:s}=e,o=N(s),a=o&&v.test(s),l=[r.required?"必填":"可选",o?"回车保留当前值":void 0].filter(e=>void 0!==e).join(","),c=`${r.name}(${l})`,u=[r.purpose?`用途: ${r.purpose}`:void 0,r.example?`示例: ${r.example}`:void 0,r.default?`默认: ${r.default}`:void 0,o?a?"当前 YAML 使用环境变量占位符,回车会原样保留。":"当前 YAML 已配置,回车保留当前值。":void 0,a&&"agents.env"!==i?"当前占位符未解析,运行时仍会把该值视为缺失。":void 0,"process.env"===i?"当前来源: 当前 shell 临时环境变量,尚未持久写入 YAML。":void 0].filter(e=>void 0!==e).join("\n");if(u.length>0&&t.info(u),"process.env"===i&&"string"==typeof n&&n.length>0){if(await t.confirm({message:`是否将 ${r.name} 的当前 shell 值写入 roll.config.yaml?`,initialValue:!0}))return n}const f=r.required&&!o&&void 0===r.default&&"process.env"!==i,m={message:c,...o&&void 0!==s?{defaultValue:s}:{},...void 0!==r.example?{placeholder:r.example}:{},required:f},p=d(r)?await t.password({message:c,required:f}):await t.text(m);return 0===p.trim().length?o?s:void 0:p}function A(e){M(e,["install","fetchRetries"],i.install.fetchRetries),M(e,["install","preferOffline"],i.install.preferOffline),M(e,["install","networkTimeoutMs"],i.install.networkTimeoutMs)}function x(){const e=new t,r=e.readForRepair(),i=V(e);if(r.revision!==i.revision)throw new n(r.revision,i.revision);return{configPath:r.configPath,existed:r.existed,document:R(r.persisted),basePersisted:i.persisted,snapshot:r,service:e}}function j(e){const t=r(e.snapshot.persisted,e.document),n=V(e.service);E(e.basePersisted,n.persisted,t,e.snapshot.revision,n.revision),e.service.savePatches(t,n.revision)}function V(e){const t=e.store.read(),r=c(t.persisted);if(!S(r))throw new Error(`配置内容必须是 object: ${t.configPath}`);return{revision:t.revision,persisted:r}}function E(t,r,i,s,o){for(const a of i){const i=P(t,a.path),l=P(r,a.path);if(i.exists!==l.exists||!e(i.value,l.value))throw new n(s,o)}}function P(e,t){let r=e;for(const e of t)if("number"!=typeof e){if(!S(r)||!(e in r))return{exists:!1,value:void 0};r=r[e]}else{if(!Array.isArray(r)||!(e in r))return{exists:!1,value:void 0};r=r[e]}return{exists:!0,value:r}}function M(e,t,r){const n=t[t.length-1];if(void 0===n)throw new Error("配置路径不能为空");let i=e;for(const e of t.slice(0,-1)){const t=i[e];("object"!=typeof t||null===t||Array.isArray(t))&&(i[e]={}),i=i[e]}i[n]=r}function q(e,t){const r=t[t.length-1];if(void 0===r)return;let n=e;for(const e of t.slice(0,-1)){if("object"!=typeof n||null===n||Array.isArray(n))return;n=n[e]}"object"!=typeof n||null===n||Array.isArray(n)||delete n[r]}function L(e,t){const r=e.agents,n=S(r)?r.env:void 0,i=S(n)?n[t]:void 0;return S(i)?Object.fromEntries(Object.entries(i).filter(e=>"string"==typeof e[1])):{}}function R(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,O(t)]))}function O(e){return Array.isArray(e)?e.map(O):S(e)?R(e):e}function S(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function I(e,t){return r=>{if(!/^\d+$/u.test(r))return"请输入整数";const n=Number.parseInt(r,10);return n<e||n>t?`请输入 ${String(e)} 到 ${String(t)} 之间的整数`:void 0}}function N(e){return"string"==typeof e&&e.length>0}function T(e,t,r){0===r.trim().length||v.test(r)||e.warn(`${t} 将以明文写入 roll.config.yaml,建议改用 \${ENV_VAR} 引用并避免把配置文件提交到版本库。`)}function _(e,t){const r=t.skill.name;switch(t.runtime.ownership){case"core-managed":e.info(`需重启后生效:roll agent stop ${r} && roll agent start ${r}`);break;case"external-managed":e.info(`请重启你自行管理的 "${r}" 进程以加载新环境变量。`);break;case"on-demand":e.info(`下次 roll run / roll ask 调用 "${r}" 时自动生效。`)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineCommand as o}from"citty";import{
|
|
1
|
+
import{defineCommand as o}from"citty";import{existsSync as e,readFileSync as r}from"node:fs";import{homedir as t}from"node:os";import{resolve as n}from"node:path";import{createInterface as i}from"node:readline/promises";import{ConfigApplicationService as s}from"../../config/application-service.js";import{DEFAULT_CONFIG as c,DEFAULT_LLM_MODELS as a,LLM_PROVIDER_OPTIONS as l}from"../../config/defaults.js";import{inspectConfigFile as f,loadConfig as d,parseConfigDocument as u,resolveConfigPath as p}from"../../config/loader.js";import{createConfigRevision as g}from"../../config/document-store.js";import{decodeFromYaml as m,normalizeUserPath as v}from"../../config/key-codec.js";import{applyKnownConfigMigrations as h}from"../../config/migration.js";import{explainConfig as y}from"./config-explain.js";import{runConfigSetup as $}from"./config-setup.js";export default o({meta:{description:"管理全局配置"},args:{action:{type:"positional",description:"操作(init/get/set/migrate/setup/explain)",required:!0},key:{type:"positional",description:"配置键(get/set 时使用,用英文句点 `.` 分隔,如 ask.confirm-threshold)",required:!1},value:{type:"positional",description:"配置值(set 时使用)",required:!1}},async run({args:o}){try{if("init"===o.action)return void await Y();if("get"===o.action)return void K(o.key);if("set"===o.action)return void S(o.key,o.value);if("migrate"===o.action)return void migrateConfig();if("setup"===o.action)return process.stdin.isTTY?void await $(o.key,o.value):(console.error("✗ `roll config setup` 需要交互式终端。"),console.error(" 非交互环境请改用 `roll config set <key> <value>` 或直接编辑 roll.config.yaml。"),console.error(" 查看配置说明:`roll config explain <key>`。"),void(process.exitCode=1));if("explain"===o.action)return void y(o.key);console.error(`✗ 未知操作: ${o.action}。可用: init, get, set, migrate, setup, explain`),process.exitCode=1}catch(o){const e=o instanceof Error?o.message:String(o);console.error(`✗ ${e}`),process.exitCode=1}}});const P={anthropic:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY",qwen:"DASHSCOPE_API_KEY",deepseek:"DEEPSEEK_API_KEY"};function w(o,e){const r=o?.trim();return r||e}function k(o){return l.some(e=>e===o)}function E(o){return k(o)?a[o]:c.llm.defaultModel}function x(o){return k(o)?P[o]:P.anthropic}function C({provider:o,model:e,apiKeyEnv:r}){return`llm:\n default-provider: ${o}\n default-model: ${e}\n providers:\n ${o}:\n api-key: \${${r}}\n\nask:\n confirm-threshold: 0.5\n\nagents:\n data-dir: ~/.roll-agent/agents\n`}async function j(){if(!process.stdin.isTTY){const[o,e,t]=r(0,"utf-8").split(/\r?\n/u),n=w(o,c.llm.defaultProvider);return{provider:n,model:w(e,E(n)),apiKeyEnv:w(t,x(n))}}const o=i({input:process.stdin,output:process.stderr}),e=w(await o.question(`默认 LLM provider (${l.join("/")}) [${c.llm.defaultProvider}]: `),c.llm.defaultProvider),t=E(e),n=w(await o.question(`默认 model [${t}]: `),t),s=x(e),a=w(await o.question(`API Key 环境变量名 [${s}]: `),s);return o.close(),{provider:e,model:n,apiKeyEnv:a}}async function Y(){const o=p()??n(t(),"roll.config.yaml");let c,a=!1;if(e(o)){c=g(r(o,"utf-8"));const e=f({configPath:o});switch(e.status){case"needs-migration":console.error(`⚠ 现有配置文件需要迁移: ${o}`),console.error(" 建议先运行 `roll config migrate`,再决定是否重新初始化。");break;case"invalid":a=!0,console.error(`⚠ 现有配置文件存在问题:\n${e.error.message}`)}if(console.error(`⚠ 配置文件已存在: ${o}`),!process.stdin.isTTY)throw new Error("非交互模式下不会覆盖现有配置文件,请手动删除后重试。");const t=i({input:process.stdin,output:process.stderr}),n=await t.question("是否覆盖?(y/N) ");if(t.close(),"y"!==n.toLowerCase())return void console.error("已取消。")}const l=C(await j()),d=new s({configPath:o}),u=void 0===c?d.saveYaml(l):a?d.replaceYamlForInit(l,c):d.saveYaml(l,c);console.log(`✓ 配置文件已创建: ${o}`),void 0!==u.backupPath&&console.log(`✓ 已备份原文件: ${u.backupPath}`)}export function migrateConfig(){const o=f();if("not-found"===o.status)throw new Error("未找到配置文件。请先运行 roll config init");if("valid"===o.status)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);if("invalid"===o.status)throw o.error;const e=u(o.raw,o.configPath),r=h(e);if(!r.ok){const o=r.issues.map(o=>` - ${o.message}`).join("\n");throw new Error(`配置无法自动迁移:\n${o}`)}if(!r.changed)return void console.log(`✓ 配置文件已是最新格式,无需迁移: ${o.configPath}`);const t=new s({configPath:o.configPath}).saveStructured(m(r.document),g(o.raw));console.log(`✓ 配置文件已迁移: ${o.configPath}`),void 0!==t.backupPath&&console.log(`✓ 已备份原文件: ${t.backupPath}`);for(const o of r.summary)console.log(` - ${o}`)}function K(o){const{config:e,configPath:r}=d();if(!o)return console.log(JSON.stringify(e,null,2)),void(r&&console.error(`(来源: ${r})`));const t=v(o.split("."));let n=e;for(const e of t){if("object"!=typeof n||null===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);n=n[e]}if(void 0===n)return console.error(`✗ 配置键 "${o}" 不存在`),void(process.exitCode=1);console.log("object"==typeof n?JSON.stringify(n,null,2):String(n))}function S(o,e){if(!o||void 0===e)return console.error("✗ 用法: roll config set <key> <value>"),console.error(" 示例: roll config set ask.confirm-threshold 0.5"),void(process.exitCode=1);const{configPath:r}=d();if(!r)return console.error("✗ 未找到配置文件。请先运行 roll config init"),void(process.exitCode=1);const t=v(o.split("."));if(0===t.length)return console.error("✗ 配置键不能为空"),void(process.exitCode=1);let n=e;"true"===e?n=!0:"false"===e?n=!1:/^\d+(\.\d+)?$/.test(e)&&(n=Number(e));const i=new s({configPath:r}),c=i.read();i.savePatches([{op:"set",path:t,value:n}],c.revision),console.log(`✓ ${o} = ${String(n)}`),console.error(` (已写入: ${r})`)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineCommand as e}from"citty";import{existsSync as s,mkdirSync as t,writeFileSync as n}from"node:fs";import{stringify as a}from"yaml";import{getAgentEnv as o,getAgentEnvFromAgentsConfig as r,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{collectBrowserConfigWarnings as l}from"../../config/browser-inspection.js";import{inspectConfigFile as c,loadAgentsConfig as u,loadConfig as g,parseConfigDocument as m,validateConfigText as f}from"../../config/loader.js";import{applyKnownConfigMigrations as p}from"../../config/migration.js";import{inspectAgentRuntimeEnvRequirements as d,summarizeAgentRuntimeEnvReport as h}from"../../config/runtime-env.js";import{inspectAgentRuntimeEnv as w}from"../../mcp/agent-diagnostics.js";import{cleanupOrphanAgentRuntimeMetadata as b,inspectManagedAgentRuntime as x}from"../../registry/process-manager.js";import{AgentStore as k}from"../../registry/store.js";const $={ok:"✓",warn:"⚠",fail:"✗"},y={applied:"✓",skipped:"•",failed:"✗"},S={major:22,minor:6,patch:0};function j(e){const s="Browser security (browser-use-agent)";if("unverified"===e.status)return{name:s,status:"warn",message:`未校验: ${e.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const t=e.payload.security;if(void 0===t)return{name:s,status:"warn",message:"browser_status.security 未返回,无法确认浏览器安全策略",fix:"升级并重启 browser-use-agent"};const n=e.payload.policyWarnings??[];return{name:s,status:n.length>0?"warn":"ok",message:I(t,e.payload.toolPolicy,n),...n.length>0?{fix:"检查 BROWSER_SECURITY_JSON 与 BROWSER_USE_POLICY_JSON 的组合;Boss 日常编排建议 browser actionPolicy=log"}:{}}}function v(e,s){const t=l(e,s);if(0!==t.length)return{name:"Browser config (browser.instances)",status:"warn",message:t.join(";"),fix:"在 roll.config.yaml 中配置 browser.default-instance,并移除 agents.env.browser-use-agent 下已废弃的 BROWSER_CDP_* / BROWSER_USER_DATA_DIR 等实例身份 env",details:{type:"browser-config",warnings:t}}}function P(e,s){const t="Browser runtime (browser-use-agent)",n=Object.keys(e.instances);if(0===n.length)return{name:t,status:"ok",message:"跳过(未配置 browser.instances,使用 legacy 单实例运行时)"};if("unverified"===s.status)return{name:t,status:"warn",message:`未校验: ${s.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const a=s.payload.instances??[];if(0===a.length)return{name:t,status:"warn",message:"browser_status.instances 未返回,无法确认浏览器实例运行态",fix:"升级并重启 browser-use-agent"};const o=new Set(a.map(e=>e.id)),r=n.filter(e=>!o.has(e)),i=a.filter(A),l=a.filter(e=>"missing"===e.tracking.source),c={type:"browser-runtime",declaredInstanceIds:n,...void 0!==e.defaultInstance?{defaultInstanceId:e.defaultInstance}:{},runtimeInstances:a,missingInstanceIds:r,unhealthyInstanceIds:i.map(e=>e.id),missingTrackingInstanceIds:l.map(e=>e.id)},u=r.length>0||i.length>0||l.length>0?"warn":"ok";return{name:t,status:u,details:c,message:[`declared=${n.join(",")}`,`runtime=${a.map(D).join(";")}`,...r.length>0?[`missing=${r.join(",")}`]:[],...l.length>0?[`trackingMissing=${l.map(e=>e.id).join(",")}`]:[]].join(" "),..."warn"===u?{fix:"检查 browser.instances、BROWSER_INSTANCES_JSON、CDP 端口和 profile 目录;必要时重启 browser-use-agent"}:{}}}function A(e){return!(e.cdp.versionReachable&&e.cdp.listReachable&&e.profile.exists&&e.profile.writable)}function D(e){return`${e.id}:cdp=${e.cdp.versionReachable&&e.cdp.listReachable?"ok":"warn"},profile=${e.profile.exists&&e.profile.writable?"ok":"warn"},tracking=${e.tracking.source}`}function I(e,s,t){const n=e.domainAllowlist.length>0?e.domainAllowlist.join(", "):"none",a=O(s),o=t.length>0?`; warnings=${t.map(e=>e.code).join(", ")}`:"";return`actionPolicy=${e.actionPolicy}; foregroundPolicy=${e.foregroundPolicy}; domainAllowlist=${n}; maxPageContentBytes=${String(e.maxPageContentBytes)}; maxSnapshotNodes=${String(e.maxSnapshotNodes)}; toolPolicy=${a}`+o}function O(e){if(void 0===e)return"unavailable";const s=Object.entries(e.tools),t=s.length>0?s.map(([e,s])=>`${e}:${s.policy}`).join(","):"none";return`approvalTtlMs=${String(e.approvalTtlMs)},tools=${t}`}export function isNodeVersionSupported(e){const s=/^(\d+)\.(\d+)\.(\d+)/.exec(e);if(!s)return!1;const t=Number(s[1]),n=Number(s[2]),a=Number(s[3]);return t>S.major||!(t<S.major)&&(n>S.minor||!(n<S.minor)&&a>=S.patch)}export default e({meta:{description:"诊断 Roll 配置、Agent 注册表和运行时状态"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1},"fix-plan":{type:"boolean",description:"输出可执行修复建议(不自动修改)",default:!1},fix:{type:"boolean",description:"执行安全自动修复:配置自动迁移、创建 dataDir、清理孤儿 runtime 元数据",default:!1}},async run({args:e}){const t=[],n=[],a=!0===e.fix;let l;const m=process.versions.node;t.push(isNodeVersionSupported(m)?{name:"Node.js 版本",status:"ok",message:`v${m}`}:{name:"Node.js 版本",status:"fail",message:`v${m} (需要 ≥22.6.0)`,fix:"升级 Node.js 到 22.6.0 或更高版本"});let f,p=c();if("needs-migration"===p.status&&a){const e=R(p);n.push(e),"applied"===e.status&&(p=c())}switch(p.status){case"not-found":t.push({name:"配置文件",status:"warn",message:"未找到,使用默认配置",fix:"运行 `roll config init` 生成显式配置文件"}),l=g().config;break;case"valid":t.push({name:"配置文件",status:"ok",message:p.configPath}),l=p.config;break;case"needs-migration":t.push({name:"配置文件",status:"warn",message:`${p.configPath} (需要迁移,运行 roll config migrate)`,fix:"运行 `roll config migrate`,确认备份文件后提交配置变更"});break;case"invalid":t.push({name:"配置文件",status:"fail",message:p.error.message,fix:"修正配置文件语法或运行 `roll config migrate` 处理 breaking schema change"})}try{f=u().agentsConfig}catch(e){t.push({name:"Agent 配置",status:"fail",message:e instanceof Error?e.message:String(e),fix:"检查 `roll.config.yaml` 的 `agents` 段;如配置版本过旧,运行 `roll config migrate`"})}if(l){const e=Object.keys(l.llm.providers);if(e.length>0){const s=e.filter(e=>{const s=l.llm.providers[e]?.apiKey??"";return s.startsWith("${")||0===s.length});s.length>0?t.push({name:"LLM Providers",status:"warn",message:`${e.join(", ")} (${s.join(", ")} API key 未设置)`,fix:`配置 ${s.map(e=>`llm.providers.${e}.apiKey`).join(", ")} 或对应环境变量`}):t.push({name:"LLM Providers",status:"ok",message:e.join(", ")})}else t.push({name:"LLM Providers",status:"warn",message:"未配置任何 provider",fix:"运行 `roll config init` 或手动配置 `llm.providers`"})}else"needs-migration"===p.status&&t.push({name:"LLM Providers",status:"warn",message:"配置需要迁移,跳过完整 LLM 配置校验",fix:"先运行 `roll config migrate`,再重新执行 `roll doctor`"});if(f){const e=f.dataDir;let c=s(e);if(!c&&a){const t=N(e);n.push(t),c=s(e)}t.push(c?{name:"Agent 数据目录",status:"ok",message:e}:{name:"Agent 数据目录",status:"warn",message:`${e} (不存在,首次 add 时创建)`,fix:"运行 `roll doctor --fix` 创建目录,或手动创建该目录"});const u=new k(f.dataDir).list();if(t.push({name:"已注册 Agent",status:u.length>0?"ok":"warn",message:u.length>0?`${String(u.length)} 个 (${u.map(e=>e.skill.name).join(", ")})`:"无",...0===u.length?{fix:"运行 `roll agent add <path|git-url>` 注册 Agent"}:{}}),l&&Object.keys(l.browser.instances).length>0&&!u.some(e=>"browser-use-agent"===e.skill.name)&&t.push({name:"Browser runtime (browser-use-agent)",status:"fail",message:"已配置 browser.instances,但 browser-use-agent 未注册",fix:"运行 `roll agent add agents/browser-use` 或安装并注册 browser-use-agent",details:{type:"browser-runtime",declaredInstanceIds:Object.keys(l.browser.instances),missingAgent:"browser-use-agent"}}),l&&Object.keys(l.browser.instances).length>0){const e=v(l.browser,o(l,"browser-use-agent"));e&&t.push(e)}for(const e of u){let s;const c=async()=>l?(s??=await w(e,{config:l}),s):{status:"unverified",reason:"connection-failed",message:"无法校验运行态: 配置未加载"};if("core-managed"===e.runtime.ownership){let s=x(e,f.dataDir);if(a&&s.issues.some(e=>"orphan-sidecar"===e.code)){const t=C(f.dataDir,e.skill.name);n.push(t),"applied"===t.status&&(s=x(e,f.dataDir))}(void 0!==s.pid||s.issues.length>0)&&t.push({name:`Agent runtime (${e.skill.name})`,status:s.issues.length>0?"warn":"ok",message:s.issues.length>0?s.issues.map(e=>e.message).join(";"):`PID ${String(s.pid)},runtime sidecar 与当前配置一致`,...s.issues.length>0?{fix:E(s.issues.map(e=>e.fix)).join(";")}:{}})}const u=i(e.skill.name,e.skill.env,f.env);if(!u){"browser-use-agent"===e.skill.name&&(s=await c(),l&&t.push(P(l.browser,s)),t.push(j(s)));continue}s=await c();const g=void 0!==l?o(l,e.skill.name):r(f,e.skill.name),m=d(u,g,s),p=h(m);t.push({name:`Agent 环境变量 (${e.skill.name})`,status:p.status,message:p.message,..."ok"===p.status?{}:{fix:`在 \`roll.config.yaml\` 的 \`agents.env.${e.skill.name}\` 或 shell 环境中配置缺失变量`}}),"browser-use-agent"===e.skill.name&&(l&&t.push(P(l.browser,s)),t.push(j(s)))}}const b=t.some(e=>"fail"===e.status),$=n.some(e=>"failed"===e.status),y=t.some(e=>"warn"===e.status),S=!0===e["fix-plan"]||a;if(e.json){const e=a?formatDoctorJsonOutput(t,{fixPlan:S,fixes:n}):formatDoctorChecksForJsonOutput(t,{fixPlan:S});return console.log(JSON.stringify(e,null,2)),void((b||$)&&(process.exitCode=1))}console.log("Roll Agent 系统诊断\n");for(const e of t)for(const s of formatDoctorCheckLines(e,{fixPlan:S}))console.log(s);if(a)if(console.log("\nFix 执行结果\n"),0===n.length)console.log(" ✓ 无可自动修复项");else for(const e of n)for(const s of formatDoctorFixLines(e))console.log(s);console.log(b||$?"\n存在问题,请修复后重试。":y?"\n存在警告,可按 fix plan 处理。":"\n系统状态正常。"),(b||$)&&(process.exitCode=1)}});export function formatDoctorCheckLines(e,s){const t=[` ${$[e.status]} ${e.name}: ${e.message}`];return s.fixPlan&&e.fix&&t.push(` fix: ${e.fix}`),t}export function formatDoctorChecksForJsonOutput(e,s){return s.fixPlan?e:e.map(({fix:e,...s})=>s)}export function formatDoctorJsonOutput(e,s){return{checks:formatDoctorChecksForJsonOutput(e,{fixPlan:s.fixPlan}),fixes:s.fixes}}export function formatDoctorFixLines(e){return[` ${y[e.status]} ${e.name}: ${e.message}`]}function R(e){if(!e.report.canAutoMigrate)return{name:"配置迁移",status:"skipped",message:"检测到需要人工处理的配置迁移项,未自动修改"};try{const s=m(e.raw,e.configPath),t=p(s);if(!t.ok)return{name:"配置迁移",status:"skipped",message:`无法自动迁移:${_(t.issues)}`};if(!t.changed)return{name:"配置迁移",status:"skipped",message:"配置文件已是最新格式"};const o=a(t.document,{lineWidth:0});f(o,e.configPath);const r=L(e.configPath);return n(r,e.raw,"utf-8"),n(e.configPath,o,"utf-8"),{name:"配置迁移",status:"applied",message:`已迁移 ${e.configPath},备份 ${r}`}}catch(e){return{name:"配置迁移",status:"failed",message:e instanceof Error?e.message:String(e)}}}function N(e){try{return t(e,{recursive:!0}),{name:"Agent 数据目录",status:"applied",message:`已创建 ${e}`}}catch(e){return{name:"Agent 数据目录",status:"failed",message:e instanceof Error?e.message:String(e)}}}function C(e,s){try{return b(e,s)?{name:`Agent runtime (${s})`,status:"applied",message:"已清理孤儿 runtime 元数据"}:{name:`Agent runtime (${s})`,status:"skipped",message:"检测到活动 PID,跳过 runtime 元数据清理"}}catch(e){return{name:`Agent runtime (${s})`,status:"failed",message:e instanceof Error?e.message:String(e)}}}function L(e){const s=new Date;return`${e}.bak.${[s.getFullYear().toString().padStart(4,"0"),(s.getMonth()+1).toString().padStart(2,"0"),s.getDate().toString().padStart(2,"0"),"-",s.getHours().toString().padStart(2,"0"),s.getMinutes().toString().padStart(2,"0"),s.getSeconds().toString().padStart(2,"0")].join("")}`}function _(e){return e.map(e=>e.message).join(";")}function E(e){return[...new Set(e)]}
|
|
1
|
+
import{defineCommand as e}from"citty";import{existsSync as s,mkdirSync as t}from"node:fs";import{ConfigApplicationService as n}from"../../config/application-service.js";import{createConfigRevision as o}from"../../config/document-store.js";import{getAgentEnv as a,getAgentEnvFromAgentsConfig as r,inspectAgentEnvRequirements as i}from"../../config/helpers.js";import{collectBrowserConfigWarnings as l}from"../../config/browser-inspection.js";import{inspectConfigFile as c,loadAgentsConfig as u,loadConfig as m,parseConfigDocument as g}from"../../config/loader.js";import{decodeFromYaml as f}from"../../config/key-codec.js";import{applyKnownConfigMigrations as p}from"../../config/migration.js";import{inspectAgentRuntimeEnvRequirements as d,summarizeAgentRuntimeEnvReport as h}from"../../config/runtime-env.js";import{inspectAgentRuntimeEnv as w}from"../../mcp/agent-diagnostics.js";import{cleanupOrphanAgentRuntimeMetadata as b,inspectManagedAgentRuntime as x}from"../../registry/process-manager.js";import{AgentStore as k}from"../../registry/store.js";const $={ok:"✓",warn:"⚠",fail:"✗"},y={applied:"✓",skipped:"•",failed:"✗"},j={major:22,minor:6,patch:0};function v(e){const s="Browser security (browser-use-agent)";if("unverified"===e.status)return{name:s,status:"warn",message:`未校验: ${e.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const t=e.payload.security;if(void 0===t)return{name:s,status:"warn",message:"browser_status.security 未返回,无法确认浏览器安全策略",fix:"升级并重启 browser-use-agent"};const n=e.payload.policyWarnings??[];return{name:s,status:n.length>0?"warn":"ok",message:O(t,e.payload.toolPolicy,n),...n.length>0?{fix:"检查 BROWSER_SECURITY_JSON 与 BROWSER_USE_POLICY_JSON 的组合;Boss 日常编排建议 browser actionPolicy=log"}:{}}}function P(e,s){const t=l(e,s);if(0!==t.length)return{name:"Browser config (browser.instances)",status:"warn",message:t.join(";"),fix:"在 roll.config.yaml 中配置 browser.default-instance,并移除 agents.env.browser-use-agent 下已废弃的 BROWSER_CDP_* / BROWSER_USER_DATA_DIR 等实例身份 env",details:{type:"browser-config",warnings:t}}}function S(e,s){const t="Browser runtime (browser-use-agent)",n=Object.keys(e.instances);if(0===n.length)return{name:t,status:"ok",message:"跳过(未配置 browser.instances,使用 legacy 单实例运行时)"};if("unverified"===s.status)return{name:t,status:"warn",message:`未校验: ${s.message}`,fix:"启动 browser-use-agent 后重新运行 `roll doctor`"};const o=s.payload.instances??[];if(0===o.length)return{name:t,status:"warn",message:"browser_status.instances 未返回,无法确认浏览器实例运行态",fix:"升级并重启 browser-use-agent"};const a=new Set(o.map(e=>e.id)),r=n.filter(e=>!a.has(e)),i=o.filter(A),l=o.filter(e=>"missing"===e.tracking.source),c={type:"browser-runtime",declaredInstanceIds:n,...void 0!==e.defaultInstance?{defaultInstanceId:e.defaultInstance}:{},runtimeInstances:o,missingInstanceIds:r,unhealthyInstanceIds:i.map(e=>e.id),missingTrackingInstanceIds:l.map(e=>e.id)},u=r.length>0||i.length>0||l.length>0?"warn":"ok";return{name:t,status:u,details:c,message:[`declared=${n.join(",")}`,`runtime=${o.map(I).join(";")}`,...r.length>0?[`missing=${r.join(",")}`]:[],...l.length>0?[`trackingMissing=${l.map(e=>e.id).join(",")}`]:[]].join(" "),..."warn"===u?{fix:"检查 browser.instances、BROWSER_INSTANCES_JSON、CDP 端口和 profile 目录;必要时重启 browser-use-agent"}:{}}}function A(e){return!(e.cdp.versionReachable&&e.cdp.listReachable&&e.profile.exists&&e.profile.writable)}function I(e){return`${e.id}:cdp=${e.cdp.versionReachable&&e.cdp.listReachable?"ok":"warn"},profile=${e.profile.exists&&e.profile.writable?"ok":"warn"},tracking=${e.tracking.source}`}function O(e,s,t){const n=e.domainAllowlist.length>0?e.domainAllowlist.join(", "):"none",o=D(s),a=t.length>0?`; warnings=${t.map(e=>e.code).join(", ")}`:"";return`actionPolicy=${e.actionPolicy}; foregroundPolicy=${e.foregroundPolicy}; domainAllowlist=${n}; maxPageContentBytes=${String(e.maxPageContentBytes)}; maxSnapshotNodes=${String(e.maxSnapshotNodes)}; toolPolicy=${o}`+a}function D(e){if(void 0===e)return"unavailable";const s=Object.entries(e.tools),t=s.length>0?s.map(([e,s])=>`${e}:${s.policy}`).join(","):"none";return`approvalTtlMs=${String(e.approvalTtlMs)},tools=${t}`}export function isNodeVersionSupported(e){const s=/^(\d+)\.(\d+)\.(\d+)/.exec(e);if(!s)return!1;const t=Number(s[1]),n=Number(s[2]),o=Number(s[3]);return t>j.major||!(t<j.major)&&(n>j.minor||!(n<j.minor)&&o>=j.patch)}export default e({meta:{description:"诊断 Roll 配置、Agent 注册表和运行时状态"},args:{json:{type:"boolean",description:"JSON 格式输出",default:!1},"fix-plan":{type:"boolean",description:"输出可执行修复建议(不自动修改)",default:!1},fix:{type:"boolean",description:"执行安全自动修复:配置自动迁移、创建 dataDir、清理孤儿 runtime 元数据",default:!1}},async run({args:e}){const t=[],n=[],o=!0===e.fix;let l;const g=process.versions.node;t.push(isNodeVersionSupported(g)?{name:"Node.js 版本",status:"ok",message:`v${g}`}:{name:"Node.js 版本",status:"fail",message:`v${g} (需要 ≥22.6.0)`,fix:"升级 Node.js 到 22.6.0 或更高版本"});let f,p=c();if("needs-migration"===p.status&&o){const e=R(p);n.push(e),"applied"===e.status&&(p=c())}switch(p.status){case"not-found":t.push({name:"配置文件",status:"warn",message:"未找到,使用默认配置",fix:"运行 `roll config init` 生成显式配置文件"}),l=m().config;break;case"valid":t.push({name:"配置文件",status:"ok",message:p.configPath}),l=p.config;break;case"needs-migration":t.push({name:"配置文件",status:"warn",message:`${p.configPath} (需要迁移,运行 roll config migrate)`,fix:"运行 `roll config migrate`,确认备份文件后提交配置变更"});break;case"invalid":t.push({name:"配置文件",status:"fail",message:p.error.message,fix:"修正配置文件语法或运行 `roll config migrate` 处理 breaking schema change"})}try{f=u().agentsConfig}catch(e){t.push({name:"Agent 配置",status:"fail",message:e instanceof Error?e.message:String(e),fix:"检查 `roll.config.yaml` 的 `agents` 段;如配置版本过旧,运行 `roll config migrate`"})}if(l){const e=Object.keys(l.llm.providers);if(e.length>0){const s=e.filter(e=>{const s=l.llm.providers[e]?.apiKey??"";return s.startsWith("${")||0===s.length});s.length>0?t.push({name:"LLM Providers",status:"warn",message:`${e.join(", ")} (${s.join(", ")} API key 未设置)`,fix:`配置 ${s.map(e=>`llm.providers.${e}.apiKey`).join(", ")} 或对应环境变量`}):t.push({name:"LLM Providers",status:"ok",message:e.join(", ")})}else t.push({name:"LLM Providers",status:"warn",message:"未配置任何 provider",fix:"运行 `roll config init` 或手动配置 `llm.providers`"})}else"needs-migration"===p.status&&t.push({name:"LLM Providers",status:"warn",message:"配置需要迁移,跳过完整 LLM 配置校验",fix:"先运行 `roll config migrate`,再重新执行 `roll doctor`"});if(f){const e=f.dataDir;let c=s(e);if(!c&&o){const t=N(e);n.push(t),c=s(e)}t.push(c?{name:"Agent 数据目录",status:"ok",message:e}:{name:"Agent 数据目录",status:"warn",message:`${e} (不存在,首次 add 时创建)`,fix:"运行 `roll doctor --fix` 创建目录,或手动创建该目录"});const u=new k(f.dataDir).list();if(t.push({name:"已注册 Agent",status:u.length>0?"ok":"warn",message:u.length>0?`${String(u.length)} 个 (${u.map(e=>e.skill.name).join(", ")})`:"无",...0===u.length?{fix:"运行 `roll agent add <path|git-url>` 注册 Agent"}:{}}),l&&Object.keys(l.browser.instances).length>0&&!u.some(e=>"browser-use-agent"===e.skill.name)&&t.push({name:"Browser runtime (browser-use-agent)",status:"fail",message:"已配置 browser.instances,但 browser-use-agent 未注册",fix:"运行 `roll agent add agents/browser-use` 或安装并注册 browser-use-agent",details:{type:"browser-runtime",declaredInstanceIds:Object.keys(l.browser.instances),missingAgent:"browser-use-agent"}}),l&&Object.keys(l.browser.instances).length>0){const e=P(l.browser,a(l,"browser-use-agent"));e&&t.push(e)}for(const e of u){let s;const c=async()=>l?(s??=await w(e,{config:l}),s):{status:"unverified",reason:"connection-failed",message:"无法校验运行态: 配置未加载"};if("core-managed"===e.runtime.ownership){let s=x(e,f.dataDir);if(o&&s.issues.some(e=>"orphan-sidecar"===e.code)){const t=C(f.dataDir,e.skill.name);n.push(t),"applied"===t.status&&(s=x(e,f.dataDir))}(void 0!==s.pid||s.issues.length>0)&&t.push({name:`Agent runtime (${e.skill.name})`,status:s.issues.length>0?"warn":"ok",message:s.issues.length>0?s.issues.map(e=>e.message).join(";"):`PID ${String(s.pid)},runtime sidecar 与当前配置一致`,...s.issues.length>0?{fix:_(s.issues.map(e=>e.fix)).join(";")}:{}})}const u=i(e.skill.name,e.skill.env,f.env);if(!u){"browser-use-agent"===e.skill.name&&(s=await c(),l&&t.push(S(l.browser,s)),t.push(v(s)));continue}s=await c();const m=void 0!==l?a(l,e.skill.name):r(f,e.skill.name),g=d(u,m,s),p=h(g);t.push({name:`Agent 环境变量 (${e.skill.name})`,status:p.status,message:p.message,..."ok"===p.status?{}:{fix:`在 \`roll.config.yaml\` 的 \`agents.env.${e.skill.name}\` 或 shell 环境中配置缺失变量`}}),"browser-use-agent"===e.skill.name&&(l&&t.push(S(l.browser,s)),t.push(v(s)))}}const b=t.some(e=>"fail"===e.status),$=n.some(e=>"failed"===e.status),y=t.some(e=>"warn"===e.status),j=!0===e["fix-plan"]||o;if(e.json){const e=o?formatDoctorJsonOutput(t,{fixPlan:j,fixes:n}):formatDoctorChecksForJsonOutput(t,{fixPlan:j});return console.log(JSON.stringify(e,null,2)),void((b||$)&&(process.exitCode=1))}console.log("Roll Agent 系统诊断\n");for(const e of t)for(const s of formatDoctorCheckLines(e,{fixPlan:j}))console.log(s);if(o)if(console.log("\nFix 执行结果\n"),0===n.length)console.log(" ✓ 无可自动修复项");else for(const e of n)for(const s of formatDoctorFixLines(e))console.log(s);console.log(b||$?"\n存在问题,请修复后重试。":y?"\n存在警告,可按 fix plan 处理。":"\n系统状态正常。"),(b||$)&&(process.exitCode=1)}});export function formatDoctorCheckLines(e,s){const t=[` ${$[e.status]} ${e.name}: ${e.message}`];return s.fixPlan&&e.fix&&t.push(` fix: ${e.fix}`),t}export function formatDoctorChecksForJsonOutput(e,s){return s.fixPlan?e:e.map(({fix:e,...s})=>s)}export function formatDoctorJsonOutput(e,s){return{checks:formatDoctorChecksForJsonOutput(e,{fixPlan:s.fixPlan}),fixes:s.fixes}}export function formatDoctorFixLines(e){return[` ${y[e.status]} ${e.name}: ${e.message}`]}function R(e){if(!e.report.canAutoMigrate)return{name:"配置迁移",status:"skipped",message:"检测到需要人工处理的配置迁移项,未自动修改"};try{const s=g(e.raw,e.configPath),t=p(s);if(!t.ok)return{name:"配置迁移",status:"skipped",message:`无法自动迁移:${L(t.issues)}`};if(!t.changed)return{name:"配置迁移",status:"skipped",message:"配置文件已是最新格式"};const a=new n({configPath:e.configPath}).saveStructured(f(t.document),o(e.raw));return{name:"配置迁移",status:"applied",message:void 0===a.backupPath?`已迁移 ${e.configPath}`:`已迁移 ${e.configPath},备份 ${a.backupPath}`}}catch(e){return{name:"配置迁移",status:"failed",message:e instanceof Error?e.message:String(e)}}}function N(e){try{return t(e,{recursive:!0}),{name:"Agent 数据目录",status:"applied",message:`已创建 ${e}`}}catch(e){return{name:"Agent 数据目录",status:"failed",message:e instanceof Error?e.message:String(e)}}}function C(e,s){try{return b(e,s)?{name:`Agent runtime (${s})`,status:"applied",message:"已清理孤儿 runtime 元数据"}:{name:`Agent runtime (${s})`,status:"skipped",message:"检测到活动 PID,跳过 runtime 元数据清理"}}catch(e){return{name:`Agent runtime (${s})`,status:"failed",message:e instanceof Error?e.message:String(e)}}}function L(e){return e.map(e=>e.message).join(";")}function _(e){return[...new Set(e)]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface RunRollUiOptions {
|
|
2
|
+
readonly configPath?: string;
|
|
3
|
+
readonly open: boolean;
|
|
4
|
+
}
|
|
5
|
+
interface RunRollUiDependencies {
|
|
6
|
+
readonly assetsDirectory?: string;
|
|
7
|
+
readonly openExternalUrl?: (url: string) => Promise<void>;
|
|
8
|
+
readonly waitForShutdown?: () => Promise<NodeJS.Signals>;
|
|
9
|
+
}
|
|
10
|
+
interface ExternalOpenCommand {
|
|
11
|
+
readonly command: string;
|
|
12
|
+
readonly args: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
declare const _default: import("citty").CommandDef<{
|
|
15
|
+
config: {
|
|
16
|
+
type: "string";
|
|
17
|
+
description: string;
|
|
18
|
+
required: false;
|
|
19
|
+
};
|
|
20
|
+
open: {
|
|
21
|
+
type: "boolean";
|
|
22
|
+
description: string;
|
|
23
|
+
default: true;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
export default _default;
|
|
27
|
+
export declare function runRollUi(options: RunRollUiOptions, dependencies?: RunRollUiDependencies): Promise<void>;
|
|
28
|
+
export declare function resolveRollUiConfigPath(explicitPath?: string): string;
|
|
29
|
+
export declare function resolveRollUiAssetsDirectory(moduleUrl?: string): string;
|
|
30
|
+
export declare function getExternalOpenCommand(url: string, platform?: NodeJS.Platform): ExternalOpenCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as o}from"node:child_process";import{existsSync as r}from"node:fs";import{homedir as n}from"node:os";import{dirname as e,resolve as t}from"node:path";import{fileURLToPath as i}from"node:url";import{defineCommand as s}from"citty";import{resolveConfigPath as c}from"../../config/loader.js";import{createFileSystemStaticAssetProvider as a,createRollUiRuntimeController as l,startRollUiServer as f}from"../../ui/index.js";import{log as m}from"../utils/output.js";export default s({meta:{description:"启动本地 Web 配置台"},args:{config:{type:"string",description:"配置文件路径(默认按 roll.config.yaml 发现链查找)",required:!1},open:{type:"boolean",description:"自动在默认浏览器打开(可用 --no-open 跳过)",default:!0}},async run({args:o}){try{await runRollUi({...void 0!==o.config?{configPath:o.config}:{},open:o.open})}catch(o){m.error(o instanceof Error?o.message:String(o)),process.exitCode=1}}});export async function runRollUi(o,r={}){const n=resolveRollUiConfigPath(o.configPath),e=r.assetsDirectory??resolveRollUiAssetsDirectory();d(e);const t=await f({controller:l({configPath:n}),staticAssets:a(e),onError:o=>{m.error(`配置台请求失败:${o instanceof Error?o.message:String(o)}`)}});try{m.info(`配置文件:${n}`),await p(t,o.open,r.openExternalUrl??u),m.info("配置台仅监听 127.0.0.1;按 Ctrl+C 停止。"),await(r.waitForShutdown??g)()}finally{await t.close(),m.success("Roll 配置台已停止。")}}export function resolveRollUiConfigPath(o){const r=c({...void 0!==o?{configPath:o}:{}});return t(r??t(n(),"roll.config.yaml"))}export function resolveRollUiAssetsDirectory(o=import.meta.url){const r=e(i(o));return o.endsWith(".ts")?t(r,"../../../dist/ui-assets"):t(r,"../../ui-assets")}export function getExternalOpenCommand(o,r=process.platform){return"darwin"===r?{command:"open",args:[o]}:"win32"===r?{command:"cmd.exe",args:["/d","/s","/c","start","",o]}:{command:"xdg-open",args:[o]}}async function p(o,r,n){if(r)try{return await n(o.url),void m.success(`配置台已在默认浏览器打开:${o.origin}`)}catch(o){m.warn(`无法自动打开浏览器:${o instanceof Error?o.message:String(o)}`)}console.log(o.url),m.info("请打开上方一次性链接;认证完成后 URL 中的 token 会立即移除并失效。")}function d(o){if(!r(t(o,"index.html")))throw new Error(`Roll UI 静态资源不存在:${o}。请先运行 \`pnpm --filter @roll-agent/core build\`。`)}function u(r){const n=getExternalOpenCommand(r);return new Promise((r,e)=>{const t=o(n.command,[...n.args],{stdio:"ignore",windowsHide:!0});t.once("error",e),t.once("close",o=>{0===o?r():e(new Error(`${n.command} 退出码:${String(o)}`))})})}function g(){return new Promise(o=>{const r=n=>{process.off("SIGINT",r),process.off("SIGTERM",r),o(n)};process.once("SIGINT",r),process.once("SIGTERM",r)})}
|
package/dist/cli/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var t=this&&this.__rewriteRelativeImportExtension||function(t,e){return"string"==typeof t&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(t,r,o,s,n){return r?e?".jsx":".js":!o||s&&n?o+s+"."+n.toLowerCase()+"js":t}):t};import{defineCommand as e,runMain as r}from"citty";import o from"chalk";import{checkForUpdate as s,getCurrentVersion as n}from"./utils/update-checker.js";import{resolveLogLevelFromArgv as a,setLogLevel as i}from"./utils/output.js";const c=n(),l=import.meta.url.endsWith(".ts")?"ts":"js";function u(e){const r=new URL(`./commands/${e}.${l}`,import.meta.url).href;return import(t(r)).then(t=>t.default)}const p=e({meta:{name:"roll",version:c,description:"花卷 Agent — 轻量级 Agent 编排系统"},args:{verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},subCommands:{agent:()=>u("agent"),run:()=>u("run"),ask:()=>u("ask"),chat:()=>u("chat"),config:()=>u("config"),setup:()=>u("setup"),skills:()=>u("skills"),browser:()=>u("browser"),doctor:()=>u("doctor"),update:()=>u("update")}}),m=s({allowNetwork:!1}).catch(()=>{});i(a(process.argv.slice(2))),r(p).then(()=>{m.then(t=>{t?.hasUpdate&&console.error(`\n${o.yellow("⬆")} roll ${o.green(`v${t.latest}`)} available (current: v${t.current}). Run ${o.cyan("roll update")} to upgrade.`)}).catch(()=>{})});
|
|
1
|
+
var t=this&&this.__rewriteRelativeImportExtension||function(t,e){return"string"==typeof t&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(t,r,o,s,n){return r?e?".jsx":".js":!o||s&&n?o+s+"."+n.toLowerCase()+"js":t}):t};import{defineCommand as e,runMain as r}from"citty";import o from"chalk";import{checkForUpdate as s,getCurrentVersion as n}from"./utils/update-checker.js";import{resolveLogLevelFromArgv as a,setLogLevel as i}from"./utils/output.js";const c=n(),l=import.meta.url.endsWith(".ts")?"ts":"js";function u(e){const r=new URL(`./commands/${e}.${l}`,import.meta.url).href;return import(t(r)).then(t=>t.default)}const p=e({meta:{name:"roll",version:c,description:"花卷 Agent — 轻量级 Agent 编排系统"},args:{verbose:{type:"boolean",alias:"v",description:"输出调试日志",default:!1}},subCommands:{agent:()=>u("agent"),run:()=>u("run"),ask:()=>u("ask"),chat:()=>u("chat"),config:()=>u("config"),setup:()=>u("setup"),skills:()=>u("skills"),browser:()=>u("browser"),ui:()=>u("ui"),doctor:()=>u("doctor"),update:()=>u("update")}}),m=s({allowNetwork:!1}).catch(()=>{});i(a(process.argv.slice(2))),r(p).then(()=>{m.then(t=>{t?.hasUpdate&&console.error(`\n${o.yellow("⬆")} roll ${o.green(`v${t.latest}`)} available (current: v${t.current}). Run ${o.cyan("roll update")} to upgrade.`)}).catch(()=>{})});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { YamlConfigDocumentStore, type ConfigPatch, type ConfigPath, type ConfigRevision } from "./document-store.ts";
|
|
2
|
+
import { type LoadConfigOptions } from "./loader.ts";
|
|
3
|
+
export declare const CONFIG_UI_SECRET_SENTINEL: "__ROLL_UI_KEEP_EXISTING_SECRET__";
|
|
4
|
+
export declare const CONFIG_ACTIVATION_KINDS: readonly ["next-command", "next-chat", "restart-agent", "manual"];
|
|
5
|
+
export type ConfigActivationKind = (typeof CONFIG_ACTIVATION_KINDS)[number];
|
|
6
|
+
export interface ConfigActivationEffect {
|
|
7
|
+
readonly kind: ConfigActivationKind;
|
|
8
|
+
readonly paths: readonly ConfigPath[];
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly description: string;
|
|
11
|
+
readonly agentName?: string;
|
|
12
|
+
readonly requiresConfirmation: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ConfigValidationIssue {
|
|
15
|
+
readonly path: string;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ConfigDiffLine {
|
|
19
|
+
readonly kind: "context" | "add" | "remove";
|
|
20
|
+
readonly text: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ConfigApplicationSnapshot {
|
|
23
|
+
readonly configPath: string;
|
|
24
|
+
readonly existed: boolean;
|
|
25
|
+
readonly revision: ConfigRevision;
|
|
26
|
+
readonly persisted: Readonly<Record<string, unknown>>;
|
|
27
|
+
readonly yaml: string;
|
|
28
|
+
readonly configuredSecretPaths: readonly ConfigPath[];
|
|
29
|
+
}
|
|
30
|
+
export interface ConfigApplicationPreview {
|
|
31
|
+
readonly snapshot: ConfigApplicationSnapshot;
|
|
32
|
+
readonly changed: boolean;
|
|
33
|
+
readonly changedPaths: readonly ConfigPath[];
|
|
34
|
+
readonly effects: readonly ConfigActivationEffect[];
|
|
35
|
+
readonly diff: readonly ConfigDiffLine[];
|
|
36
|
+
}
|
|
37
|
+
export interface ConfigApplicationSaveResult extends ConfigApplicationPreview {
|
|
38
|
+
readonly backupPath?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ConfigApplicationRecoverySaveResult {
|
|
41
|
+
readonly snapshot: ConfigApplicationSnapshot;
|
|
42
|
+
readonly changed: boolean;
|
|
43
|
+
readonly backupPath?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface ConfigApplicationServiceOptions extends LoadConfigOptions {
|
|
46
|
+
/** Extra Agent env names declared as secret in their env.yaml contract. */
|
|
47
|
+
readonly secretEnvNames?: readonly string[];
|
|
48
|
+
/** Exact Agent env visibility derived from registered env.yaml declarations. */
|
|
49
|
+
readonly agentEnvFields?: readonly ConfigAgentEnvFieldPolicy[];
|
|
50
|
+
/** Fail closed for undeclared Agent env fields, including newly added candidate values. */
|
|
51
|
+
readonly redactUnknownAgentEnv?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface ConfigAgentEnvFieldPolicy {
|
|
54
|
+
readonly agentName: string;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly secret: boolean;
|
|
57
|
+
}
|
|
58
|
+
export declare class ConfigApplicationValidationError extends Error {
|
|
59
|
+
readonly code: "config_validation_failed";
|
|
60
|
+
readonly issues: readonly ConfigValidationIssue[];
|
|
61
|
+
constructor(error: unknown);
|
|
62
|
+
}
|
|
63
|
+
export declare class ConfigApplicationService {
|
|
64
|
+
readonly configPath: string;
|
|
65
|
+
readonly store: YamlConfigDocumentStore;
|
|
66
|
+
private readonly isSecretPath;
|
|
67
|
+
constructor(options?: ConfigApplicationServiceOptions);
|
|
68
|
+
read(): ConfigApplicationSnapshot;
|
|
69
|
+
/** Load syntactically valid persisted data so a focused CLI setup can repair schema errors. */
|
|
70
|
+
readForRepair(): ConfigApplicationSnapshot;
|
|
71
|
+
previewPatches(patches: readonly ConfigPatch[], expectedRevision?: ConfigRevision): ConfigApplicationPreview;
|
|
72
|
+
savePatches(patches: readonly ConfigPatch[], expectedRevision?: ConfigRevision): ConfigApplicationSaveResult;
|
|
73
|
+
previewStructured(persisted: unknown, expectedRevision?: ConfigRevision): ConfigApplicationPreview;
|
|
74
|
+
saveStructured(persisted: unknown, expectedRevision?: ConfigRevision): ConfigApplicationSaveResult;
|
|
75
|
+
previewYaml(yaml: string, expectedRevision?: ConfigRevision): ConfigApplicationPreview;
|
|
76
|
+
saveYaml(yaml: string, expectedRevision?: ConfigRevision): ConfigApplicationSaveResult;
|
|
77
|
+
/**
|
|
78
|
+
* Recover an invalid existing file during an explicitly confirmed `roll config init` overwrite.
|
|
79
|
+
* The generated candidate is fully validated, but the old document is never parsed or returned
|
|
80
|
+
* to a browser. UI and ordinary CLI edits must continue to use preview/save methods above.
|
|
81
|
+
*/
|
|
82
|
+
replaceYamlForInit(yaml: string, expectedRevision: ConfigRevision): ConfigApplicationRecoverySaveResult;
|
|
83
|
+
private preparePatches;
|
|
84
|
+
private prepareStructured;
|
|
85
|
+
private prepareYaml;
|
|
86
|
+
private prepareDocumentPreview;
|
|
87
|
+
private commitPrepared;
|
|
88
|
+
}
|
|
89
|
+
export declare function planConfigActivation(changedPaths: readonly ConfigPath[]): readonly ConfigActivationEffect[];
|
|
90
|
+
export declare function createConfigPatches(before: unknown, after: unknown, path?: ConfigPath): readonly ConfigPatch[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{homedir as t}from"node:os";import{resolve as e}from"node:path";import{stringify as r}from"yaml";import{DEFAULT_CONFIG as n}from"./defaults.js";import{ConfigRevisionConflictError as i,YamlConfigDocumentStore as o,applyPatchesToYamlText as s}from"./document-store.js";import{CONFIG_KEY_CODEC as a,decodeFromYaml as c,encodePathToYaml as p,encodeToYaml as h,kebabToCamel as u}from"./key-codec.js";import{parseConfigDocument as f,resolveConfigPath as l,validateConfigText as d}from"./loader.js";import{detectKnownConfigMigrations as g,formatConfigMigrationError as m}from"./migration.js";import{isRollConfigSecretPath as w,isSecretConfigValue as v}from"./secret-policy.js";export const CONFIG_UI_SECRET_SENTINEL="__ROLL_UI_KEEP_EXISTING_SECRET__";export const CONFIG_ACTIVATION_KINDS=["next-command","next-chat","restart-agent","manual"];export class ConfigApplicationValidationError extends Error{code="config_validation_failed";issues;constructor(t){const e=t instanceof Error?t:new Error(String(t));super(e.message,{cause:e}),this.name="ConfigApplicationValidationError",this.issues=D(e.message)}}export class ConfigApplicationService{configPath;store;isSecretPath;constructor(r={}){this.configPath=l(r)??e(t(),"roll.config.yaml"),this.store=new o(this.configPath,Y());const n=new Set(r.secretEnvNames??[]),i=new Map((r.agentEnvFields??[]).map(t=>[F(t.agentName,t.name),t.secret]));this.isSecretPath=t=>V(t,n,i,r.redactUnknownAgentEnv??!1)}read(){const t=this.store.read();return y(t.raw,t.configPath),P(t,this.isSecretPath)}readForRepair(){const t=this.store.read(),e=f(t.raw,t.configPath),r=g(e);if(r.needsMigration)throw new ConfigApplicationValidationError(new Error(m(t.configPath,r)));return P(t,this.isSecretPath)}previewPatches(t,e){return this.preparePatches(t,e).publicPreview}savePatches(t,e){return this.commitPrepared(this.preparePatches(t,e))}previewStructured(t,e){return this.prepareStructured(t,e).publicPreview}saveStructured(t,e){return this.commitPrepared(this.prepareStructured(t,e))}previewYaml(t,e){return this.prepareYaml(t,e).publicPreview}saveYaml(t,e){return this.commitPrepared(this.prepareYaml(t,e))}replaceYamlForInit(t,e){y(t,this.configPath);const r=this.store.replaceRawForRecovery(t,e);return{snapshot:P(r,this.isSecretPath),changed:r.changed,...void 0!==r.backupPath?{backupPath:r.backupPath}:{}}}preparePatches(t,e){const r=t.map(S),n=this.store.previewPatches(r,e);return this.prepareDocumentPreview(n)}prepareStructured(t,e){if(!M(t))throw new ConfigApplicationValidationError(new Error("配置内容必须是一个 JSON/YAML object。"));const r=this.store.read();if(void 0!==e&&e!==r.revision)throw new i(e,r.revision);const n=O(t,A(r.persisted),[],this.isSecretPath),o=h(n);if(!M(o))throw new ConfigApplicationValidationError(new Error("编码后的配置必须是 object。"));const s=this.store.previewObject(o,r.revision);return this.prepareDocumentPreview(s)}prepareYaml(t,e){const r=this.store.read();if(void 0!==e&&e!==r.revision)throw new i(e,r.revision);let n,o=t;try{n=f(o,this.configPath)}catch(t){throw new ConfigApplicationValidationError(t)}const c=[];let p;j(n,A(r.persisted),[],[],c,this.isSecretPath,a),c.length>0&&(o=s(o,this.configPath,c));try{p=this.store.previewRaw(o,r.revision)}catch(t){throw new ConfigApplicationValidationError(t)}return this.prepareDocumentPreview(p)}prepareDocumentPreview(t){y(t.raw,this.configPath);const e=_(A(f(t.previousRaw,this.configPath)),A(t.persisted)),r=P(t,this.isSecretPath),n=f(t.previousRaw,this.configPath),i=P({configPath:t.configPath,existed:t.existed,raw:t.previousRaw,revision:t.previousRevision,persisted:n},this.isSecretPath);return{documentPreview:t,publicPreview:{snapshot:r,changed:t.changed,changedPaths:e,effects:planConfigActivation(e),diff:U(i.yaml,r.yaml)}}}commitPrepared(t){const e=this.store.commit(t.documentPreview,{backup:!0}),r=P(e,this.isSecretPath);return{...t.publicPreview,snapshot:r,...void 0!==e.backupPath?{backupPath:e.backupPath}:{}}}}export function planConfigActivation(t){const e=new Map;for(const r of t){const t=E(r),n=`${t.kind}:${t.agentName??""}:${t.title}`,i=e.get(n);void 0!==i?i.paths.push(r):e.set(n,{...t,paths:[r]})}return[...e.values()].map(t=>({...t,paths:t.paths}))}export function createConfigPatches(t,e,r=[]){if(M(t)&&M(e)){const n=[],i=new Set([...Object.keys(t),...Object.keys(e)]);for(const o of i)o in e?o in t?n.push(...createConfigPatches(t[o],e[o],[...r,o])):n.push({op:"set",path:[...r,o],value:e[o]}):n.push({op:"delete",path:[...r,o]});return n}if(q(t,e))return[];if(0===r.length)throw new ConfigApplicationValidationError(new Error("配置根节点必须保持为 object。"));return[{op:"set",path:r,value:e}]}function E(t){const[e,r,n]=t;return"agents"===e&&"dataDir"===r?{kind:"manual",title:"Agent 数据目录需要人工迁移",description:"保存不会搬迁旧 PID、日志或注册数据;请先停止 Agent,再人工迁移目录。",requiresConfirmation:!0}:"agents"===e&&"env"===r&&"string"==typeof n?{kind:"restart-agent",title:`重启 ${n}`,description:"运行中的 core-managed Agent 需要重启;已停止的 Agent 保持停止。",agentName:n,requiresConfirmation:!0}:"browser"===e?{kind:"restart-agent",title:"重启 browser-use-agent",description:"浏览器实例声明在 Agent 启动时注入;重启 Agent 后 Chrome 仍会在首次工具调用时懒启动。",agentName:"browser-use-agent",requiresConfirmation:!0}:"runtime"===e||"skills"===e?{kind:"next-chat",title:"新会话生效",description:"当前 roll chat 会话保持原配置,新会话会重新加载。",requiresConfirmation:!1}:{kind:"next-command",title:"后续命令生效",description:"后续 Roll 命令会重新加载该配置。",requiresConfirmation:!1}}function P(t,e){const r=A(t.persisted),n=N(t.persisted,[],[],e,a),i=n.map(t=>t.path),o=new Set(i.map(L)),c=I(r,[],o);if(!M(c))throw new ConfigApplicationValidationError(new Error("脱敏后的配置必须是 object。"));const p=n.filter(t=>{const e=G(r,t.path);return!("string"==typeof e&&$(e))}).map(t=>({op:"set",path:t.persistedPath,value:CONFIG_UI_SECRET_SENTINEL})),h=0===p.length?t.raw:s(t.raw,t.configPath,p);return{configPath:t.configPath,existed:t.existed,revision:t.revision,persisted:c,yaml:h,configuredSecretPaths:i}}function y(t,e){try{d(t,e)}catch(t){throw new ConfigApplicationValidationError(t)}}function A(t){const e=c(t);if(!M(e))throw new ConfigApplicationValidationError(new Error("持久化配置必须是 object。"));return e}function S(t){const e=C(t.path);return"set"===t.op?{op:"set",path:e,value:k(t.value,t.path)}:{op:"delete",path:e}}function C(t){return p(t.map(String)).map((e,r)=>"number"==typeof t[r]?t[r]:e)}function k(t,e){if(!M(t)&&!Array.isArray(t))return t;const r=b({},e,t),n=h(r);return M(n)?G(n,C(e)):t}function b(t,e,r){let n=t;for(const[t,i]of e.entries()){const o=String(i);if(t===e.length-1){n[o]=r;break}const s={};n[o]=s,n=s}return t}function _(t,e,r=[]){const n=[],i=new Set([...Object.keys(t),...Object.keys(e)]);for(const o of i){const i=t[o],s=e[o],a=[...r,o];M(i)||M(s)?n.push(..._(M(i)?i:{},M(s)?s:{},a)):q(i,s)||n.push(a)}return n}function N(t,e,r,n,i){const o=[];if(Array.isArray(t)){const s="array"===i?.kind?i.item:i;return t.forEach((t,i)=>{const a=[...e,i],c=[...r,i],p=R(t,s);v(a,p,n)?o.push({path:a,persistedPath:c}):(Array.isArray(t)||M(t))&&o.push(...N(t,a,c,n,s))}),o}if(!M(t))return o;for(const[s,a]of Object.entries(t)){const t=x(s,a,i),c=[...e,t.key],p=[...r,s];v(c,t.value,n)?o.push({path:c,persistedPath:p}):(Array.isArray(a)||M(a))&&o.push(...N(a,c,p,n,t.node))}return o}function I(t,e,r){return r.has(L(e))?"string"==typeof t&&$(t)?t:CONFIG_UI_SECRET_SENTINEL:"string"==typeof t?t:Array.isArray(t)?t.map((t,n)=>I(t,[...e,n],r)):M(t)?Object.fromEntries(Object.entries(t).map(([t,n])=>[t,I(n,[...e,t],r)])):t}function O(t,e,r,n){if(t===CONFIG_UI_SECRET_SENTINEL)return T(e,r,n),e;if(Array.isArray(t)){const i=Array.isArray(e)?e:[];return t.map((t,e)=>O(t,i[e],[...r,e],n))}if(!M(t))return t;const i=M(e)?e:{};return Object.fromEntries(Object.entries(t).map(([t,e])=>[t,O(e,i[t],[...r,t],n)]))}function j(t,e,r,n,i,o,s){if(Array.isArray(t)){const a="array"===s?.kind?s.item:s;return void t.forEach((t,s)=>{const c=[...r,s],p=[...n,s],h=G(e,c);if(t===CONFIG_UI_SECRET_SENTINEL)return T(h,c,o),void i.push({op:"set",path:p,value:h});j(t,e,c,p,i,o,a)})}if(M(t))for(const[a,c]of Object.entries(t)){const t=x(a,c,s),p=[...r,t.key],h=[...n,a],u=G(e,p);c!==CONFIG_UI_SECRET_SENTINEL?j(c,e,p,h,i,o,t.node):(T(u,p,o),i.push({op:"set",path:h,value:u}))}}function x(t,e,r){if("object"===r?.kind){const n=u(t),i=r.fields[n];return{key:n,value:R(e,i),node:i}}return"record"===r?.kind?{key:t,value:R(e,r.value),node:r.value}:{key:t,value:e,node:void 0}}function R(t,e){return void 0===e||"leaf"===e.kind?t:c(t,e)}function T(t,e,r){if(void 0===t||t===CONFIG_UI_SECRET_SENTINEL||!v(e,t,r))throw new ConfigApplicationValidationError(new Error(`无法在原路径恢复敏感配置: ${K(e)}`))}function V(t,e=new Set,r=new Map,n=!1){if(w(t))return!0;if(4===t.length&&"agents"===t[0]&&"env"===t[1]){const i=t[2],o=t[3];if("string"!=typeof i||"string"!=typeof o)return!1;const s=F(i,o);return r.has(s)?!0===r.get(s):!(!e.has(o)&&!n)||(/(?:^|_)(?:TOKEN|API_KEY|SECRET|PASSWORD|PRIVATE_KEY)(?:_|$)/u.test(o)||o.endsWith("_WEBHOOK"))}return!1}function F(t,e){return`${t}\0${e}`}function $(t){return/^\$\{[^{}]+\}$/u.test(t)}function L(t){return JSON.stringify(t)}function G(t,e){let r=t;for(const t of e)if(Array.isArray(r)&&"number"==typeof t)r=r[t];else{if(!M(r))return;r=r[String(t)]}return r}function U(t,e){if(t===e)return t.split("\n").map(t=>({kind:"context",text:t}));const r=t.split("\n"),n=e.split("\n");let i=0;for(;i<r.length&&i<n.length&&r[i]===n[i];)i+=1;let o=0;for(;o<r.length-i&&o<n.length-i&&r[r.length-o-1]===n[n.length-o-1];)o+=1;return[...r.slice(0,i).map(t=>({kind:"context",text:t})),...r.slice(i,r.length-o).map(t=>({kind:"remove",text:t})),...n.slice(i,n.length-o).map(t=>({kind:"add",text:t})),...r.slice(r.length-o).map(t=>({kind:"context",text:t}))]}function Y(){const t={llm:n.llm,ask:n.ask,agents:n.agents};return r(h(t),{lineWidth:0})}function D(t){const e=t.split("\n").map(t=>t.match(/^\s*-\s+([^:]+):\s*(.+)$/u)).filter(t=>null!==t).map(e=>({path:e[1]??"",message:e[2]??t}));return e.length>0?e:[{path:"",message:t}]}function K(t){return t.map(String).join(" / ")}function q(t,e){if(Object.is(t,e))return!0;if(Array.isArray(t)&&Array.isArray(e))return t.length===e.length&&t.every((t,r)=>q(t,e[r]));if(!M(t)||!M(e))return!1;const r=Object.keys(t),n=Object.keys(e);return r.length===n.length&&r.every(r=>r in e&&q(t[r],e[r]))}function M(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
|