@xingjisoft/neutrino 0.6.0 → 0.7.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/README.md +6 -1
- package/assets/fast-planner.txt +57 -0
- package/assets/fast-replanner.txt +28 -0
- package/dist/agents.d.ts +25 -0
- package/dist/app.d.ts +206 -0
- package/dist/app.js +15 -0
- package/dist/background.d.ts +96 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +40 -3
- package/dist/browser.d.ts +18 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/colors.d.ts +29 -0
- package/dist/compact.d.ts +52 -0
- package/dist/config.d.ts +221 -0
- package/dist/config.js +22 -7
- package/dist/cwd.d.ts +19 -0
- package/dist/fast/artifacts.d.ts +26 -0
- package/dist/fast/artifacts.js +109 -0
- package/dist/fast/engine.d.ts +78 -0
- package/dist/fast/engine.js +657 -0
- package/dist/fast/evidence.d.ts +21 -0
- package/dist/fast/evidence.js +68 -0
- package/dist/fast/planner.d.ts +10 -0
- package/dist/fast/planner.js +51 -0
- package/dist/fast/pool.d.ts +17 -0
- package/dist/fast/pool.js +78 -0
- package/dist/fast/scheduler.d.ts +54 -0
- package/dist/fast/scheduler.js +383 -0
- package/dist/fast/types.d.ts +100 -0
- package/dist/fast/types.js +21 -0
- package/dist/fast/worker.d.ts +23 -0
- package/dist/fast/worker.js +185 -0
- package/dist/header.d.ts +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/llm/approval.d.ts +204 -0
- package/dist/llm/approval.js +14 -3
- package/dist/llm/client.d.ts +118 -0
- package/dist/llm/client.js +57 -8
- package/dist/llm/codeExecutor.d.ts +14 -0
- package/dist/llm/codemode.d.ts +15 -0
- package/dist/llm/context.d.ts +29 -0
- package/dist/llm/context.js +17 -4
- package/dist/llm/diagnostics.d.ts +91 -0
- package/dist/llm/diagnostics.js +242 -0
- package/dist/llm/plan.d.ts +148 -0
- package/dist/llm/retry.d.ts +80 -0
- package/dist/llm/risk.d.ts +21 -0
- package/dist/llm/risk.js +7 -0
- package/dist/llm/runtime.d.ts +189 -0
- package/dist/llm/runtime.js +178 -24
- package/dist/llm/search.d.ts +2 -0
- package/dist/llm/searchNetwork.d.ts +8 -0
- package/dist/llm/subagent.d.ts +112 -0
- package/dist/llm/subagent.js +25 -12
- package/dist/llm/tools.d.ts +20 -0
- package/dist/llm/tools.js +4 -1
- package/dist/llm/workspace.d.ts +6 -0
- package/dist/llm/workspace.js +271 -0
- package/dist/markdown.d.ts +58 -0
- package/dist/migration.d.ts +8 -0
- package/dist/persistence.d.ts +14 -0
- package/dist/plugins/catalog.d.ts +20 -0
- package/dist/plugins/catalog.js +45 -0
- package/dist/plugins/cli.d.ts +1 -0
- package/dist/plugins/cli.js +106 -0
- package/dist/plugins/host.d.ts +121 -0
- package/dist/plugins/host.js +450 -0
- package/dist/plugins/manager.d.ts +24 -0
- package/dist/plugins/manager.js +288 -0
- package/dist/plugins/package.d.ts +5 -0
- package/dist/plugins/package.js +53 -0
- package/dist/plugins/runner.d.ts +1 -0
- package/dist/plugins/runner.js +227 -0
- package/dist/plugins/session.d.ts +12 -0
- package/dist/plugins/session.js +97 -0
- package/dist/plugins/terminal.d.ts +22 -0
- package/dist/plugins/terminal.js +33 -0
- package/dist/plugins/types.d.ts +178 -0
- package/dist/plugins/types.js +6 -0
- package/dist/plugins/validation.d.ts +14 -0
- package/dist/plugins/validation.js +174 -0
- package/dist/plugins/web-sdk.d.ts +58 -0
- package/dist/plugins/web-sdk.js +1 -0
- package/dist/pro/agents.d.ts +34 -0
- package/dist/pro/agents.js +1 -1
- package/dist/pro/config.d.ts +75 -0
- package/dist/pro/config.js +3 -2
- package/dist/pro/preflight.d.ts +21 -0
- package/dist/sanitize.d.ts +34 -0
- package/dist/screens/agentConversation.d.ts +28 -0
- package/dist/screens/agentHistory.d.ts +25 -0
- package/dist/screens/agentSelect.d.ts +16 -0
- package/dist/screens/chat.d.ts +292 -0
- package/dist/screens/chat.js +179 -65
- package/dist/screens/history.d.ts +23 -0
- package/dist/screens/modelSelect.d.ts +29 -0
- package/dist/screens/plugins.d.ts +29 -0
- package/dist/screens/plugins.js +196 -0
- package/dist/screens/settings.d.ts +42 -0
- package/dist/screens/start.d.ts +43 -0
- package/dist/screens/start.js +25 -17
- package/dist/screens/updatePrompt.d.ts +41 -0
- package/dist/selection.d.ts +75 -0
- package/dist/state.d.ts +26 -0
- package/dist/terminal/buffer.d.ts +47 -0
- package/dist/terminal/input.d.ts +70 -0
- package/dist/terminal/renderer.d.ts +43 -0
- package/dist/terminal/restore.d.ts +41 -0
- package/dist/terminal/screen.d.ts +57 -0
- package/dist/tray.d.ts +14 -0
- package/dist/ui.d.ts +123 -0
- package/dist/update.d.ts +71 -0
- package/dist/updateWatch.d.ts +43 -0
- package/dist/web/approvalQueue.d.ts +8 -0
- package/dist/web/approvalQueue.js +59 -0
- package/dist/web/auth.d.ts +14 -0
- package/dist/web/clientState.d.ts +16 -0
- package/dist/web/folderPicker.d.ts +34 -0
- package/dist/web/folderPicker.js +544 -0
- package/dist/web/hub.d.ts +213 -0
- package/dist/web/hub.js +14 -2
- package/dist/web/localApi.d.ts +11 -0
- package/dist/web/localApi.js +260 -0
- package/dist/web/plugins.d.ts +8 -0
- package/dist/web/plugins.js +64 -0
- package/dist/web/server.d.ts +81 -0
- package/dist/web/server.js +127 -19
- package/dist/web/service.d.ts +7 -0
- package/dist/web/service.js +1 -0
- package/dist/web/session.d.ts +176 -0
- package/dist/web/session.js +170 -21
- package/dist/web/settings.d.ts +65 -0
- package/dist/web/terminalBridge.d.ts +15 -0
- package/dist/web/terminalBridge.js +8 -2
- package/dist/width.d.ts +6 -0
- package/docs/plugins/README.md +126 -0
- package/docs/plugins/agent-sdk.md +92 -0
- package/docs/plugins/architecture.md +64 -0
- package/docs/plugins/compatibility.md +34 -0
- package/docs/plugins/development.md +55 -0
- package/docs/plugins/manifest.md +59 -0
- package/docs/plugins/manifest.schema.json +286 -0
- package/docs/plugins/security.md +53 -0
- package/docs/plugins/skills-prompts.md +69 -0
- package/docs/plugins/ui-sdk.md +109 -0
- package/examples/local-api.py +105 -0
- package/examples/plugins/cards/card.html +6 -0
- package/examples/plugins/cards/neutrino.plugin.json +42 -0
- package/examples/plugins/composer/editor.html +10 -0
- package/examples/plugins/composer/neutrino.plugin.json +10 -0
- package/examples/plugins/function-tools/agent.js +32 -0
- package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/panel.html +6 -0
- package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
- package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
- package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
- package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
- package/examples/plugins/theme/neutrino.plugin.json +12 -0
- package/package.json +66 -42
- package/web/LOCAL-API.md +65 -0
- package/web/README.md +30 -8
- package/web/app.js +256 -67
- package/web/i18n.js +18 -3
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +12 -3
- package/web/turns.js +7 -8
package/web/plugins.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Plugin views are opaque-origin iframes. The host owns permissions and conversation state.
|
|
2
|
+
export function createPluginUI(host) {
|
|
3
|
+
const zh = () => String(document.documentElement.lang).startsWith("zh");
|
|
4
|
+
const t = (en,cn) => zh() ? cn : en;
|
|
5
|
+
const node = (tag,className,text) => { const el=document.createElement(tag); if(className) el.className=className; if(text!==undefined) el.textContent=text; return el; };
|
|
6
|
+
const button = (text,action,className="control") => { const el=node("button",className,text); el.type="button"; el.addEventListener("click",action); return el; };
|
|
7
|
+
// getRandomValues also works on the LAN HTTP console, where randomUUID is unavailable.
|
|
8
|
+
const randomToken=()=>Array.from(crypto.getRandomValues(new Uint8Array(16)),byte=>byte.toString(16).padStart(2,"0")).join("");
|
|
9
|
+
let catalog={ ui:[],theme:{},skills:[],providers:{},errors:[] }, listing=null, scopeWorkspace=false;
|
|
10
|
+
const identityOf=c=>`${host.profile}:${c.identity||""}:${c.cwd||""}`;
|
|
11
|
+
let contextKey=identityOf(host.context()), catalogScope=null, activeTab="", selectedTab=null, disposed=false, timer=null, syncTimer=null;
|
|
12
|
+
let lastDraft="",draftRevision=0,loading=false;
|
|
13
|
+
let chromeSignature="";
|
|
14
|
+
const frames=new Map(), appliedTheme=new Map(), failed=new Set(), assets=new Map();
|
|
15
|
+
const dialog=node("dialog","sheet-dialog plugin-dialog"); dialog.setAttribute("aria-label","Plugins / 插件");
|
|
16
|
+
const sheet=node("div","sheet"), header=node("header","sheet-header"), title=node("h2","","Plugins / 插件");
|
|
17
|
+
const feedback=node("p","form-feedback"); feedback.setAttribute("role","status");
|
|
18
|
+
const body=node("div","sheet-body plugin-manager-body");
|
|
19
|
+
header.append(title,button("×",()=>dialog.close(),"icon-btn")); sheet.append(header,body,feedback); dialog.append(sheet); document.body.append(dialog);
|
|
20
|
+
header.querySelector("button").setAttribute("aria-label","Close plugins / 关闭插件");
|
|
21
|
+
const open=button("◈ "+t("Plugins","插件"),()=>{ if(!dialog.open)dialog.showModal(); void loadManager(); },"workspace-button plugin-open");
|
|
22
|
+
open.id="plugins-open"; document.querySelector(".sidebar-tools")?.append(open);
|
|
23
|
+
const menu=node("div","plugin-menu"),tabs=node("nav","plugin-tabs"),sidebar=node("div","plugin-sidebar"),toolbar=node("div","plugin-toolbar"),headerActions=node("div","plugin-header-actions"),panel=node("section","plugin-panel hidden");
|
|
24
|
+
tabs.setAttribute("aria-label","Plugin tabs / 插件选项卡");
|
|
25
|
+
document.querySelector(".sidebar-tools")?.append(menu);
|
|
26
|
+
document.getElementById("sidebar")?.append(sidebar);
|
|
27
|
+
document.getElementById("main-header")?.after(tabs,panel);
|
|
28
|
+
document.getElementById("main-header")?.append(headerActions);
|
|
29
|
+
document.querySelector(".composer-inner")?.prepend(toolbar);
|
|
30
|
+
|
|
31
|
+
function scopeQuery(forManager=false) { const cwd=host.context().cwd; return cwd&&(!forManager||scopeWorkspace)?`?cwd=${encodeURIComponent(cwd)}`:""; }
|
|
32
|
+
async function request(path,body) { const response=await host.api(path,body===undefined?undefined:{ method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body) }); const value=await response.json(); if(!response.ok)throw new Error(value.error||`HTTP ${response.status}`); return value; }
|
|
33
|
+
function error(e) { feedback.textContent=e.message; feedback.classList.add("error"); }
|
|
34
|
+
async function mutate(value) {
|
|
35
|
+
try { feedback.textContent=""; listing=await request(`/api/plugins${scopeQuery(true)}`,{...value,revision:listing?.revision}); assets.clear(); failed.clear(); renderManager(); await refresh(true); }
|
|
36
|
+
catch(e) { error(e); }
|
|
37
|
+
}
|
|
38
|
+
async function loadManager() { try { listing=await request(`/api/plugins${scopeQuery(true)}`); renderManager(); } catch(e){error(e);} }
|
|
39
|
+
function field(label,input) { const el=node("label","plugin-field"); el.append(node("span","",label),input); return el; }
|
|
40
|
+
function renderManager() {
|
|
41
|
+
if(!listing)return;
|
|
42
|
+
body.replaceChildren();
|
|
43
|
+
const scope=node("select"); scope.append(new Option(t("This profile","当前模式"),"profile"),new Option(t("Current project","当前项目"),"workspace")); scope.value=scopeWorkspace?"workspace":"profile";
|
|
44
|
+
scope.addEventListener("change",()=>{scopeWorkspace=scope.value==="workspace";void loadManager();});
|
|
45
|
+
if(!host.context().cwd)scope.options[1].disabled=true;
|
|
46
|
+
body.append(field(t("Enable and configure for","启用与配置范围"),scope));
|
|
47
|
+
if(listing.safeMode)body.append(node("p","plugin-warning",t("Safe mode is active. External plugins are not loaded.","安全模式已开启,外部插件不会加载。")));
|
|
48
|
+
const install=node("form","plugin-install"),source=node("input","path-input");source.placeholder=t("Plugin directory on this computer","此电脑上的插件目录");source.setAttribute("aria-label",source.placeholder);source.required=true;
|
|
49
|
+
const installButton=button(t("Install","安装"),()=>{},"control primary");installButton.type="submit";
|
|
50
|
+
install.addEventListener("submit",e=>{e.preventDefault();void mutate({action:"install",source:source.value});});install.append(source,installButton);body.append(install);
|
|
51
|
+
body.append(node("p","settings-note",t("Installation is shared. Enablement and configuration follow the selected scope. Running turns keep their current versions.","安装包共用,启用状态和配置按所选范围保存;正在执行的回合继续使用原版本。")));
|
|
52
|
+
const search=node("input","path-input");search.placeholder=t("Filter plugins…","筛选插件…");search.setAttribute("aria-label",search.placeholder);body.append(search);
|
|
53
|
+
const cards=node("div","plugin-list");body.append(cards);
|
|
54
|
+
const draw=()=>{
|
|
55
|
+
cards.replaceChildren();
|
|
56
|
+
for(const p of listing.plugins.filter(p=>`${p.manifest.id} ${p.manifest.name} ${p.manifest.description||""}`.toLowerCase().includes(search.value.toLowerCase()))) {
|
|
57
|
+
const m=p.manifest,card=node("article","plugin-card"),heading=node("div","plugin-card-heading");
|
|
58
|
+
heading.append(node("strong","",m.name),node("span","plugin-badge",p.enabled?t("Enabled","已启用"):t("Disabled","已停用")));card.append(heading,node("code","",`${m.id} · ${m.version} · API ${m.apiVersion}`),node("p","",m.description||""));
|
|
59
|
+
card.append(node("p","settings-note",`${(m.categories||[]).join(" / ")} · ${m.runtime||"sandbox"}`),node("p","settings-note",t("Permissions: ","权限:")+(m.permissions||[]).join(", ")));
|
|
60
|
+
if(p.error)card.append(node("p","plugin-warning",p.error));
|
|
61
|
+
let trust=null;
|
|
62
|
+
if(m.runtime==="native"&&m.entries?.agent&&!p.enabled) {
|
|
63
|
+
trust=node("input");trust.type="checkbox";
|
|
64
|
+
card.append(field(t("Trust this exact native package. It can execute unrestricted local code.","信任此版本的原生插件:它能运行不受限制的本机代码。"),trust));
|
|
65
|
+
}
|
|
66
|
+
const actions=node("div","plugin-actions");
|
|
67
|
+
actions.append(button(p.enabled?t("Disable","停用"):t("Enable","启用"),()=>{
|
|
68
|
+
if(trust&&!trust.checked){error(new Error(t("Select native trust before enabling.","启用前请勾选原生代码信任。")));return;}
|
|
69
|
+
void mutate({action:"configure",id:m.id,patch:{enabled:!p.enabled,trustNative:Boolean(trust?.checked)}});
|
|
70
|
+
}),button(t("Update","更新"),()=>void mutate({action:"update",id:m.id})));
|
|
71
|
+
if(p.rollback)actions.append(button(t("Roll back","回滚"),()=>void mutate({action:"rollback",id:m.id})));
|
|
72
|
+
actions.append(button(t("Remove","卸载"),()=>void mutate({action:"remove",id:m.id})));card.append(actions);
|
|
73
|
+
if(m.configuration) {
|
|
74
|
+
const details=node("details"),summary=node("summary","",t("Configuration","配置")),form=node("form","plugin-config");details.append(summary,form);
|
|
75
|
+
const inputs=[];
|
|
76
|
+
for(const [key,schema] of Object.entries(m.configuration.properties||{})) {
|
|
77
|
+
let input;
|
|
78
|
+
if(schema.enum){input=node("select");for(const value of schema.enum)input.append(new Option(String(value),JSON.stringify(value)));input.value=JSON.stringify(p.config[key]??schema.default??schema.enum[0]);}
|
|
79
|
+
else if(schema.type==="boolean"){input=node("input");input.type="checkbox";input.checked=Boolean(p.config[key]??schema.default);}
|
|
80
|
+
else if(["array","object"].includes(schema.type)){input=node("textarea");input.value=JSON.stringify(p.config[key]??schema.default??(schema.type==="array"?[]:{}),null,2);}
|
|
81
|
+
else {input=node("input","path-input");input.type=["number","integer"].includes(schema.type)?"number":"text";input.value=String(p.config[key]??schema.default??"");if(schema.minimum!==undefined)input.min=schema.minimum;if(schema.maximum!==undefined)input.max=schema.maximum;}
|
|
82
|
+
input.setAttribute("aria-label",schema.title||key);form.append(field(schema.title||key,input));if(schema.description)form.append(node("small","settings-note",schema.description));inputs.push({key,schema,input});
|
|
83
|
+
}
|
|
84
|
+
const save=button(t("Save configuration","保存配置"),()=>{},"control primary");save.type="submit";form.append(save);
|
|
85
|
+
form.addEventListener("submit",e=>{e.preventDefault();try{const config={...p.config};for(const {key,schema,input} of inputs){if(schema.enum)config[key]=JSON.parse(input.value);else if(schema.type==="boolean")config[key]=input.checked;else if(["number","integer"].includes(schema.type))config[key]=Number(input.value);else if(["array","object"].includes(schema.type))config[key]=JSON.parse(input.value);else config[key]=input.value;}void mutate({action:"configure",id:m.id,patch:{config}});}catch(e){error(e);}});card.append(details);
|
|
86
|
+
}
|
|
87
|
+
const inspect=node("details"),inspectTitle=node("summary","",t("Package details","包详情")),pre=node("pre","plugin-details",JSON.stringify({source:p.source,integrity:p.integrity,manifest:m},null,2));inspect.append(inspectTitle,pre);card.append(inspect);cards.append(card);
|
|
88
|
+
}
|
|
89
|
+
if(!cards.childNodes.length)cards.append(node("p","settings-note",t("No plugins installed. Start with examples/plugins in the repository.","尚未安装插件,可从仓库 examples/plugins 中的示例开始。")));
|
|
90
|
+
};
|
|
91
|
+
search.addEventListener("input",draw);draw();
|
|
92
|
+
const providers=node("div","plugin-providers");providers.append(node("strong","",t("Component providers","组件提供者")));
|
|
93
|
+
for(const slot of ["chat.composer","chat.message.renderer","chat.card.plan","chat.card.tool","persona"]) {
|
|
94
|
+
const choices=slot==="persona"?(catalog.prompts||[]).filter(p=>p.replace).map(p=>({key:p.id,title:p.id})):catalog.ui.filter(u=>u.slot===slot);
|
|
95
|
+
const select=node("select");select.append(new Option(t("Built in","内置"),""));for(const u of choices)select.append(new Option(u.title,u.key));select.value=listing.providers[slot]||"";
|
|
96
|
+
select.addEventListener("change",()=>void mutate({action:"provider",slot,provider:select.value}));providers.append(field(slot,select));
|
|
97
|
+
}
|
|
98
|
+
body.append(providers,node("p","settings-note",t("Disable a broken UI plugin here, or start neutrino --safe-mode --no-web. See docs/plugins/README.md for the SDK and tutorials.","界面插件异常时可在此停用,或使用 neutrino --safe-mode --no-web 启动。SDK 与教程见 docs/plugins/README.md。")));
|
|
99
|
+
const current=host.context();if(current.pluginPrompt||current.pluginErrors?.length){const details=node("details"),summary=node("summary","",t("Current turn diagnostics","当前回合诊断"));details.append(summary,node("pre","plugin-details",JSON.stringify({prompt:current.pluginPrompt,errors:current.pluginErrors},null,2)));body.append(details);}
|
|
100
|
+
for(const item of contributions().filter(u=>u.slot==="settings.sections"&&(!u.platforms||u.platforms.includes("web")))){const section=node("section","plugin-card");section.append(node("strong","",item.title));if(item.view||item.entry)void mount(item,section);else section.append(node("p","",item.text||""));body.append(section);}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function draft() { const text=host.getDraft();if(text!==lastDraft){lastDraft=text;draftRevision++;}return{text,revision:draftRevision}; }
|
|
104
|
+
function setDraft(value) {const current=draft();if(!value||typeof value.text!=="string"||value.text.length>200000)throw new Error("Invalid draft");if(value.expectedRevision!==current.revision)throw new Error("Draft changed; read it again before editing");host.setDraft(value.text);return draft();}
|
|
105
|
+
function viewContext(item,extra={}) {
|
|
106
|
+
const c=host.context(),canRead=item.permissions.includes("conversation.read");
|
|
107
|
+
return {pluginId:item.pluginId,id:item.id,title:item.title,profile:host.profile,...(canRead?extra:{}),...(canRead?{cwd:c.cwd}: {})};
|
|
108
|
+
}
|
|
109
|
+
function removeFrame(key) {const frame=frames.get(key);if(!frame)return;clearTimeout(frame.timer);frame.node.remove();if(frame.restore)frame.restore();frames.delete(key);}
|
|
110
|
+
function disposeViews(){for(const key of [...frames.keys()])removeFrame(key);}
|
|
111
|
+
async function asset(item,file) {const key=`${item.pluginId}/${file}/${scopeQuery()}`;if(!assets.has(key))assets.set(key,request(`/api/plugins/asset${scopeQuery()}${scopeQuery()?"&":"?"}id=${encodeURIComponent(item.pluginId)}&file=${encodeURIComponent(file)}`).then(r=>r.body));return assets.get(key);}
|
|
112
|
+
function encoded(value){return JSON.stringify(value).replace(/</g,"\\u003c");}
|
|
113
|
+
async function mount(item,target,extra={},fallback=null) {
|
|
114
|
+
const key=`${item.key}:${target.dataset.pluginTarget||(target.dataset.pluginTarget=randomToken())}`;
|
|
115
|
+
if(failed.has(key))return;
|
|
116
|
+
if(frames.has(key)){const f=frames.get(key);f.context=viewContext(item,extra);f.node.contentWindow?.postMessage({neutrino:f.nonce,type:"context",context:f.context},"*");return;}
|
|
117
|
+
const iframe=node("iframe","plugin-frame");iframe.title=item.title;iframe.setAttribute("sandbox","allow-scripts");iframe.setAttribute("referrerpolicy","no-referrer");iframe.style.height=`${item.height||160}px`;
|
|
118
|
+
const nonce=randomToken(), frame={node:iframe,nonce,item,target,context:viewContext(item,extra),ready:false,timer:null,restore:null,identity:contextKey,rateAt:Date.now(),rate:0};frames.set(key,frame);
|
|
119
|
+
const fail=message=>{failed.add(key);removeFrame(key);host.notice?.(`${item.title}: ${message}`);};frame.fail=fail;
|
|
120
|
+
if(fallback){frame.restore=()=>{fallback.style.display="";};}
|
|
121
|
+
target.append(iframe);
|
|
122
|
+
try {
|
|
123
|
+
const html=item.view?await asset(item,item.view):"<div id=\"plugin-root\"></div>";
|
|
124
|
+
const entry=item.entry?await asset(item,item.entry):null;
|
|
125
|
+
if(!frames.has(key)||frame.identity!==contextKey)return;
|
|
126
|
+
const boot=`(() => { const nonce=${encoded(nonce)}, pending=new Map(), listeners=new Map();let id=0,context=${encoded(frame.context)};
|
|
127
|
+
const request=(op,args)=>new Promise((resolve,reject)=>{const key=++id;const timer=setTimeout(()=>{pending.delete(key);reject(new Error('Plugin request timed out'));},30000);pending.set(key,{resolve,reject,timer});parent.postMessage({neutrino:nonce,type:'request',id:key,op,args},'*');});
|
|
128
|
+
const api={get context(){return context},request,composer:{read:()=>request('composer.read'),write:args=>request('composer.write',args),submit:()=>request('composer.submit')},conversation:{read:()=>request('conversation.read')},configuration:{read:()=>request('configuration.read'),write:args=>request('configuration.write',args)},commands:{execute:(name,args={})=>request('commands.execute',{name,args})},view:{resize:height=>request('view.resize',{height})},on:(name,fn)=>{if(!listeners.has(name))listeners.set(name,new Set());listeners.get(name).add(fn);return()=>listeners.get(name).delete(fn);}};
|
|
129
|
+
Object.defineProperty(window,'neutrino',{value:Object.freeze(api)});
|
|
130
|
+
addEventListener('message',event=>{if(event.source!==parent||event.data?.neutrino!==nonce)return;const m=event.data;if(m.type==='context'){context=m.context;for(const fn of listeners.get('context.changed')||[])try{fn(context)}catch(e){}}else if(m.type==='response'){const p=pending.get(m.id);if(p){clearTimeout(p.timer);pending.delete(m.id);m.error?p.reject(new Error(m.error)):p.resolve(m.value);}}});
|
|
131
|
+
addEventListener('error',event=>parent.postMessage({neutrino:nonce,type:'failure',error:event.message},'*'));
|
|
132
|
+
addEventListener('unhandledrejection',event=>parent.postMessage({neutrino:nonce,type:'failure',error:String(event.reason)},'*'));
|
|
133
|
+
addEventListener('DOMContentLoaded',async()=>{try{${entry?`const source=${encoded(entry)};const module=await import(URL.createObjectURL(new Blob([source],{type:'text/javascript'})));await (module.default?.mount||module.mount)?.(api,document.getElementById('plugin-root')||document.body);`:""}parent.postMessage({neutrino:nonce,type:'ready'},'*');}catch(e){parent.postMessage({neutrino:nonce,type:'failure',error:String(e)},'*');}});
|
|
134
|
+
})();`;
|
|
135
|
+
const prefix=`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' blob:; style-src 'unsafe-inline'; img-src data:; connect-src 'none'; form-action 'none'; base-uri 'none'"><script>${boot.replace(/<\/script/gi,"<\\/script")}<\/script>`;
|
|
136
|
+
iframe.srcdoc=/<head[^>]*>/i.test(html)?html.replace(/<head[^>]*>/i,m=>m+prefix):prefix+html;
|
|
137
|
+
frame.timer=setTimeout(()=>fail(t("View did not become ready","视图加载超时")),10000);
|
|
138
|
+
frame.fallback=fallback;
|
|
139
|
+
}catch(e){fail(e.message);}
|
|
140
|
+
}
|
|
141
|
+
async function onMessage(event) {
|
|
142
|
+
const f=[...frames.values()].find(f=>f.node.contentWindow===event.source&&event.data?.neutrino===f.nonce);
|
|
143
|
+
if(!f||event.origin!=="null"||f.identity!==contextKey)return;
|
|
144
|
+
const m=event.data;
|
|
145
|
+
if(m.type==="ready"){f.ready=true;clearTimeout(f.timer);if(f.fallback)f.fallback.style.display="none";return;}
|
|
146
|
+
if(m.type==="failure"){f.fail(String(m.error));return;}
|
|
147
|
+
if(m.type!=="request")return;
|
|
148
|
+
if(Date.now()-f.rateAt>1000){f.rateAt=Date.now();f.rate=0;}if(++f.rate>100){f.fail("Plugin message rate exceeded");return;}
|
|
149
|
+
const requirePermission=p=>{if(!f.item.permissions.includes(p))throw new Error(`Plugin requires ${p}`);};
|
|
150
|
+
try {
|
|
151
|
+
let value;
|
|
152
|
+
if(m.op==="composer.read"){requirePermission("composer.read");value=draft();}
|
|
153
|
+
else if(m.op==="composer.write"){requirePermission("composer.write");value=setDraft(m.args);}
|
|
154
|
+
else if(m.op==="composer.submit"){requirePermission("composer.submit");value=await host.submit();draft();}
|
|
155
|
+
else if(m.op==="conversation.read"){requirePermission("conversation.read");value=host.context();}
|
|
156
|
+
else if(m.op==="configuration.read"||m.op==="configuration.write"){
|
|
157
|
+
requirePermission("ui.contribute");const path=`/api/plugins${scopeQuery()}`;const state=await request(path),plugin=state.plugins.find(p=>p.manifest.id===f.item.pluginId);if(!plugin)throw new Error("Plugin is unavailable");
|
|
158
|
+
if(m.op==="configuration.read")value={values:plugin.config,schema:plugin.manifest.configuration,revision:state.revision};
|
|
159
|
+
else{if(m.args?.expectedRevision!==state.revision)throw new Error("Configuration changed; read it again");const updated=await request(path,{action:"configure",id:f.item.pluginId,revision:state.revision,patch:{config:m.args.values}});value={revision:updated.revision};void refresh(true);}
|
|
160
|
+
}
|
|
161
|
+
else if(m.op==="commands.execute"){requirePermission("commands.register");if(!/^[a-z][a-z0-9_-]{0,39}$/.test(m.args?.name))throw new Error("Invalid command name");value=await host.command(`${f.item.pluginId}/${m.args.name}`,m.args.args||{});}
|
|
162
|
+
else if(m.op==="view.resize"){const h=Number(m.args?.height);if(!Number.isFinite(h))throw new Error("Invalid view height");f.node.style.height=`${Math.min(1200,Math.max(40,h))}px`;value=true;}
|
|
163
|
+
else throw new Error("Unsupported plugin operation");
|
|
164
|
+
event.source?.postMessage({neutrino:f.nonce,type:"response",id:m.id,value:value??null},"*");
|
|
165
|
+
}catch(e){event.source?.postMessage({neutrino:f.nonce,type:"response",id:m.id,error:e.message},"*");}
|
|
166
|
+
}
|
|
167
|
+
window.addEventListener("message",onMessage);
|
|
168
|
+
const matches=(item,message)=>!item.match||((!item.match.role||item.match.role===message?.role)&&(!item.match.tool||item.match.tool===message?.name));
|
|
169
|
+
function runItem(item){if(item.command){void host.command(`${item.pluginId}/${item.command}`,{}).catch(e=>host.notice?.(e.message));}else if(item.insert!==undefined){const d=draft();setDraft({text:d.text+item.insert,expectedRevision:d.revision});}else{activeTab=item.key;selectedTab=item;renderPanel();}}
|
|
170
|
+
function renderPanel(){panel.replaceChildren();panel.classList.toggle("hidden",!selectedTab);if(!selectedTab)return;const close=button(t("Close panel","关闭面板"),()=>{selectedTab=null;activeTab="";renderPanel();});panel.append(close);const target=node("div","plugin-panel-content");panel.append(target);if(selectedTab.view||selectedTab.entry)void mount(selectedTab,target);else target.textContent=selectedTab.text||selectedTab.title;}
|
|
171
|
+
function contributions(){
|
|
172
|
+
const merged=new Map(catalog.ui.map(u=>[u.key,u]));
|
|
173
|
+
for(const item of host.context().pluginUI||[])if(catalog.permissions?.[item.pluginId]?.includes("ui.contribute")&&item.key===`${item.pluginId}/${item.id}`)merged.set(item.key,{...item,permissions:catalog.permissions[item.pluginId],entry:catalog.entries?.[item.pluginId]});
|
|
174
|
+
return [...merged.values()].filter(u=>!u.platforms||u.platforms.includes("web")).sort((a,b)=>(a.order||0)-(b.order||0)||a.key.localeCompare(b.key));
|
|
175
|
+
}
|
|
176
|
+
function rebuildChrome(){
|
|
177
|
+
for(const target of [menu,tabs,sidebar,toolbar,headerActions])target.replaceChildren();
|
|
178
|
+
const targets={"app.menu":menu,"workspace.tabs":tabs,"sidebar.footer":sidebar,"chat.composer.toolbar":toolbar,"chat.header.actions":headerActions};
|
|
179
|
+
const items=contributions();chromeSignature=JSON.stringify(items.filter(u=>targets[u.slot]));
|
|
180
|
+
for(const item of items){const target=targets[item.slot];if(!target)continue;if(item.slot==="sidebar.footer"&&item.text){target.append(node("p","settings-note",item.text));}else target.append(button(item.title,()=>runItem(item)));}
|
|
181
|
+
if(catalog.skills.length){const select=node("select","plugin-skill-select");select.setAttribute("aria-label",t("Skills","技能"));select.append(new Option(t("Add skill…","加入技能…"),""));for(const s of catalog.skills)select.append(new Option(s.id,s.id));select.addEventListener("change",()=>{if(select.value){const d=draft();setDraft({text:`/skill ${select.value}\n${d.text}`,expectedRevision:d.revision});select.value="";}});toolbar.append(select);}
|
|
182
|
+
if(selectedTab){selectedTab=items.find(u=>u.key===activeTab)||null;renderPanel();}
|
|
183
|
+
}
|
|
184
|
+
function replacement(item,target,extra={}) {
|
|
185
|
+
if(!target||!item||(!item.view&&!item.entry))return;
|
|
186
|
+
let content=target.querySelector(":scope > .plugin-fallback");
|
|
187
|
+
if(!content){content=node("div","plugin-fallback");while(target.firstChild)content.append(target.firstChild);target.append(content);}
|
|
188
|
+
void mount(item,target,extra,content);
|
|
189
|
+
}
|
|
190
|
+
function sync(){
|
|
191
|
+
if(disposed)return;
|
|
192
|
+
const openLabel="◈ "+t("Plugins","插件");if(open.textContent!==openLabel)open.textContent=openLabel;
|
|
193
|
+
const c=host.context(),key=identityOf(c);
|
|
194
|
+
if(key!==contextKey){contextKey=key;disposeViews();failed.clear();for(const target of [menu,tabs,sidebar,toolbar,headerActions])target.replaceChildren();selectedTab=null;activeTab="";panel.replaceChildren();panel.classList.add("hidden");void refresh(true);return;}
|
|
195
|
+
if(catalogScope!==scopeQuery())return;
|
|
196
|
+
for(const [key,f] of frames)if(!f.node.isConnected)removeFrame(key);
|
|
197
|
+
const ui=contributions();if(JSON.stringify(ui.filter(u=>["app.menu","workspace.tabs","sidebar.footer","chat.composer.toolbar","chat.header.actions"].includes(u.slot)))!==chromeSignature)rebuildChrome();
|
|
198
|
+
const provider=slot=>ui.find(u=>u.slot===slot&&catalog.providers[slot]===u.key);
|
|
199
|
+
const composer=provider("chat.composer"),input=document.getElementById("input");
|
|
200
|
+
if(composer&&input){const target=input.parentElement;if(target){let view=target.querySelector(":scope > .plugin-composer-target");if(!view){view=node("div","plugin-composer-target");target.insertBefore(view,input);}void mount(composer,view,{},input);}}
|
|
201
|
+
if(!composer&&input){input.style.display="";document.querySelector(".plugin-composer-target")?.remove();}
|
|
202
|
+
const plan=document.getElementById("plan-card");if(plan&&!plan.classList.contains("hidden"))replacement(provider("chat.card.plan"),plan,{plan:c.plan});
|
|
203
|
+
for(const article of document.querySelectorAll("article.message[data-plugin-message]")) {
|
|
204
|
+
const index=Number(article.dataset.pluginMessage),message=index<0?{role:"assistant",content:c.streamingContent||""}:c.messages?.filter(m=>m.role!=="system")[index];
|
|
205
|
+
const tool=message?.role==="tool"?provider("chat.card.tool"):null;
|
|
206
|
+
const item=tool||provider("chat.message.renderer");if(item&&matches(item,message))replacement(item,article,{message});
|
|
207
|
+
for(const action of ui.filter(u=>u.slot==="chat.message.actions"&&matches(u,message)))if(!article.querySelector(`[data-plugin-action="${action.key}"]`)){const b=button(action.title,()=>runItem(action));b.dataset.pluginAction=action.key;article.append(b);}
|
|
208
|
+
}
|
|
209
|
+
for(const f of frames.values())if(f.ready){const extra=f.context.message?{message:f.context.message}:f.context.plan?{plan:c.plan}:{};f.node.contentWindow?.postMessage({neutrino:f.nonce,type:"context",context:viewContext(f.item,extra)},"*");}
|
|
210
|
+
}
|
|
211
|
+
async function refresh(force=false){
|
|
212
|
+
if(loading||disposed)return;loading=true;
|
|
213
|
+
const requestedScope=scopeQuery();let stale=false;
|
|
214
|
+
try{
|
|
215
|
+
const next=await request(`/api/plugins/catalog${requestedScope}`);
|
|
216
|
+
if(disposed)return;
|
|
217
|
+
if(requestedScope!==scopeQuery()){stale=true;return;}
|
|
218
|
+
catalogScope=requestedScope;
|
|
219
|
+
if(force||JSON.stringify(next)!==JSON.stringify(catalog)){
|
|
220
|
+
disposeViews();failed.clear();catalog=next;
|
|
221
|
+
for(const [key,old] of appliedTheme){if(old)document.documentElement.style.setProperty(key,old);else document.documentElement.style.removeProperty(key);}appliedTheme.clear();
|
|
222
|
+
for(const [key,value] of Object.entries(catalog.theme)){appliedTheme.set(key,document.documentElement.style.getPropertyValue(key));document.documentElement.style.setProperty(key,value);}
|
|
223
|
+
rebuildChrome();
|
|
224
|
+
}
|
|
225
|
+
}catch(e){if(dialog.open)error(e);}finally{loading=false;if(stale&&!disposed)void refresh(true);else sync();}
|
|
226
|
+
}
|
|
227
|
+
const observer=new MutationObserver(()=>{clearTimeout(syncTimer);syncTimer=setTimeout(sync,40);});observer.observe(document.getElementById("app"),{childList:true,subtree:true});
|
|
228
|
+
timer=setInterval(()=>void refresh(),5000);void refresh(true);
|
|
229
|
+
return { sync,open:()=>open.click(),dispose(){disposed=true;clearInterval(timer);clearTimeout(syncTimer);observer.disconnect();window.removeEventListener("message",onMessage);disposeViews();dialog.remove();} };
|
|
230
|
+
}
|
package/web/style.css
CHANGED
|
@@ -1370,7 +1370,7 @@ svg {
|
|
|
1370
1370
|
transform: rotate(90deg);
|
|
1371
1371
|
}
|
|
1372
1372
|
|
|
1373
|
-
.turn-
|
|
1373
|
+
.turn-label {
|
|
1374
1374
|
flex: 1 1 auto;
|
|
1375
1375
|
min-width: 0;
|
|
1376
1376
|
overflow: hidden;
|
|
@@ -1505,6 +1505,12 @@ svg {
|
|
|
1505
1505
|
.agent-mode-check { flex: none; width: 16px; height: 16px; color: var(--accent); visibility: hidden; }
|
|
1506
1506
|
.agent-mode-menu [aria-checked="true"] .agent-mode-check { visibility: visible; }
|
|
1507
1507
|
.agent-mode-menu > button:disabled { opacity: .55; cursor: wait; }
|
|
1508
|
+
.agent-mode-menu > .fast-mode-card { border: 1px solid transparent; margin-block: 4px; }
|
|
1509
|
+
.agent-mode-menu > .fast-mode-card[aria-checked="true"] { border-color: var(--accent); background: var(--surface-3); }
|
|
1510
|
+
.fast-progress { padding: 10px 12px; margin-block: 8px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }
|
|
1511
|
+
.fast-progress p { margin: 4px 0; }
|
|
1512
|
+
.fast-progress ul { padding-left: 18px; margin: 6px 0; max-height: 180px; overflow-y: auto; }
|
|
1513
|
+
.fast-progress .error { color: var(--danger, #df6767); }
|
|
1508
1514
|
.agent-mode-feedback { position: absolute; bottom: 100%; left: 0; width: min(370px, calc(100vw - 48px)); margin: 0 0 5px; font-size: 11px; color: var(--text-secondary); }
|
|
1509
1515
|
.agent-mode-feedback:empty, .agent-mode-picker[open] .agent-mode-feedback { display: none; }
|
|
1510
1516
|
.agent-mode-feedback.error { color: var(--error, #ef8181); }
|
|
@@ -1634,11 +1640,14 @@ svg {
|
|
|
1634
1640
|
}
|
|
1635
1641
|
|
|
1636
1642
|
#send .pause-icon,
|
|
1637
|
-
#send
|
|
1643
|
+
#send .resume-icon,
|
|
1644
|
+
#send.is-pause .send-icon,
|
|
1645
|
+
#send.is-resume .send-icon {
|
|
1638
1646
|
display: none;
|
|
1639
1647
|
}
|
|
1640
1648
|
|
|
1641
|
-
#send.is-pause .pause-icon
|
|
1649
|
+
#send.is-pause .pause-icon,
|
|
1650
|
+
#send.is-resume .resume-icon {
|
|
1642
1651
|
display: block;
|
|
1643
1652
|
}
|
|
1644
1653
|
|
package/web/turns.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// A conversation is a sequence of turns: each one starts at a user message and
|
|
4
4
|
// runs until the next one. Once the agent has finished a turn, the browser folds
|
|
5
|
-
// away the work it took — the
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// away the work it took — the thinking and the tool calls — and keeps the full
|
|
6
|
+
// user prompt and final reply on screen. The complete round is still one click
|
|
7
|
+
// away.
|
|
8
8
|
//
|
|
9
9
|
// The thresholds mirror TURN_FOLD_MIN_BLOCKS / TURN_FOLD_MIN_CHARS in
|
|
10
10
|
// src/screens/chat.ts, so the terminal and the browser fold the same turns.
|
|
@@ -33,8 +33,8 @@ export function turnAnswerIndex(messages) {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* True when folding the work behind this turn saves more room than it costs.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* User input and the reply are never folded away, so neither counts toward the
|
|
37
|
+
* folding threshold, however long either one is.
|
|
38
38
|
*/
|
|
39
39
|
export function turnFolds(messages) {
|
|
40
40
|
const reply = turnAnswerIndex(messages);
|
|
@@ -42,7 +42,7 @@ export function turnFolds(messages) {
|
|
|
42
42
|
let chars = 0;
|
|
43
43
|
messages.forEach((message, index) => {
|
|
44
44
|
if (index === reply) return;
|
|
45
|
-
if (message.role !== "
|
|
45
|
+
if (message.role !== "assistant") return;
|
|
46
46
|
blocks++;
|
|
47
47
|
chars += String(message.content ?? "").length;
|
|
48
48
|
});
|
|
@@ -50,8 +50,7 @@ export function turnFolds(messages) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* The first line of the first message in `role
|
|
54
|
-
* signpost, and the full text is one click away.
|
|
53
|
+
* The first nonempty line of the first message in `role` that contains text.
|
|
55
54
|
*/
|
|
56
55
|
export function turnPreview(messages, role) {
|
|
57
56
|
for (const message of messages) {
|