@evandu/super-pi 0.1.75 → 0.1.76

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.
@@ -52,7 +52,7 @@ const Parameters = Type.Object({
52
52
  task_id: Type.Optional(Type.String({ description: "Resume a previous task when supported" })),
53
53
  });
54
54
  const QuestionParameters = Type.Object({ question: Type.String(), options: Type.Array(Type.String(), { minItems: 1 }), multiple: Type.Optional(Type.Boolean()), extraNote: Type.Optional(Type.Boolean()) });
55
- const ToolRegisterParameters = Type.Object({ slug: Type.String({ description: "Lowercase tool slug, e.g. json-viewer" }), name: Type.String({ description: "Human-readable tool name" }), description: Type.Optional(Type.String()), entrypoint: Type.String({ description: "Workspace-relative HTML entrypoint, normally tools/<slug>/index.html" }) });
55
+ const ToolRegisterParameters = Type.Object({ slug: Type.String({ description: "Lowercase global tool slug, e.g. json-viewer" }), name: Type.String({ description: "Human-readable tool name" }), description: Type.Optional(Type.String()), entrypoint: Type.Optional(Type.String({ description: "HTML entrypoint, normally index.html" })), files: Type.Array(Type.Object({ path: Type.String({ description: "Tool-relative file path" }), content: Type.String({ description: "Complete UTF-8 text content" }) }), { minItems: 1, maxItems: 32 }), script: Type.Optional(Type.Object({ path: Type.String({ description: "Optional tool-relative Python .py entrypoint" }), timeoutMs: Type.Optional(Type.Number()), parameters: Type.Optional(Type.Record(Type.String(), Type.Object({ type: Type.Union([Type.Literal("string"), Type.Literal("number"), Type.Literal("boolean"), Type.Literal("object"), Type.Literal("array")]), required: Type.Optional(Type.Boolean()), enum: Type.Optional(Type.Array(Type.Union([Type.String(), Type.Number(), Type.Boolean()]))) }))) })) });
56
56
 
57
57
  /**
58
58
  * The `todowrite` parameter schema, OpenCode's `tool/todo.ts:6-8` in the TypeBox dialect this adapter
@@ -145,13 +145,15 @@ const extension = (pi: ExtensionApi): void => {
145
145
  // cancellation. The text says what was asked and what was chosen, in words.
146
146
  return text(JSON.stringify({ answers: (payload.answers ?? []).map((answer) => ({ question: p.question, selected: answer.selected, ...(answer.note ? { note: answer.note } : {}) })), ...(payload.summary ? { summary: payload.summary } : {}) }));
147
147
  } });
148
- pi.registerTool({ name: "tool_register", label: "Register Tool", description: "Register a workspace tool in Super Pi's right sidebar. Use this when the user asks to create or add a right-sidebar tool. First create the HTML/JS/CSS files under the workspace, then call this tool; do not start another HTTP server.", promptSnippet: "register a newly created right-sidebar tool with Super Pi", parameters: ToolRegisterParameters, execute: async (_id, raw, _signal, _update, context): Promise<TaskToolResult> => {
148
+ pi.registerTool({ name: "tool_register", label: "Register Tool", description: "Register a global Super Pi sidebar tool. Use this when the user asks to create or add a right-sidebar tool. Send complete HTML/JS/CSS files directly; they are stored by Super Pi and work in every workspace. An optional registered Python script receives validated JSON on stdin and must print one JSON object to stdout. Do not start another HTTP server.", promptSnippet: "register a newly created global right-sidebar tool with Super Pi", parameters: ToolRegisterParameters, execute: async (_id, raw, _signal, _update, _context): Promise<TaskToolResult> => {
149
149
  const endpoint = process.env.SUPER_PI_TOOLS_ENDPOINT;
150
150
  const text = (body: string, isError?: boolean): TaskToolResult => ({ content: [{ type: "text", text: body }], details: undefined, ...(isError ? { isError: true } : {}) });
151
151
  if (!endpoint) return text("The Super Pi tool registry is not available in this run.", true);
152
- const p = raw as { slug: string; name: string; description?: string; entrypoint: string };
152
+ const p = raw as { slug: string; name: string; description?: string; entrypoint?: string; files: { path: string; content: string }[]; script?: unknown };
153
153
  try {
154
- const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...p, workspacePath: context.cwd }) });
154
+ const capability = process.env.SUPER_PI_TOOLS_CAPABILITY;
155
+ const conversationId = process.env.SUPER_PI_TASK_CONVERSATION;
156
+ const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json", ...(capability ? { "x-super-pi-capability": capability } : {}) }, body: JSON.stringify({ ...p, conversationId, capability }) });
155
157
  const payload = await response.json() as { id?: string; error?: string };
156
158
  if (!response.ok || payload.error) return text(payload.error ?? `Tool registration failed (HTTP ${response.status}).`, true);
157
159
  return text(JSON.stringify({ registered: true, id: payload.id, url: `/tools/${payload.id}/` }));
@@ -49,15 +49,11 @@ npx @evandu/super-pi`})})]}),(0,m.jsxs)(`div`,{className:`help-card`,children:[(
49
49
  已验证的步骤、命令和注意事项`:`输入长期有效的事实或偏好`,value:u,onChange:e=>d(e.target.value)}),(0,m.jsxs)(`div`,{className:`settings-rule-form-buttons`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`settings-dir-add-btn`,disabled:!u.trim(),onClick:async()=>{await v({action:`write`,operation:{target:c,action:f?`replace`:`add`,id:f,content:u}})&&(h(void 0),d(``))},children:[`保存`,f?`修改`:`新条目`]}),f&&(0,m.jsx)(`button`,{type:`button`,className:`settings-rule-cancel-btn`,onClick:()=>{h(void 0),d(``)},children:`取消编辑`})]})]})]}),(0,m.jsxs)(`section`,{className:`settings-section`,children:[(0,m.jsxs)(`h4`,{className:`settings-section-title`,children:[`待审批提案(共 `,n.pending.length,` 个)`]}),n.pending.length===0?(0,m.jsx)(`div`,{className:`settings-empty-hint`,children:`暂无待审批提案。`}):(0,m.jsx)(`div`,{className:`settings-dirs-list`,children:n.pending.map(e=>(0,m.jsxs)(`article`,{"data-memory-proposal":e.id,className:`settings-proposal-card`,children:[(0,m.jsxs)(`div`,{className:`settings-proposal-header`,children:[(0,m.jsx)(`span`,{className:`settings-proposal-badge`,children:e.operation.target}),(0,m.jsx)(`span`,{className:`settings-proposal-action`,children:e.operation.action})]}),e.previous&&(0,m.jsxs)(`pre`,{className:`settings-proposal-diff old`,children:[`原内容:`,e.previous]}),(0,m.jsx)(`pre`,{className:`settings-proposal-diff new`,children:e.operation.content}),(0,m.jsxs)(`div`,{className:`settings-proposal-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`settings-rule-edit-btn`,onClick:()=>void v({action:`approve`,proposalId:e.id}),children:`批准`}),(0,m.jsx)(`button`,{type:`button`,className:`settings-dir-remove-btn`,onClick:()=>void v({action:`discard`,proposalId:e.id}),children:`拒绝`})]})]},e.id))})]})]}),t&&(0,m.jsx)(`div`,{className:`picker-actions`,children:(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,onClick:t,children:`完成`})})]}):(0,m.jsx)(`div`,{className:`settings-empty-hint`,children:`请先在主界面选择工作区以管理其专属记忆与技能。`})}var Yt=[{kind:`calculator`,name:`计算器`,glyph:`∑`,blurb:`算术表达式,四则运算与乘方。除零会明确报错。`},{kind:`timestamp`,name:`时间戳`,glyph:`◷`,blurb:`Unix 时间戳与日期互转,本地计算,不联网。`}],Xt=[{id:`preset_calculator`,kind:`calculator`,name:`计算器`,glyph:`∑`}],Zt=40,N=2048,P=`super-pi:tools:v1`;function Qt(e){return typeof e==`string`?e.replace(/\s+/g,` `).trim().slice(0,Zt):``}function $t(e){let t=e.trim();if(!t)return{ok:!1,reason:`请填写网址。`};if(t.length>N)return{ok:!1,reason:`网址太长了,请控制在 ${N} 个字符以内。`};let n=se(t);return n?{ok:!0,url:n}:{ok:!1,reason:`这个网址不是允许的协议。只支持 http / https / mailto 开头的网址。`}}function en(e){if(!e)return[];let t;try{t=JSON.parse(e)}catch{return[]}if(!Array.isArray(t))return[];let n=[],r=new Set;for(let e of t){if(typeof e!=`object`||!e)continue;let t=e,i=t.kind;if(typeof i!=`string`||i!==`calculator`&&i!==`timestamp`&&i!==`link`)continue;let a=Qt(t.name);if(!a)continue;let o=typeof t.glyph==`string`&&t.glyph.trim()?t.glyph.trim().slice(0,4):``;if(!o)continue;let s=typeof t.id==`string`&&t.id.trim()?t.id.trim().slice(0,64):``;if(s&&!r.has(s)){if(i===`link`){let e=se(typeof t.url==`string`?t.url:``);if(!e)continue;r.add(s),n.push({id:s,kind:`link`,name:a,glyph:o,url:e})}else if(r.add(s),n.push({id:s,kind:i,name:a,glyph:o}),n.length>=32)break}}return n}function tn(e){return JSON.stringify(e.map(e=>e.kind===`link`?{...e}:{id:e.id,kind:e.kind,name:e.name,glyph:e.glyph}))}function nn(){let e=typeof globalThis<`u`?globalThis.crypto:void 0;return typeof e?.randomUUID==`function`?e.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)})}function rn(e,t){let n=e===`link`?`link`:`preset_${e}`,r=nn().replace(/-/g,``).slice(0,8),i=`${n}_${r}`,a=1;for(;t.includes(i);)i=`${n}_${r}_${a}`,a+=1;return i}function an(e,t){if(e.some(e=>e.kind===t))return e.slice();let n=Yt.find(e=>e.kind===t);return n?[...e,{id:rn(t,e.map(e=>e.id)),kind:t,name:n.name,glyph:n.glyph}]:e.slice()}function on(e,t){return e.filter(e=>e.id!==t)}var sn=e=>({ok:!1,reason:e}),cn=`除数不能为 0:除以零没有结果,请换一个非零的除数。`,ln=new Set([`/`,`%`]),un=e=>e!==void 0&&e>=`0`&&e<=`9`,dn=e=>e!==void 0&&/\s/.test(e);function fn(e){for(;dn(e.source[e.at]);)e.at+=1}function pn(e){let t=e.at,n=0;for(;un(e.source[e.at]);)e.at+=1,n+=1;if(e.source[e.at]===`.`)for(e.at+=1;un(e.source[e.at]);)e.at+=1,n+=1;if(!n)return e.at=t,sn(`「${e.source[t]}」不是有效的数字。`);let r=Number(e.source.slice(t,e.at));return Number.isFinite(r)?{ok:!0,value:r}:sn(`数字太长了,无法表示。`)}function mn(e){fn(e);let t=e.source[e.at];if(t===void 0)return sn(`算式不完整:末尾还缺一个数字或右括号。`);if(t===`(`){e.at+=1;let t=_n(e);return t.ok?(fn(e),e.source[e.at]===`)`?(e.at+=1,t):sn(`括号不匹配:少了一个右括号「)」。`)):t}return un(t)||t===`.`?pn(e):sn(`无法识别的字符「${t}」。`)}function hn(e){let t=mn(e);if(!t.ok||(fn(e),e.source[e.at]!==`^`))return t;e.at+=1;let n=F(e);if(!n.ok)return n;let r=t.value**n.value;return Number.isFinite(r)?{ok:!0,value:r}:sn(`结果超出了可计算的数值范围。`)}function F(e){fn(e);let t=e.source[e.at];if(t!==`+`&&t!==`-`)return hn(e);e.at+=1;let n=F(e);if(!n.ok)return n;let r=t===`-`?-n.value:n.value;return Number.isFinite(r)?{ok:!0,value:r}:sn(`结果超出了可计算的数值范围。`)}function gn(e){let t=F(e);if(!t.ok)return t;for(;;){fn(e);let n=e.source[e.at];if(n!==`*`&&!ln.has(n))return t;e.at+=1;let r=F(e);if(!r.ok)return r;if(ln.has(n)&&r.value===0)return sn(cn);let i=n===`*`?t.value*r.value:n===`%`?t.value%r.value:t.value/r.value;if(!Number.isFinite(i))return sn(`结果超出了可计算的数值范围。`);t={ok:!0,value:i}}}function _n(e){let t=gn(e);if(!t.ok)return t;for(;;){fn(e);let n=e.source[e.at];if(n!==`+`&&n!==`-`)return t;e.at+=1;let r=gn(e);if(!r.ok)return r;let i=n===`+`?t.value+r.value:t.value-r.value;if(!Number.isFinite(i))return sn(`结果超出了可计算的数值范围。`);t={ok:!0,value:i}}}function vn(e){let t=e.trim();if(!t)return sn(`请输入算式。`);if(t.length>200)return sn(`算式太长了,请控制在 200 个字符以内。`);let n={source:t,at:0},r=_n(n);return r.ok?(fn(n),n.at===t.length?Number.isFinite(r.value)?r:sn(`结果不是有限数字。`):sn(`「${t[n.at]}」后面的内容无法识别,请检查是否漏了运算符。`)):r}function yn(e){return String(!Number.isFinite(e)||Number.isInteger(e)&&Math.abs(e)<1e21?e:Number(e.toPrecision(12)))}function bn(e){let t=e.trim();if(!t)return{ok:!1,reason:`请输入时间戳或日期。`};if(/^\d+$/.test(t)){let e=Number(t);if(!Number.isFinite(e))return{ok:!1,reason:`这个数字太大,无法表示。`};let n=t.length<=11?e*1e3:e;return!Number.isFinite(n)||Math.abs(n)>864e13?{ok:!1,reason:`这个时间戳超出了日期能表示的范围。`}:{ok:!0,ms:n}}let n=Date.parse(t);return Number.isNaN(n)?{ok:!1,reason:`看不懂这个日期。试试 1757000000、2026-10-08 或 2026-10-08T09:00:00。`}:{ok:!0,ms:n}}var I=e=>String(Math.abs(e)).padStart(2,`0`);function xn(e){let t=new Date(e);return Number.isNaN(t.getTime())?null:{local:`${t.getFullYear()}-${I(t.getMonth()+1)}-${I(t.getDate())} ${I(t.getHours())}:${I(t.getMinutes())}:${I(t.getSeconds())}`,iso:t.toISOString(),seconds:String(Math.floor(e/1e3)),milliseconds:String(e)}}var Sn=e=>e.nativeEvent.isComposing||e.keyCode===229,Cn=`super-pi.theme`,wn=[{id:`dracula`,name:`Dracula`,blurb:`紫调暗色,饱满对比,适合夜间专注。`},{id:`monokai`,name:`Monokai`,blurb:`暖调暗色,橙红点缀,经典编辑器风格。`},{id:`solarized-light`,name:`Solarized Light`,blurb:`暖黄米色调,柔和护眼,适合长时间阅读。`},{id:`github-light`,name:`GitHub Light`,blurb:`冷调浅色,中性清晰,日常办公首选。`}],Tn=`dracula`,En=e=>typeof e==`string`&&wn.some(t=>t.id===e);function Dn(){try{let e=window.localStorage.getItem(Cn);if(!e)return Tn;let t=JSON.parse(e),n=typeof t==`string`?t:t?.id;return En(n)?n:Tn}catch{return Tn}}function On(e){document.documentElement.setAttribute(`data-theme`,e);try{window.localStorage.setItem(Cn,JSON.stringify({id:e,version:1}))}catch{}}function kn(){let[e,t]=(0,p.useState)(Tn);return(0,p.useEffect)(()=>{t(Dn());let e=e=>{if(e.key===`super-pi.theme`&&e.newValue)try{let n=JSON.parse(e.newValue),r=typeof n==`string`?n:n?.id;En(r)&&(document.documentElement.setAttribute(`data-theme`,r),t(r))}catch{}};return window.addEventListener(`storage`,e),()=>window.removeEventListener(`storage`,e)},[]),{theme:e,setTheme:(0,p.useCallback)(e=>{On(e),t(e)},[])}}function An({theme:e,onChooseTheme:t,onClose:n}){let r=wn.find(t=>t.id===e);return(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`div`,{className:`settings-scope`,children:`主题只影响这台设备的显示,不改变任何内容;切换立即生效。`}),wn.length===0?(0,m.jsx)(`div`,{className:`theme-empty`,"data-theme-empty":`1`,children:`没有可用的主题。界面将继续使用默认配色,你可以在主题文件恢复后重试。`}):(0,m.jsx)(`div`,{className:`theme-options`,role:`radiogroup`,"aria-label":`主题`,children:wn.map(n=>{let r=n.id===e;return(0,m.jsxs)(`button`,{type:`button`,className:`theme-option`,role:`radio`,"aria-checked":r,"aria-pressed":r,"data-theme-option":n.id,"data-theme-current":r?`1`:`0`,onClick:()=>t(n.id),children:[(0,m.jsx)(`span`,{className:`theme-preview-strip`,"data-preview":n.id,"aria-hidden":`true`}),(0,m.jsxs)(`span`,{className:`theme-option-name`,children:[n.name,r&&(0,m.jsx)(`span`,{className:`theme-option-current`,children:`当前 ✓`})]}),(0,m.jsx)(`span`,{className:`theme-option-blurb`,children:n.blurb})]},n.id)})}),(0,m.jsx)(`div`,{className:`picker-actions`,children:(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,"data-theme-close":`1`,onClick:n,children:`完成`})}),(0,m.jsxs)(`div`,{className:`settings-scope theme-current-line`,children:[`当前:`,r?`${r.name}(${r.id})`:`默认`]})]})}function jn({userNickname:e,onSetUserNickname:t,onClose:n}){return(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`div`,{className:`settings-scope`,children:`昵称会显示在消息时间戳旁和头像上,替代默认的「你」。留空恢复默认。`}),(0,m.jsxs)(`div`,{className:`general-nickname`,children:[(0,m.jsx)(`label`,{className:`general-nickname-label`,htmlFor:`general-nickname-input`,children:`昵称`}),(0,m.jsx)(`input`,{id:`general-nickname-input`,type:`text`,className:`general-nickname-input`,"data-general-nickname":`1`,maxLength:24,placeholder:`你`,value:e??``,onChange:e=>t?.(e.target.value)}),(0,m.jsx)(`span`,{className:`general-nickname-hint`,"data-general-nickname-preview":`1`,title:`当前显示`,children:e?.trim()?`将显示为:${e.trim()}`:`将显示为:你`})]}),(0,m.jsx)(`div`,{className:`picker-actions`,children:(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,onClick:n,children:`完成`})})]})}function Mn({context:e}){let[t,n]=(0,p.useState)(``),[r,i]=(0,p.useState)(``),[a,o]=(0,p.useState)(!1),s=e.agentCatalog,c=e.subagentCatalog,l=s?.agents??[],u=c?.agents??[],d=[...l,...u],f=[...s?.problems??[],...c?.problems??[]],h=[...new Set([...s?.directories??(s?.directory?[s.directory]:[]),...e.customAgentDirs??[]])],g=e.customAgentDirs??[],_=r=>{if(r.preventDefault(),e.pendingSettings)return;let a=t.trim();a&&(g.includes(a)?i(`该路径已存在于自定义目录列表中`):(e.onSetCustomAgentDirs?.([...g,a]),n(``),i(``)))},v=t=>{let n=g.filter(e=>e!==t);e.onSetCustomAgentDirs?.(n)};return(0,m.jsxs)(`div`,{className:`settings-agents-container`,children:[(0,m.jsxs)(`div`,{className:`settings-agents-header`,children:[(0,m.jsx)(`p`,{className:`settings-agents-desc`,children:`Super Pi 支持多目录发现智能体。系统会按优先级自上而下合并扫描目录;若存在同名配置,高优先级目录的文件将覆盖低优先级目录。`}),(0,m.jsx)(`button`,{type:`button`,className:`settings-agent-rescan-btn`,onClick:()=>{o(!0),e.onRescanAgents?.(),setTimeout(()=>o(!1),600)},disabled:a,title:`重新扫描所有目录中的智能体定义文件`,children:a?`正在扫描…`:`🔄 立即重新扫描`})]}),(0,m.jsxs)(`section`,{className:`settings-section`,children:[(0,m.jsx)(`h4`,{className:`settings-section-title`,children:`发现路径清单(按优先级自高到低)`}),(0,m.jsx)(`div`,{className:`settings-dirs-list`,children:h.length===0?(0,m.jsx)(`div`,{className:`settings-empty-hint`,children:`暂无活跃的发现路径`}):h.map((t,n)=>{let r=g.includes(t),i=t.includes(`.super_pi`)||t.includes(`.super-pi`)||t.includes(`.pi`),a=!r&&!i,o=d.filter(e=>e.sourceDir===t).length;return(0,m.jsxs)(`div`,{className:`settings-dir-row`,children:[(0,m.jsxs)(`div`,{className:`settings-dir-info`,children:[(0,m.jsxs)(`span`,{className:`settings-dir-priority`,children:[`#`,n+1]}),(0,m.jsx)(`span`,{className:`settings-dir-path`,title:t,children:t}),(0,m.jsx)(`span`,{className:`settings-dir-tag`,children:r?`自定义`:i?`用户全局`:a?`内置`:`环境`}),(0,m.jsxs)(`span`,{className:`settings-dir-count`,children:[`(发现 `,o,` 个智能体)`]})]}),r&&(0,m.jsx)(`button`,{type:`button`,className:`settings-dir-remove-btn`,disabled:e.pendingSettings,onClick:()=>v(t),title:`移除此自定义扫描路径`,children:`移除`})]},t)})})]}),(0,m.jsxs)(`section`,{className:`settings-section`,children:[(0,m.jsx)(`h4`,{className:`settings-section-title`,children:`添加自定义路径`}),(0,m.jsxs)(`form`,{onSubmit:_,className:`settings-add-dir-form`,children:[(0,m.jsx)(`input`,{type:`text`,className:`settings-dir-input`,value:t,onChange:e=>{n(e.target.value),r&&i(``)},onKeyDown:e=>{e.key===`Enter`&&Sn(e)&&e.preventDefault()},placeholder:`输入本地目录绝对路径或 ~/ 开头路径,如 D:\\agents 或 ~/.super_pi/agents`}),(0,m.jsx)(`button`,{type:`submit`,className:`settings-dir-add-btn`,disabled:e.pendingSettings||!t.trim(),children:`添加目录`})]}),r&&(0,m.jsx)(`div`,{className:`settings-dir-error`,children:r})]}),f.length>0&&(0,m.jsxs)(`section`,{className:`settings-section settings-problems-section`,children:[(0,m.jsxs)(`h4`,{className:`settings-problems-title`,children:[`⚠️ 发现 `,f.length,` 个文件解析问题`]}),(0,m.jsx)(`div`,{className:`settings-problems-list`,children:f.map((e,t)=>(0,m.jsxs)(`div`,{className:`settings-problem-row`,children:[(0,m.jsx)(`span`,{className:`settings-problem-file`,children:e.file}),`: `,e.message]},t))})]}),(0,m.jsxs)(`section`,{className:`settings-section`,children:[(0,m.jsxs)(`h4`,{className:`settings-section-title`,children:[`已发现的智能体(共 `,d.length,` 个)`]}),d.length===0?(0,m.jsxs)(`div`,{className:`settings-empty-hint`,children:[`当前未发现任何智能体。请在上述目录中添加 `,(0,m.jsx)(`code`,{children:`.md`}),` 文件(需包含 YAML frontmatter)。`]}):(0,m.jsx)(`div`,{className:`settings-agent-grid`,children:d.map(e=>(0,m.jsxs)(`div`,{className:`settings-agent-card`,children:[(0,m.jsxs)(`div`,{className:`settings-agent-card-header`,children:[(0,m.jsx)(`span`,{className:`settings-agent-name`,children:e.name}),(0,m.jsxs)(`span`,{className:`settings-agent-id`,children:[`(`,e.id,`)`]}),(0,m.jsx)(`span`,{className:`settings-agent-mode-badge ${e.mode}`,children:e.mode===`primary`?`主智能体`:`子智能体`})]}),e.sourceDir&&(0,m.jsxs)(`div`,{className:`settings-agent-source`,title:e.sourceDir,children:[`来源:`,e.sourceDir]}),e.description&&(0,m.jsx)(`div`,{className:`settings-agent-desc`,children:e.description}),(0,m.jsxs)(`div`,{className:`settings-agent-footer`,children:[(0,m.jsx)(`span`,{className:`settings-agent-tools`,children:e.toolSummary}),e.model&&(0,m.jsxs)(`span`,{className:`settings-agent-model`,children:[e.model.provider,`/`,e.model.modelId]})]})]},`${e.sourceDir}-${e.id}`))})]}),(0,m.jsx)(`div`,{className:`picker-actions`,children:(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,onClick:e.onClose,children:`完成`})})]})}function Nn(e){return(0,m.jsx)(Mn,{context:e})}function Pn({context:e}){let[t,n]=(0,p.useState)(``),[r,i]=(0,p.useState)(``),[a,o]=(0,p.useState)(),s=e=>{try{return new RegExp(e,`i`),!1}catch{return!0}},c=s(t),l=s(r),u=e.modelFilterRules??[];return(0,m.jsxs)(`section`,{"data-model-rules":`1`,className:`settings-panel-container`,children:[(0,m.jsx)(`div`,{className:`settings-agents-header`,children:(0,m.jsxs)(`p`,{className:`settings-agents-desc`,children:[(0,m.jsx)(`strong`,{children:`按工作区约束可用模型。`}),`当工作区名称匹配指定正则表达式时,会话中仅可选择符合模型正则的模型(匹配模型名称或 `,(0,m.jsx)(`code`,{children:`provider/modelId`}),`)。多条命中规则必须全部满足。`]})}),(0,m.jsxs)(`section`,{className:`settings-section`,children:[(0,m.jsxs)(`h4`,{className:`settings-section-title`,children:[`已配置的过滤规则(共 `,u.length,` 条)`]}),u.length===0?(0,m.jsx)(`div`,{className:`settings-empty-hint`,children:`暂无模型过滤规则。所有工作区默认可使用全部已配置模型。`}):(0,m.jsx)(`div`,{className:`settings-dirs-list`,children:u.map(t=>{let r=a===t.id;return(0,m.jsxs)(`div`,{className:`settings-rule-card ${r?`editing`:``}`,children:[(0,m.jsx)(`div`,{className:`settings-rule-info`,children:(0,m.jsxs)(`div`,{className:`settings-rule-patterns`,children:[(0,m.jsxs)(`span`,{className:`settings-rule-pill`,children:[(0,m.jsx)(`span`,{className:`settings-rule-pill-tag`,children:`工作区`}),(0,m.jsx)(`code`,{className:`settings-rule-code`,title:t.workspacePattern,children:t.workspacePattern})]}),(0,m.jsx)(`span`,{className:`settings-rule-arrow`,"aria-hidden":`true`,children:`→`}),(0,m.jsxs)(`span`,{className:`settings-rule-pill`,children:[(0,m.jsx)(`span`,{className:`settings-rule-pill-tag`,children:`允许模型`}),(0,m.jsx)(`code`,{className:`settings-rule-code`,title:t.modelPattern,children:t.modelPattern})]})]})}),(0,m.jsxs)(`div`,{className:`settings-rule-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`settings-rule-edit-btn`,onClick:()=>{o(t.id),n(t.workspacePattern),i(t.modelPattern)},children:r?`编辑中`:`编辑`}),(0,m.jsx)(`button`,{type:`button`,className:`settings-dir-remove-btn`,onClick:()=>{a===t.id&&(o(void 0),n(``),i(``)),e.onSetModelRules?.(u.filter(e=>e.id!==t.id))},children:`删除`})]})]},t.id)})})]}),(0,m.jsxs)(`section`,{className:`settings-section`,children:[(0,m.jsx)(`h4`,{className:`settings-section-title`,children:a?`编辑过滤规则`:`添加新规则`}),(0,m.jsxs)(`fieldset`,{disabled:e.pendingSettings,className:`model-rules-fields settings-rule-fieldset`,children:[(0,m.jsxs)(`div`,{className:`settings-rule-form-grid`,children:[(0,m.jsxs)(`label`,{className:`settings-rule-input-group`,children:[(0,m.jsx)(`span`,{className:`settings-rule-label-text`,children:`工作区名正则表达式`}),(0,m.jsx)(`input`,{type:`text`,"data-rule-workspace":`1`,className:`settings-dir-input`,value:t,"aria-invalid":c,placeholder:`如 ^proj- 或 my-workspace`,onChange:e=>n(e.target.value)})]}),(0,m.jsxs)(`label`,{className:`settings-rule-input-group`,children:[(0,m.jsx)(`span`,{className:`settings-rule-label-text`,children:`允许的模型名正则表达式`}),(0,m.jsx)(`input`,{type:`text`,"data-rule-model":`1`,className:`settings-dir-input`,value:r,"aria-invalid":l,placeholder:`如 anthropic/.* 或 gpt-4o|claude`,onChange:e=>i(e.target.value)})]})]}),(c||l)&&(0,m.jsx)(`div`,{className:`settings-dir-error`,role:`alert`,children:`⚠️ 正则表达式语法不合法,请检查后修正。`}),(0,m.jsxs)(`div`,{className:`settings-rule-form-buttons`,children:[(0,m.jsx)(`button`,{type:`button`,"data-rule-save":`1`,className:`settings-dir-add-btn`,disabled:!t.trim()||!r.trim()||c||l,onClick:()=>{let s={id:a??nn(),workspacePattern:t.trim(),modelPattern:r.trim()};e.onSetModelRules?.([...u.filter(e=>e.id!==s.id),s]),n(``),i(``),o(void 0)},children:a?`保存修改`:`添加规则`}),a&&(0,m.jsx)(`button`,{type:`button`,className:`settings-rule-cancel-btn`,onClick:()=>{o(void 0),n(``),i(``)},children:`取消编辑`})]})]})]}),(0,m.jsx)(`div`,{className:`picker-actions`,children:(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,onClick:e.onClose,children:`完成`})})]})}var Fn=[{id:`appearance`,label:`外观`,render:An},{id:`general`,label:`通用`,render:jn},{id:`memory`,label:`主动记忆`,render:e=>(0,m.jsx)(Jt,{workspaceId:e.workspaceId,onClose:e.onClose})},{id:`agents`,label:`智能体`,render:Nn},{id:`model-rules`,label:`模型过滤`,render:e=>(0,m.jsx)(Pn,{context:e})}],In=Fn[0]?.id;function Ln({workspaceId:e,theme:t,onChooseTheme:n,onClose:r,agentCatalog:i,subagentCatalog:a,modelFilterRules:o,onSetModelRules:s,customAgentDirs:c,pendingSettings:l,onSetCustomAgentDirs:u,onRescanAgents:d,userNickname:f,onSetUserNickname:h}){let[g,_]=(0,p.useState)(In);(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&!e.defaultPrevented&&r()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[r]);let v=Fn.find(e=>e.id===g)??Fn[0];return(0,m.jsx)(`div`,{className:`overlay`,onClick:r,children:(0,m.jsxs)(`div`,{className:`picker settings-window`,"data-theme-dialog":`1`,"data-settings-window":`1`,onClick:e=>e.stopPropagation(),children:[(0,m.jsx)(`div`,{className:`settings-window-head`,children:(0,m.jsx)(`h2`,{children:`设置`})}),(0,m.jsxs)(`div`,{className:`settings-window-body`,children:[(0,m.jsxs)(`nav`,{className:`settings-nav`,role:`tablist`,"aria-orientation":`vertical`,"aria-label":`设置分类`,"data-settings-nav":`1`,children:[(0,m.jsx)(`div`,{className:`settings-nav-label`,children:`分类`}),Fn.map(e=>(0,m.jsx)(`button`,{type:`button`,id:`settings-tab-${e.id}`,className:`settings-category`,role:`tab`,"aria-selected":e.id===v?.id,"aria-controls":`settings-panel`,tabIndex:e.id===v?.id?0:-1,"data-settings-category":e.id,onClick:()=>_(e.id),children:e.label},e.id))]}),v&&(0,m.jsxs)(`div`,{className:`settings-panel`,id:`settings-panel`,role:`tabpanel`,"aria-labelledby":`settings-tab-${v.id}`,"data-settings-panel":v.id,children:[(0,m.jsx)(`h3`,{className:`settings-panel-title`,"data-settings-panel-title":v.id,children:v.label}),v.render({workspaceId:e,theme:t,onChooseTheme:n,onClose:r,agentCatalog:i,subagentCatalog:a,modelFilterRules:o,onSetModelRules:s,customAgentDirs:c,pendingSettings:l,onSetCustomAgentDirs:u,onRescanAgents:d,userNickname:f,onSetUserNickname:h})]})]})]})})}var Rn={char:` `,fg:null,bg:null,bold:!1,dim:!1,underline:!1,inverse:!1},zn=[0,95,135,175,215,255];function Bn(e){if(e<16)return`var(--term-ansi-${e})`;if(e<232){let t=e-16;return`rgb(${zn[Math.floor(t/36)%6]},${zn[Math.floor(t/6)%6]},${zn[t%6]})`}let t=8+(e-232)*10;return`rgb(${t},${t},${t})`}var Vn=class{cols;rows;utf8;last;state=`ground`;params=``;intermediates=``;oscBuffer=``;screen;alt=null;cursorRow=0;cursorCol=0;savedRow=0;savedCol=0;fg=null;bg=null;bold=!1;dim=!1;underline=!1;inverse=!1;scrollTop=0;scrollBottom;autowrap=!0;wrapPending=!1;dirty=!0;constructor(e,t){this.cols=e,this.rows=t,this.cols=Gn(e,20,500,80),this.rows=Gn(t,5,200,24),this.scrollBottom=this.rows-1,this.screen=Un(this.cols,this.rows),this.utf8=new Uint8Array,this.last=this.screen}write(e){let t=this.utf8.length?Wn(this.utf8,e):e,n=this.pendingByteCount(t);this.utf8=n?t.slice(t.length-n):new Uint8Array;let r=n?t.subarray(0,t.length-n):t,i=new TextDecoder(`utf-8`,{fatal:!1}).decode(r);for(let e of i)this.step(e);return this.dirty?this.snapshot():this.last}pendingByteCount(e){for(let t=1;t<=3&&t<=e.length;t++){let n=e[e.length-t];if((n&192)!=128)return(n<128?1:(n&224)==192?2:(n&240)==224?3:4)>t?t:0}return 0}snapshot(){return this.dirty=!1,this.last=this.screen.map(e=>e.map(e=>({...e}))),this.last}cell(e,t){let n=this.screen[e];if(!n)return{...Rn};let r=n[t];if(r)return r;for(;n.length<=t;)n.push({...Rn});return n[t]}put(e){this.wrapPending&&this.autowrap&&(this.cursorCol=0,this.lineFeed(),this.wrapPending=!1);let t=this.cell(this.cursorRow,this.cursorCol);t.char=e,t.fg=this.fg,t.bg=this.bg,t.bold=this.bold,t.dim=this.dim,t.underline=this.underline,t.inverse=this.inverse,this.cursorCol+1>=this.cols?this.wrapPending=!0:this.cursorCol+=1}lineFeed(){this.wrapPending=!1,this.cursorRow===this.scrollBottom?(this.screen.splice(this.scrollTop,1),this.screen.splice(this.scrollBottom,0,Hn(this.cols))):this.cursorRow<this.rows-1&&(this.cursorRow+=1),this.dirty=!0}carriageReturn(){this.cursorCol=0,this.wrapPending=!1,this.dirty=!0}clampCursor(){this.cursorRow=Math.max(0,Math.min(this.rows-1,this.cursorRow)),this.cursorCol=Math.max(0,Math.min(Math.max(0,this.cols-1),this.cursorCol))}resize(e,t){let n=Gn(e,20,500,this.cols),r=Gn(t,5,200,this.rows);if(n===this.cols&&r===this.rows)return;let i=[];for(let e=0;e<r;e++){let t=this.screen[e]??[],r=[];for(let e=0;e<n;e++)r.push(t[e]?{...t[e]}:{...Rn});i.push(r)}this.cols=n,this.rows=r,this.screen=i,this.alt=this.alt?this.alt.slice(0,r).map(e=>e.slice(0,n)):null,this.scrollTop=0,this.scrollBottom=r-1,this.cursorRow=Math.min(this.cursorRow,r-1),this.cursorCol=Math.min(this.cursorCol,n-1),this.dirty=!0}step(e){switch(this.state){case`ground`:return this.stepGround(e);case`escape`:return this.stepEscape(e);case`csi`:return this.stepCsi(e);case`osc`:return this.stepOsc(e);case`oscEscape`:return this.stepOscEscape(e)}}stepGround(e){let t=e.charCodeAt(0);if(e===`\x1B`)this.state=`escape`,this.params=``,this.intermediates=``;else switch(e){case`\r`:return this.carriageReturn();case`
50
50
  `:case`\v`:case`\f`:return this.lineFeed();case`\b`:this.cursorCol=Math.max(0,this.cursorCol-1),this.wrapPending=!1,this.dirty=!0;return;case` `:{let e=Math.min(this.cols-1,(Math.floor(this.cursorCol/8)+1)*8);this.cursorCol=e,this.dirty=!0;return}case`\x07`:return;default:if(t<32||t===127)return;this.put(e),this.dirty=!0;return}}stepEscape(e){e===`\x1B`?(this.params=``,this.intermediates=``):e===`[`?(this.state=`csi`,this.params=``,this.intermediates=``):e===`]`||e===`P`||e===`^`||e===`_`||e===`X`?(this.state=`osc`,this.oscBuffer=``):e===`7`?(this.savedRow=this.cursorRow,this.savedCol=this.cursorCol,this.state=`ground`):e===`8`?(this.cursorRow=this.savedRow,this.cursorCol=this.savedCol,this.clampCursor(),this.state=`ground`):e===`c`?(this.screen=Un(this.cols,this.rows),this.cursorRow=0,this.cursorCol=0,this.dirty=!0,this.state=`ground`):e===`D`?(this.lineFeed(),this.state=`ground`):e===`E`?(this.carriageReturn(),this.lineFeed(),this.state=`ground`):(e===`M`&&(this.cursorRow===this.scrollTop?(this.screen.splice(this.scrollBottom,1),this.screen.splice(this.scrollTop,0,Hn(this.cols))):this.cursorRow=Math.max(0,this.cursorRow-1),this.dirty=!0),this.state=`ground`)}stepCsi(e){let t=e.charCodeAt(0);t>=48&&t<=63?this.params+=e:t>=32&&t<=47?this.intermediates+=e:(this.state=`ground`,this.dispatchCsi(e,this.params,this.intermediates))}dispatchCsi(e,t,n){let r=t.startsWith(`?`),i=r?t.slice(1):t,a=i.length?i.split(`;`).map(e=>Number.parseInt(e===``?`0`:e,10)):[],o=(e,t)=>{let n=a[e];return typeof n==`number`&&Number.isFinite(n)?n:t},s=e=>a[e]===0?1:o(e,1);if(r){let t=o(0,0),n=e===`h`;if(t===1049||t===47||t===1047){this.switchAlternateScreen(n);return}if(t===25)return;if(t===7){this.autowrap=n;return}}else{switch(e){case`A`:this.cursorRow=Math.max(0,this.cursorRow-s(0)),this.wrapPending=!1;break;case`B`:this.cursorRow=Math.min(this.rows-1,this.cursorRow+s(0)),this.wrapPending=!1;break;case`C`:this.cursorCol=Math.min(this.cols-1,this.cursorCol+s(0)),this.wrapPending=!1;break;case`D`:this.cursorCol=Math.max(0,this.cursorCol-s(0)),this.wrapPending=!1;break;case`G`:this.cursorCol=Math.max(0,Math.min(this.cols-1,s(0)-1)),this.wrapPending=!1;break;case`H`:case`f`:this.cursorRow=Math.max(0,Math.min(this.rows-1,s(0)-1)),this.cursorCol=Math.max(0,Math.min(this.cols-1,s(1)-1)),this.wrapPending=!1;break;case`J`:this.eraseInDisplay(o(0,0));break;case`K`:this.eraseInLine(o(0,0));break;case`L`:this.insertLines(s(0));break;case`M`:this.deleteLines(s(0));break;case`P`:this.deleteCharacters(s(0));break;case`@`:this.insertCharacters(s(0));break;case`X`:{let e=Math.min(s(0),this.cols-this.cursorCol);for(let t=0;t<e;t++)this.cell(this.cursorRow,this.cursorCol+t).char=` `;break}case`d`:this.cursorRow=Math.max(0,Math.min(this.rows-1,s(0)-1));break;case`r`:{let e=Math.max(0,s(0)-1),t=Math.min(this.rows-1,o(1,this.rows)-1);e<t&&(this.scrollTop=e,this.scrollBottom=t),this.cursorRow=0,this.cursorCol=0;break}case`s`:this.savedRow=this.cursorRow,this.savedCol=this.cursorCol;break;case`u`:this.cursorRow=this.savedRow,this.cursorCol=this.savedCol;break;case`m`:this.selectGraphicRendition(a);return;default:return}this.clampCursor(),this.dirty=!0}}switchAlternateScreen(e){e&&!this.alt?(this.alt=this.screen,this.screen=Un(this.cols,this.rows),this.cursorRow=0,this.cursorCol=0):!e&&this.alt&&(this.screen=this.alt,this.alt=null,this.cursorRow=this.savedRow,this.cursorCol=this.savedCol),this.clampCursor(),this.dirty=!0}eraseInDisplay(e){if(e===0){this.eraseInLine(0);for(let e=this.cursorRow+1;e<this.rows;e++)this.screen[e]=Hn(this.cols)}else if(e===1){this.eraseInLine(1);for(let e=0;e<this.cursorRow;e++)this.screen[e]=Hn(this.cols)}else this.screen=Un(this.cols,this.rows);this.dirty=!0}eraseInLine(e){let t=this.screen[this.cursorRow];if(!t)return;let n=e===0?this.cursorCol:0,r=e===1?this.cursorCol:this.cols-1;for(let e=n;e<=r;e++)t[e]&&(t[e].char=` `);this.dirty=!0}insertLines(e){if(!(this.cursorRow<this.scrollTop||this.cursorRow>this.scrollBottom)){for(let t=0;t<e;t++)this.screen.splice(this.scrollBottom,1),this.screen.splice(this.cursorRow,0,Hn(this.cols));this.dirty=!0}}deleteLines(e){if(!(this.cursorRow<this.scrollTop||this.cursorRow>this.scrollBottom)){for(let t=0;t<e;t++)this.screen.splice(this.cursorRow,1),this.screen.splice(this.scrollBottom,0,Hn(this.cols));this.dirty=!0}}deleteCharacters(e){let t=this.screen[this.cursorRow];if(t){for(t.splice(this.cursorCol,e);t.length<this.cols;)t.push({...Rn});this.dirty=!0}}insertCharacters(e){let t=this.screen[this.cursorRow];if(t){for(let n=0;n<e;n++)t.splice(this.cursorCol,0,{...Rn});t.length=this.cols,this.dirty=!0}}selectGraphicRendition(e){if(!e.length)this.fg=null,this.bg=null,this.bold=!1,this.dim=!1,this.underline=!1,this.inverse=!1;else{for(let t=0;t<e.length;t++){let n=e[t];if(n===0)this.fg=null,this.bg=null,this.bold=!1,this.dim=!1,this.underline=!1,this.inverse=!1;else if(n===1)this.bold=!0;else if(n===2)this.dim=!0;else if(n===4)this.underline=!0;else if(n===7)this.inverse=!0;else if(n===22)this.bold=!1,this.dim=!1;else if(n===24)this.underline=!1;else if(n===27)this.inverse=!1;else if(n>=30&&n<=37)this.fg=`var(--term-ansi-${n-30})`;else if(n===39)this.fg=null;else if(n>=40&&n<=47)this.bg=`var(--term-ansi-${n-40})`;else if(n===49)this.bg=null;else if(n>=90&&n<=97)this.fg=`var(--term-ansi-${n-90+8})`;else if(n>=100&&n<=107)this.bg=`var(--term-ansi-${n-100+8})`;else if(n===38||n===48){let r=n===38?`fg`:`bg`;e[t+1]===5?(this[r]=Bn(e[t+2]??0),t+=2):e[t+1]===2&&(this[r]=`rgb(${e[t+2]??0},${e[t+3]??0},${e[t+4]??0})`,t+=4)}}this.dirty=!0}}stepOsc(e){e===`\x07`?this.state=`ground`:e===`\x1B`?this.state=`oscEscape`:this.oscBuffer.length<4096&&(this.oscBuffer+=e)}stepOscEscape(e){this.state=e===`\\`?`ground`:`osc`}cursor(){return{row:this.cursorRow,col:this.cursorCol}}toLines(){return this.screen.map(e=>e.slice(0,this.cols).map(e=>e.char).join(``).replace(/\s+$/,``))}};function Hn(e){let t=Array(e);for(let n=0;n<e;n++)t[n]={...Rn};return t}function Un(e,t){let n=Array(t);for(let r=0;r<t;r++)n[r]=Hn(e);return n}function Wn(e,t){let n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}function Gn(e,t,n,r){return Number.isFinite(e)?Math.min(n,Math.max(t,Math.floor(e))):r}var Kn=`terminal-right-rail`,qn={checking:`检查中`,unavailable:`未启用`,starting:`启动中`,running:`运行中`,exited:`已结束`},Jn=5,Yn=e=>`${Kn}-${e}`,Xn=e=>({id:Yn(e),title:String(e),phase:`checking`});function Zn(e){let{key:t,ctrlKey:n,altKey:r,metaKey:i}=e;if(!i){if(n&&!r&&t.length===1){let e=t.toLowerCase().charCodeAt(0);if(e>=96&&e<=122)return String.fromCharCode(e-96);if(t===` `)return`\0`}if(r&&t.length===1)return`\x1b${t}`;switch(t){case`Enter`:return`\r`;case`Backspace`:return``;case`Tab`:return` `;case`Escape`:return`\x1B`;case`ArrowUp`:return`\x1B[A`;case`ArrowDown`:return`\x1B[B`;case`ArrowRight`:return`\x1B[C`;case`ArrowLeft`:return`\x1B[D`;case`Home`:return`\x1B[H`;case`End`:return`\x1B[F`;case`PageUp`:return`\x1B[5~`;case`PageDown`:return`\x1B[6~`;case`Delete`:return`\x1B[3~`;case`Insert`:return`\x1B[2~`;case`F1`:return`\x1BOP`;case`F2`:return`\x1BOQ`;case`F3`:return`\x1BOR`;case`F4`:return`\x1BOS`;case`F5`:return`\x1B[15~`;case`F6`:return`\x1B[17~`;case`F7`:return`\x1B[18~`;case`F8`:return`\x1B[19~`;case`F9`:return`\x1B[20~`;case`F10`:return`\x1B[21~`;case`F11`:return`\x1B[23~`;case`F12`:return`\x1B[24~`;default:return}}}function Qn(e){let t=[];for(let n of e){let e=t[t.length-1];e&&$n(e.cell,n)?e.text+=n.char:t.push({text:n.char,cell:n})}return t}function $n(e,t){return e.fg===t.fg&&e.bg===t.bg&&e.bold===t.bold&&e.dim===t.dim&&e.underline===t.underline&&e.inverse===t.inverse}function er({workspaceId:e,workspaceLabel:t,connected:n,sendCommand:r,registerSink:i}){let[a,o]=(0,p.useState)(()=>[Xn(1)]),[s,c]=(0,p.useState)(()=>Yn(1)),l=(0,p.useRef)(new Set),u=(0,p.useRef)(!1),d=(0,p.useRef)(new Map),f=(0,p.useRef)(new Map),[h,g]=(0,p.useState)(()=>({grid:new Vn(80,24).snapshot(),cursor:{row:0,col:0}})),_=a.find(e=>e.id===s)??a[0],v=(0,p.useRef)(s);v.current=s;let y=h.grid,b=h.cursor,x=_.phase,S=_.reason??``,C=_.exitCode,w=(0,p.useRef)(null),T=(0,p.useRef)({cols:80,rows:24,charWidth:9,lineHeight:16}),ee=(0,p.useRef)(null),E=(0,p.useCallback)(()=>{let e=w.current,t=ee.current;e&&t&&(t.value&&=``,t.focus(),document.activeElement!==t&&e.focus())},[]),te=(0,p.useCallback)(()=>{let e=ee.current;if(!e)return``;let t=e.value;return e.value=``,t},[]);(0,p.useEffect)(()=>{let e=!1;return fetch(`/api/terminal`).then(e=>e.json()).then(t=>{e||(t.enabled?(o(e=>e.map(e=>e.phase===`checking`?{...e,phase:`starting`}:e)),u.current=!0):o(e=>e.map(e=>({...e,phase:`unavailable`,reason:t.reason??`终端未启用。`}))))}).catch(()=>{e||o(e=>e.map(e=>({...e,phase:`unavailable`,reason:`无法读取终端状态,请确认服务端已启动。`})))}),()=>{e=!0}},[]);let ne=e=>{let t=new Vn(T.current.cols,T.current.rows);d.current.set(e,t);let n={grid:t.snapshot(),cursor:{row:0,col:0}};f.current.set(e,n),v.current===e&&g(n)},re=(0,p.useCallback)(e=>{if(e.type===`terminal.output`){let t=d.current.get(e.sessionId);if(!t)return;let n={grid:t.write(new TextEncoder().encode(e.data)),cursor:t.cursor()};f.current.set(e.sessionId,n),v.current===e.sessionId&&g(n)}else e.type===`terminal.exit`?(l.current.delete(e.sessionId),ne(e.sessionId),o(t=>t.map(t=>t.id===e.sessionId?{...t,phase:`exited`,exitCode:e.exitCode}:t))):e.type===`terminal.error`?(l.current.delete(e.sessionId),ne(e.sessionId),o(t=>t.map(t=>t.id===e.sessionId?{...t,phase:`exited`,reason:e.message}:t))):e.type===`terminal.status`&&!e.enabled&&o(t=>t.map(t=>({...t,phase:`unavailable`,reason:e.reason??`终端未启用。`})))},[]),ie=(0,p.useRef)(i);ie.current=i;let ae=(0,p.useRef)(r);ae.current=r,(0,p.useEffect)(()=>(ie.current(re),()=>{ie.current(void 0)}),[re]),(0,p.useEffect)(()=>()=>{for(let e of l.current)ae.current({type:`terminal.close`,sessionId:e});l.current.clear()},[]);let oe=(0,p.useCallback)(t=>{if(!e)return;let n=new Vn(T.current.cols,T.current.rows);d.current.set(t,n),f.current.set(t,{grid:n.snapshot(),cursor:n.cursor()}),g({grid:n.snapshot(),cursor:{row:0,col:0}}),o(e=>e.map(e=>e.id===t?{...e,phase:`starting`,exitCode:void 0,reason:``}:e)),E(),r({type:`terminal.open`,sessionId:t,workspaceId:e,cols:T.current.cols,rows:T.current.rows})?(l.current.add(t),o(e=>e.map(e=>e.id===t?{...e,phase:`running`}:e))):o(e=>e.map(e=>e.id===t?{...e,phase:`exited`,reason:`与 Super Pi 服务的连接尚未就绪。`}:e))},[E,r,e]);(0,p.useEffect)(()=>{if(u.current)for(let e of a)(e.phase===`starting`||e.phase===`checking`)&&!l.current.has(e.id)&&oe(e.id)},[a,oe]);let se=(0,p.useRef)(n);(0,p.useEffect)(()=>{let e=n&&!se.current;se.current=n,e&&o(e=>e.map(e=>e.phase===`exited`&&!e.deliberate?{...e,phase:`starting`,reason:``}:e))},[n]);let D=(0,p.useCallback)(()=>{let e=new Set(a.map(e=>e.title)),t=1;for(;e.has(String(t));)t+=1;let n=Xn(t);o(e=>e.length>=Jn?e:[...e,n]),c(n.id),g(f.current.get(n.id)??{grid:new Vn(T.current.cols,T.current.rows).snapshot(),cursor:{row:0,col:0}}),E()},[E,a]),ce=(0,p.useCallback)(e=>{c(e);let t=f.current.get(e);t&&g(t),E()},[E]),le=(0,p.useCallback)(e=>{r({type:`terminal.close`,sessionId:e}),l.current.delete(e),d.current.delete(e),f.current.delete(e),o(t=>{let n=t.findIndex(t=>t.id===e),r=t.filter(t=>t.id!==e);if(!r.length)return t;if(v.current===e){let e=r[Math.min(n,r.length-1)];c(e.id);let t=f.current.get(e.id);t&&g(t)}return r})},[r]);(0,p.useLayoutEffect)(()=>{let e=w.current;if(!e)return;let t=()=>{let t=e.querySelector(`[data-terminal-probe]`),n=t?.getBoundingClientRect().width||7.2,i=t?.getBoundingClientRect().height||16,a=getComputedStyle(e),o=e.clientWidth-parseFloat(a.paddingLeft)-parseFloat(a.paddingRight),s=e.clientHeight-parseFloat(a.paddingTop)-parseFloat(a.paddingBottom),c=Math.max(20,Math.min(500,Math.floor(o/n))),u=Math.max(5,Math.min(200,Math.floor(s/i)));if(c===T.current.cols&&u===T.current.rows&&n===T.current.charWidth&&i===T.current.lineHeight)return;T.current={cols:c,rows:u,charWidth:n,lineHeight:i};for(let[e,t]of d.current)t.resize(c,u),l.current.has(e)&&r({type:`terminal.resize`,sessionId:e,cols:c,rows:u});let f=v.current,p=d.current.get(f);p&&g({grid:p.snapshot(),cursor:p.cursor()})};t();let n=new ResizeObserver(t);return n.observe(e),()=>n.disconnect()},[r]);let ue=e=>{if((e.ctrlKey||e.metaKey)&&(e.key===`c`||e.key===`v`))return;let t=Zn(e);t!==void 0&&(e.preventDefault(),r({type:`terminal.input`,sessionId:v.current,data:t}))},de=()=>{let e=te();e&&r({type:`terminal.input`,sessionId:v.current,data:e})},fe=e=>{let t=e.clipboardData.getData(`text`);t&&(e.preventDefault(),r({type:`terminal.input`,sessionId:v.current,data:t.replace(/\r?\n/g,`\r`)}))},pe=(0,p.useCallback)(()=>{let e=v.current;r({type:`terminal.close`,sessionId:e}),l.current.delete(e),o(t=>t.map(t=>t.id===e?{...t,phase:`exited`,deliberate:!0}:t))},[r]),me=(0,p.useMemo)(()=>y.map((e,t)=>({rowIndex:t,runs:Qn(e)})),[y]);return x===`checking`?(0,m.jsx)(`p`,{className:`rail-empty`,"data-terminal-phase":`checking`,children:`正在检查终端状态…`}):x===`unavailable`?(0,m.jsxs)(`div`,{className:`terminal-unavailable`,"data-terminal-phase":`unavailable`,children:[(0,m.jsx)(`p`,{className:`terminal-unavailable-reason`,children:S}),(0,m.jsx)(`p`,{className:`rail-empty`,children:`终端默认关闭,因为它会把服务器的 shell 暴露给能访问该端口的设备。开启后仅接受本机与局域网地址的连接,且工作目录限定为已配置的工作区。`})]}):(0,m.jsxs)(`div`,{className:`terminal-panel`,"data-terminal-panel":`1`,"data-terminal-phase":x,children:[(0,m.jsxs)(`div`,{className:`terminal-strip`,role:`tablist`,"aria-label":`终端会话`,"data-terminal-strip":`1`,children:[a.map(e=>(0,m.jsxs)(`div`,{className:`terminal-tab${e.id===s?` is-active`:``}`,children:[(0,m.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":e.id===s,className:`terminal-tab-btn`,"data-terminal-slot":e.id,onClick:()=>ce(e.id),title:qn[e.phase],children:[(0,m.jsx)(`span`,{className:`terminal-tab-dot terminal-tab-dot-${e.phase}`,"aria-hidden":`true`}),e.title]}),(0,m.jsx)(`button`,{type:`button`,className:`terminal-tab-close`,"aria-label":`关闭终端 ${e.title}`,"data-terminal-slot-close":e.id,onClick:()=>le(e.id),children:`×`})]},e.id)),a.length<Jn&&(0,m.jsx)(`button`,{type:`button`,className:`terminal-strip-add`,"data-terminal-add":`1`,onClick:D,title:`新建终端`,"aria-label":`新建终端`,children:`+`})]}),(0,m.jsxs)(`div`,{className:`terminal-toolbar`,children:[(0,m.jsxs)(`span`,{className:`terminal-status terminal-status-${x}`,"data-terminal-status":x,children:[(0,m.jsx)(`span`,{className:`terminal-status-dot`,"aria-hidden":`true`}),qn[x]]}),(0,m.jsxs)(`span`,{className:`terminal-toolbar-right`,children:[t&&(0,m.jsx)(`span`,{className:`terminal-cwd`,"data-terminal-hint":`1`,title:t,children:t}),x===`running`?(0,m.jsx)(`button`,{type:`button`,className:`terminal-close-btn`,"data-terminal-close":`1`,title:`关闭这个终端会话`,"aria-label":`关闭这个终端会话`,onClick:pe,children:`结束会话`}):(0,m.jsx)(`button`,{type:`button`,className:`terminal-restart-btn`,"data-terminal-restart":`1`,onClick:()=>oe(s),children:`重新打开`})]})]}),S&&(0,m.jsx)(`p`,{className:`tool-error`,"data-terminal-error":`1`,role:`alert`,children:S}),x===`exited`&&C!==void 0&&C!==0&&(0,m.jsxs)(`p`,{className:`tool-error`,"data-terminal-exit":`1`,children:[`会话已结束,退出码 `,C,`。`]}),(0,m.jsxs)(`div`,{className:`terminal-surface`,"data-terminal-surface":`1`,ref:w,tabIndex:0,role:`textbox`,"aria-label":`终端输出与输入`,"aria-multiline":`true`,onKeyDown:ue,onPaste:fe,onMouseDown:e=>{e.preventDefault(),E()},children:[(0,m.jsx)(`textarea`,{ref:ee,className:`terminal-input-capture`,"aria-label":`终端输入`,spellCheck:!1,autoCapitalize:`off`,autoCorrect:`off`,rows:1,defaultValue:``,onChange:de,onFocus:()=>w.current?.classList.add(`is-input-focused`),onBlur:()=>w.current?.classList.remove(`is-input-focused`)}),(0,m.jsx)(`span`,{className:`terminal-probe`,"data-terminal-probe":`1`,"aria-hidden":`true`,children:`M`}),(0,m.jsxs)(`div`,{className:`terminal-grid`,"data-terminal-grid":`1`,"aria-hidden":`true`,children:[me.map(({rowIndex:e,runs:t})=>(0,m.jsx)(`div`,{className:`terminal-row`,children:t.map((e,t)=>(0,m.jsx)(`span`,{className:[`terminal-cell`,e.cell.bold?`is-bold`:``,e.cell.dim?`is-dim`:``,e.cell.underline?`is-underline`:``,e.cell.inverse?`is-inverse`:``].filter(Boolean).join(` `),style:{...e.cell.fg?{color:e.cell.fg}:{},...e.cell.bg?{background:e.cell.bg}:{}},children:e.text},t))},e)),(0,m.jsx)(`span`,{className:`terminal-cursor`,style:{left:b.col*T.current.charWidth,top:b.row*T.current.lineHeight,width:T.current.charWidth,height:T.current.lineHeight},"aria-hidden":`true`})]})]})]})}var tr={workspaces:[],conversations:[],subagentRuns:[],queuedMessages:[]},nr=e=>e.nativeEvent.isComposing||e.keyCode===229,rr=e=>{if(typeof e!=`string`||e.length===0)return``;let t=new Date(e);return Number.isNaN(t.getTime())?``:new Intl.DateTimeFormat(`zh-CN`,{hour:`2-digit`,minute:`2-digit`}).format(t)},ir=6e4,ar=60*ir,or=24*ar,sr=7*or,cr=30*or,lr=365*or,ur=(e,t)=>{let n=new Date(e);if(Number.isNaN(n.getTime()))return``;let r=t-n.getTime();return r<3e4?`now`:r<ar?`${Math.max(1,Math.floor(r/ir))}m ago`:r<or?`${Math.floor(r/ar)}h ago`:r<sr?`${Math.floor(r/or)}d ago`:r<cr?`${Math.floor(r/sr)}w ago`:r<lr?`${Math.floor(r/cr)}mo ago`:`${Math.floor(r/lr)}y ago`},dr=(e,t)=>e.updatedAt<t.updatedAt?1:e.updatedAt>t.updatedAt?-1:0,fr=(e,t)=>{let n=e.archivedAt??``,r=t.archivedAt??``;return n<r?1:n>r?-1:0},pr=e=>{if(!e)return``;let t=new Date(e);return Number.isNaN(t.getTime())?``:`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`},mr=1e3,hr=3e4,gr=async e=>{try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}let t=document.createElement(`textarea`);t.value=e,t.setAttribute(`readonly`,``),t.style.cssText=`position:fixed;top:0;left:0;width:1px;height:1px;padding:0;border:0;opacity:0;pointer-events:none`,document.body.appendChild(t);try{return t.select(),t.setSelectionRange(0,t.value.length),document.execCommand(`copy`)}catch{return!1}finally{t.remove()}};function _r({workspace:e,x:t,y:n,notify:r,onClose:i}){let a=(0,p.useRef)(null),[o,s]=(0,p.useState)({left:t,top:n});(0,p.useLayoutEffect)(()=>{let e=a.current;if(!e)return;let r=e.getBoundingClientRect();s({left:Math.max(8,Math.min(t,window.innerWidth-r.width-8)),top:Math.max(8,Math.min(n,window.innerHeight-r.height-8))})},[t,n]),(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&(e.stopPropagation(),i())},t=e=>{a.current?.contains(e.target)||i()},n={x:window.scrollX,y:window.scrollY,width:window.innerWidth,height:window.innerHeight},r=()=>{(window.scrollX!==n.x||window.scrollY!==n.y)&&i()},o=()=>{(window.innerWidth!==n.width||window.innerHeight!==n.height)&&i()};return window.addEventListener(`keydown`,e,!0),window.addEventListener(`pointerdown`,t,!0),window.addEventListener(`scroll`,r,!0),window.addEventListener(`resize`,o),()=>{window.removeEventListener(`keydown`,e,!0),window.removeEventListener(`pointerdown`,t,!0),window.removeEventListener(`scroll`,i,!0),window.removeEventListener(`resize`,o)}},[i]);let c=async()=>{let t=await gr(e.path);i(),t?r(`已复制工作区路径:${e.path}`,`info`):r(`复制失败:浏览器拒绝了剪贴板写入,可以手动选中路径复制`,`error`)};return(0,m.jsxs)(`div`,{ref:a,className:`ctx-menu`,role:`menu`,"data-ctx-menu":`1`,"data-workspace-id":e.id,style:{left:o.left,top:o.top},onContextMenu:e=>e.preventDefault(),onPointerDown:e=>e.stopPropagation(),children:[(0,m.jsx)(`button`,{type:`button`,role:`menuitem`,className:`ctx-menu-item`,"data-copy-workspace-path":e.id,onClick:()=>void c(),children:`复制工作区路径`}),(0,m.jsx)(`div`,{className:`ctx-menu-hint`,"aria-hidden":`true`,children:e.path})]})}function vr({conversation:e,x:t,y:n,notify:r,onRename:i,onClose:a}){let o=(0,p.useRef)(null),[s,c]=(0,p.useState)({left:t,top:n});(0,p.useLayoutEffect)(()=>{let e=o.current;if(!e)return;let r=e.getBoundingClientRect();c({left:Math.max(8,Math.min(t,window.innerWidth-r.width-8)),top:Math.max(8,Math.min(n,window.innerHeight-r.height-8))})},[t,n]),(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&(e.stopPropagation(),a())},t=e=>{o.current?.contains(e.target)||a()},n={x:window.scrollX,y:window.scrollY,width:window.innerWidth,height:window.innerHeight},r=()=>{(window.scrollX!==n.x||window.scrollY!==n.y)&&a()},i=()=>{(window.innerWidth!==n.width||window.innerHeight!==n.height)&&a()};return window.addEventListener(`keydown`,e,!0),window.addEventListener(`pointerdown`,t,!0),window.addEventListener(`scroll`,r,!0),window.addEventListener(`resize`,i),()=>{window.removeEventListener(`keydown`,e,!0),window.removeEventListener(`pointerdown`,t,!0),window.removeEventListener(`scroll`,a,!0),window.removeEventListener(`resize`,i)}},[a]);let l=async()=>{let t=await gr(e.id);a(),t?r(`已复制会话 ID:${e.id}`,`info`):r(`复制失败:浏览器拒绝了剪贴板写入`,`error`)};return(0,m.jsxs)(`div`,{ref:o,className:`ctx-menu`,role:`menu`,"data-ctx-menu":`1`,"data-session-id":e.id,style:{left:s.left,top:s.top},onContextMenu:e=>e.preventDefault(),onPointerDown:e=>e.stopPropagation(),children:[(0,m.jsx)(`button`,{type:`button`,role:`menuitem`,className:`ctx-menu-item`,"data-copy-session-id":e.id,onClick:()=>void l(),children:`复制会话 ID`}),(0,m.jsx)(`button`,{type:`button`,role:`menuitem`,className:`ctx-menu-item`,"data-ai-rename-session":e.id,onClick:()=>{a(),i()},children:`AI 智能重命名`}),(0,m.jsx)(`div`,{className:`ctx-menu-hint`,"aria-hidden":`true`,children:e.id})]})}var yr={model:`模型`,thinking:`思考`,agent:`智能体`,subagent:`子智能体`},br={model:`模型`,thinking:`思考等级`,agent:`智能体`,subagent:`子智能体`},xr={model:`选择模型`,thinking:`选择思考等级`,agent:`选择智能体`,subagent:`选择子智能体`},Sr=`图文`,Cr=e=>e.imageInput?Sr:void 0,wr=e=>e.normalize(`NFKD`).replace(/\p{M}/gu,``).toLowerCase(),Tr=(e,t)=>{let n=wr(t.trim());return n?e.filter(e=>wr(`${e.label} ${e.detail??``} ${e.badge??``}`).includes(n)):e};function Er({onClose:e,onCreate:t}){let[n,r]=(0,p.useState)(),[i,a]=(0,p.useState)(!0),[o,s]=(0,p.useState)(),[c,l]=(0,p.useState)(``),[u,d]=(0,p.useState)(``),f=async e=>{a(!0),s(void 0);try{let t=await fetch(`/api/fs/dirs${e?`?path=${encodeURIComponent(e)}`:``}`),n=await t.json();if(!t.ok)throw Error(n.error??`HTTP ${t.status}`);let i=n;r(i),d(i.path),l(i.suggestedName)}catch(e){s(e instanceof Error?e.message:`目录加载失败`)}finally{a(!1)}};(0,p.useEffect)(()=>{f()},[]),(0,p.useEffect)(()=>{let t=t=>{t.key===`Escape`&&e()};return window.addEventListener(`keydown`,t),()=>window.removeEventListener(`keydown`,t)},[e]);let h=e=>{e?.preventDefault();let t=u.trim();t&&f(t)};return(0,m.jsx)(`div`,{className:`overlay`,onClick:e,children:(0,m.jsxs)(`div`,{className:`picker workspace-picker`,onClick:e=>e.stopPropagation(),children:[(0,m.jsx)(`h2`,{children:`选择工作区目录`}),(0,m.jsxs)(`form`,{className:`picker-path-bar`,onSubmit:h,children:[(0,m.jsx)(`input`,{className:`picker-path-input`,value:u,onChange:e=>d(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(nr(e)?e.preventDefault():(e.preventDefault(),h()))},placeholder:`输入或粘贴目录绝对路径(如 D:\\projects 或 ~/code)`}),(0,m.jsx)(`button`,{type:`submit`,className:`picker-go-btn`,disabled:!u.trim()||i,children:`跳转`}),(0,m.jsx)(`button`,{type:`button`,className:`picker-up-btn`,disabled:!n?.parent||i,onClick:()=>n?.parent&&void f(n.parent),title:`返回上一级目录`,children:`上一级`})]}),n?.drives?.length||n?.homeDir?(0,m.jsxs)(`div`,{className:`picker-shortcuts`,children:[(0,m.jsx)(`span`,{className:`picker-shortcuts-label`,children:`快捷位置:`}),n?.homeDir&&(0,m.jsx)(`button`,{type:`button`,className:`picker-shortcut`,onClick:()=>void f(`~`),title:n.homeDir,children:`🏠 主目录`}),n?.drives?.map(e=>(0,m.jsx)(`button`,{type:`button`,className:`picker-shortcut`,onClick:()=>void f(e),children:e},e))]}):null,(0,m.jsxs)(`div`,{className:`picker-list`,children:[i&&(0,m.jsx)(`div`,{className:`picker-status`,children:`加载中…`}),!i&&o&&(0,m.jsxs)(`div`,{className:`picker-error`,children:[o,(0,m.jsx)(`button`,{onClick:()=>void f(n?.path||u),children:`重试`})]}),!i&&!o&&n?.entries.map(e=>(0,m.jsxs)(`button`,{className:`picker-dir`,onClick:()=>void f(e.path),children:[`📁 `,e.name]},e.path)),!i&&!o&&n&&n.entries.length===0&&(0,m.jsx)(`div`,{className:`picker-status`,children:`此目录下没有子目录`}),!i&&!o&&n?.truncated&&(0,m.jsx)(`div`,{className:`picker-status`,children:`目录过多,仅显示前 2000 项`})]}),(0,m.jsxs)(`label`,{className:`picker-name`,children:[`工作区名称`,(0,m.jsx)(`input`,{value:c,onChange:e=>l(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(nr(e)?e.preventDefault():(e.preventDefault(),c.trim()&&n&&t(c.trim(),n.path)))},placeholder:`工作区名称`})]}),(0,m.jsxs)(`div`,{className:`picker-actions`,children:[(0,m.jsx)(`button`,{className:`picker-cancel`,onClick:e,children:`取消`}),(0,m.jsx)(`button`,{className:`picker-ok`,disabled:!c.trim()||i||!!o||!n,onClick:()=>n&&t(c.trim(),n.path),children:`使用这个目录`})]})]})})}var Dr={timeout:`扩展提问超时,已取消`,aborted:`运行已停止,扩展提问已取消`,expired:`运行已结束,扩展提问已取消`},Or=10;function kr(e,t){if(!t)return`正在从 Pi 读取${e}列表…`;if(t.source!==`pi`)return`读取 Pi ${e}列表失败:${t.error??`未知原因`}`}function Ar(e){if(!e)return`正在读取 agent 列表…`;if(e.source!==`directory`)return`读取 agent 列表失败:${e.error??`未知原因`}`;if(e.problems.length)return`${e.problems.length} 个 agent 文件无法使用:${e.problems.map(e=>e.file).join(`、`)}`;if(!e.agents.length)return`${e.directory} 下没有 primary agent`}var jr=e=>Math.abs(e)<1e3?String(e):`${(e/1e3).toFixed(1).replace(/\.0$/,``)}k`;function Mr(e){if(e===void 0||!Number.isFinite(e))return``;let t=Math.max(0,e);return t<1e3?`${Math.round(t)}ms`:t<6e4?`${(t/1e3).toFixed(1)}s`:`${Math.floor(t/6e4)}分${Math.round(t%6e4/1e3)}秒`}var Nr={dispatch:`派发时(子进程还没起来)`,prompt:`派活时(Pi 拒收了任务)`,turn:`生成回答时`,tool:`调用工具时`,settle:`收尾时`};function Pr({call:e,index:t,expanded:n,onToggle:r}){let i=e.args??[];return(0,m.jsxs)(`div`,{className:`call-block call-block-${e.status??`running`}`,"data-call-block":t,"data-call-tool":e.label,"data-call-detail":e.detail??void 0,"data-call-status":e.status??`running`,"data-call-expanded":n?`true`:`false`,"data-call-arg-count":i.length,"data-thinking-at":e.thinkingAt,children:[(0,m.jsx)(`button`,{type:`button`,className:`call-fold-edge`,"data-call-fold-edge":t,"aria-expanded":n,"aria-label":`${n?`折叠`:`展开`} ${e.label} 调用`,title:n?`折叠这次调用`:`展开这次调用`,onClick:r}),(0,m.jsxs)(`div`,{className:`call-head-row`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`call-head`,"data-call-head":t,"aria-expanded":n,onClick:r,children:[(0,m.jsx)(`span`,{className:`call-chevron`,"aria-hidden":`true`,children:n?`▾`:`▸`}),(0,m.jsx)(`span`,{className:`call-glyph`,"aria-hidden":`true`,children:e.status===`error`?`✕`:e.status===`ok`?`✓`:`·`}),(0,m.jsx)(`span`,{className:`call-label`,title:e.label,children:e.label}),e.detail&&(0,m.jsx)(`span`,{className:`call-detail`,title:e.detail,children:e.detail}),e.durationMs!==void 0&&(0,m.jsx)(`span`,{className:`call-duration`,children:Mr(e.durationMs)}),e.preparing&&(0,m.jsx)(`span`,{className:`call-preparing`,"data-call-preparing":t,children:`生成参数中…`})]}),n&&(0,m.jsx)(`button`,{type:`button`,className:`call-fold-top`,"data-call-fold-top":t,onClick:r,children:`收起`})]}),!n&&e.live&&(0,m.jsx)(`div`,{className:`call-live-preview`,"data-call-live-preview":t,title:`展开查看完整输出`,children:e.live.split(`
51
51
  `).slice(-1)[0]}),n&&(0,m.jsx)(`div`,{className:`call-args`,"data-call-args":i.length,children:i.length===0?(0,m.jsx)(`div`,{className:`call-arg call-arg-empty`,"data-call-arg-empty":t,children:`(这次调用没有可显示的参数)`}):i.map(e=>(0,m.jsxs)(`div`,{className:`call-arg`,"data-call-arg":e.key,"data-call-arg-value":e.value,children:[(0,m.jsx)(`span`,{className:`call-arg-key`,children:e.key}),(0,m.jsx)(`span`,{className:`call-arg-value`,title:e.value,children:e.value})]},e.key))}),n&&(e.result||e.live)&&(0,m.jsxs)(`div`,{className:`call-result`,"data-call-result":t,"data-call-result-state":e.result?`done`:`running`,children:[(0,m.jsxs)(`div`,{className:`call-result-head`,children:[(0,m.jsx)(`span`,{className:`call-result-title`,children:e.result&&!e.live?`执行结果`:e.result?`执行结果 · 完整输出`:`执行中 · 输出预览`}),e.result?(0,m.jsx)(Fr,{index:t,text:e.live??e.result.text}):null]}),(0,m.jsx)(`pre`,{className:`call-result-body`,"data-call-result-body":t,children:e.live??e.result?.text}),e.result?.truncated&&e.live&&(0,m.jsx)(`div`,{className:`call-result-truncated`,children:`(服务端已截断到末尾 8 KB;上方显示的是执行中累积的完整输出)`}),e.result?.truncated&&!e.live&&(0,m.jsxs)(`div`,{className:`call-result-truncated`,children:[`(输出过长,已保留末尾关键内容`,e.result.droppedLines?`,省略前 ${e.result.droppedLines} 行`:``,`)`]})]})]})}function Fr({index:e,text:t}){let[n,r]=(0,p.useState)(!1);return(0,m.jsx)(`button`,{type:`button`,className:`call-result-copy`,"data-call-result-copy":e,onClick:async()=>{await gr(t)&&(r(!0),window.setTimeout(()=>r(!1),1600))},children:n?`已复制`:`复制`})}function Ir({messageId:e,nestedCount:t,preview:n,expanded:r,onToggle:i,children:a}){return(0,m.jsxs)(`section`,{className:`thinking-block`,"data-thinking-block":e,"data-thinking-expanded":r?`true`:`false`,"data-thinking-calls":t,children:[(0,m.jsx)(`button`,{type:`button`,className:`call-fold-edge`,"data-thinking-fold-edge":e,"aria-expanded":r,"aria-label":`${r?`折叠`:`展开`}这段思考`,title:r?`折叠这段思考`:`展开这段思考`,onClick:i}),(0,m.jsxs)(`button`,{type:`button`,className:`thinking-head`,"data-thinking-head":e,"aria-expanded":r,title:r?`折叠这段思考`:`展开这段思考`,onClick:i,children:[(0,m.jsx)(`span`,{className:`thinking-chevron`,"aria-hidden":`true`,children:r?`▾`:`▸`}),(0,m.jsx)(`span`,{className:`thinking-label`,children:`思考`}),t>0&&(0,m.jsxs)(`span`,{className:`thinking-count`,"data-thinking-count":t,children:[t,` 次工具调用`]}),!r&&n&&(0,m.jsx)(`span`,{className:`thinking-preview`,title:n,children:n})]}),r&&(0,m.jsx)(`div`,{className:`thinking-body`,"data-thinking-body":e,children:a})]})}function Lr({run:e,name:t,expanded:n,onToggle:r,onStop:i,stopping:a}){let o=e.toolCalls??[],s=n?o:o.slice(-10),c=o.length-s.length,l=e.status===`running`,u=e.output??``,d=e.dispatchedBy===`model`,[f,h]=(0,p.useState)(0);(0,p.useEffect)(()=>{if(!l)return;h(Date.now());let e=window.setInterval(()=>h(Date.now()),200);return()=>window.clearInterval(e)},[l,e.startedAt]);let g=Date.parse(e.startedAt),_=l&&f>0&&Number.isFinite(g)?f-g:void 0,v=[...o].reverse().find(e=>e.status===void 0)?.label;return(0,m.jsxs)(`article`,{className:`subagent-card subagent-${e.status}${d?` subagent-by-model`:``}`,"data-subagent-card":e.id,"data-subagent-status":e.status,"data-subagent-agent":e.agentId,"data-subagent-tools":o.length,"data-subagent-dispatched-by":d?`model`:`human`,children:[(0,m.jsxs)(`div`,{className:`subagent-head`,children:[(0,m.jsx)(`span`,{className:`subagent-badge`,children:`子智能体`}),d&&(0,m.jsxs)(`span`,{className:`subagent-source`,"data-subagent-source":`model`,title:`primary 模型自己决定派这个子智能体去干活`,children:[(0,m.jsx)(`span`,{className:`subagent-source-glyph`,"aria-hidden":`true`,children:`⚡`}),`模型自主派发`]}),(0,m.jsx)(`strong`,{className:`subagent-name`,"data-subagent-name":`1`,children:t||e.agentId}),(0,m.jsx)(`span`,{className:`subagent-status`,"data-subagent-status-text":`1`,children:l?`运行中`:e.status===`completed`?`已完成`:e.status===`stopped`?`已停止`:`失败`}),l?_!==void 0&&(0,m.jsxs)(`span`,{className:`subagent-clock`,"data-subagent-elapsed":`1`,"data-subagent-elapsed-ms":_,children:[`已运行 `,Mr(_)]}):e.durationMs!==void 0&&(0,m.jsxs)(`span`,{className:`subagent-duration`,"data-subagent-duration":`1`,"data-subagent-duration-ms":e.durationMs,children:[`用时 `,Mr(e.durationMs)]}),l&&(0,m.jsx)(`button`,{type:`button`,className:`subagent-stop`,"data-subagent-stop":e.id,disabled:a,onClick:i,children:`停止`})]}),l&&e.activity&&(0,m.jsxs)(`div`,{className:`subagent-activity`,"data-subagent-activity":e.activity.kind,"data-subagent-activity-at":e.activity.at,children:[(0,m.jsx)(`span`,{className:`subagent-activity-dot`,"aria-hidden":`true`}),e.activity.kind===`tool`?`正在调用 ${e.activity.label??`工具`}`:`正在思考…`]}),(0,m.jsxs)(`div`,{className:`subagent-task`,"data-subagent-task":`1`,children:[d&&(0,m.jsx)(`span`,{className:`subagent-task-source`,"data-subagent-model-task":`1`,children:`模型给的任务`}),e.task]}),(0,m.jsx)(`div`,{className:`subagent-output`,"data-subagent-output":`1`,"data-live-length":u.length,children:u||(l?`…`:e.outputTruncated?`(已按 50 KB 上限截断,浏览器仍收到完整内容)`:`(没有输出)`)}),e.failure&&(0,m.jsxs)(`div`,{className:`subagent-failure`,"data-subagent-failure":`1`,"data-subagent-failure-stage":e.failure.stage,children:[(0,m.jsx)(`span`,{className:`subagent-failure-label`,children:`卡在`}),(0,m.jsx)(`span`,{className:`subagent-failure-stage`,"data-subagent-failure-text":`1`,children:Nr[e.failure.stage]}),e.failure.stage===`tool`&&v&&(0,m.jsxs)(`span`,{className:`subagent-failure-tool`,"data-subagent-failure-tool":`1`,children:[`· 正在跑 `,v]}),o.length>0&&(0,m.jsxs)(`span`,{className:`subagent-failure-progress`,children:[`· 死前已尝试 `,o.length,` 次工具调用`]})]}),e.error&&(0,m.jsx)(`div`,{className:`subagent-error`,"data-subagent-error":`1`,children:e.error}),o.length>0&&(0,m.jsxs)(`div`,{className:`subagent-tools`,"data-subagent-tool-count":o.length,children:[c>0&&!n&&(0,m.jsxs)(`button`,{type:`button`,className:`subagent-more`,"data-subagent-hidden":c,onClick:r,children:[`… 另有 `,c,` 条工具调用`]}),s.map((e,t)=>(0,m.jsxs)(`div`,{className:`subagent-tool${e.status?` subagent-tool-${e.status}`:``}`,"data-subagent-tool":e.label,"data-subagent-tool-detail":e.detail??void 0,"data-subagent-tool-status":e.status??`running`,children:[(0,m.jsx)(`span`,{className:`subagent-tool-glyph`,"aria-hidden":`true`,children:e.status===`error`?`✕`:e.status===`ok`?`✓`:`·`}),(0,m.jsx)(`span`,{className:`subagent-tool-label`,title:e.label,children:e.label}),e.detail&&(0,m.jsx)(`span`,{className:`subagent-tool-detail`,title:e.detail,children:e.detail}),e.durationMs!==void 0&&(0,m.jsx)(`span`,{className:`subagent-tool-duration`,children:Mr(e.durationMs)})]},`${t}:${e.label}`))]}),e.usage&&(0,m.jsxs)(`div`,{className:`subagent-usage`,"data-subagent-usage":`1`,"data-subagent-turns":e.usage.turns,children:[(0,m.jsxs)(`span`,{children:[e.usage.turns,` turns`]}),(0,m.jsxs)(`span`,{children:[`↑`,jr(e.usage.input)]}),(0,m.jsxs)(`span`,{children:[`↓`,jr(e.usage.output)]}),e.usage.cacheRead>0&&(0,m.jsxs)(`span`,{children:[`cache↓`,jr(e.usage.cacheRead)]}),e.usage.cacheWrite>0&&(0,m.jsxs)(`span`,{children:[`cache↑`,jr(e.usage.cacheWrite)]}),e.usage.cost>0&&(0,m.jsxs)(`span`,{children:[`$`,e.usage.cost.toFixed(4)]})]}),o.length>10&&(0,m.jsx)(`button`,{type:`button`,className:`subagent-collapse`,"data-subagent-collapse":e.id,"aria-expanded":n,onClick:r,children:n?`收起`:`展开全部 ${o.length} 条`})]})}function Rr({items:e,pi:t,running:n,onCancel:r,onSteer:i,onSendQueued:a}){let[o,s]=(0,p.useState)(!1);if(!e.length)return null;let c=e.filter(e=>e.state===`sending`);return(0,m.jsxs)(`section`,{className:`queue-dock`,"data-queue-dock":`1`,"data-queue-count":e.length,"data-queue-sending":c.length,"aria-label":`排队等待发送的消息`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`queue-dock-head`,"data-queue-collapse":`1`,"aria-expanded":!o,onClick:()=>s(e=>!e),children:[(0,m.jsx)(`span`,{className:`chevron`,children:o?`▸`:`▾`}),(0,m.jsxs)(`strong`,{children:[e.length,` 条待发送`]}),e.some(e=>e.priority)&&(0,m.jsx)(`span`,{className:`queue-dock-badge`,"data-queue-badge":`priority`,children:`下一轮优先发送`}),o&&(0,m.jsx)(`span`,{className:`queue-dock-preview`,children:e[0].content})]}),!o&&(0,m.jsx)(m.Fragment,{children:(0,m.jsx)(`ul`,{className:`queue-dock-list`,children:e.map((e,t)=>{let o=e.state===`sending`,s=!!e.priority,c=e.content||(e.images?.length?`【图片】(${e.images.length})`:``);return(0,m.jsxs)(`li`,{className:`queue-row ${o?`queue-row-sending`:`queue-row-queued`}`,"data-queue-id":e.id,"data-queue-state":e.state,children:[(0,m.jsx)(`span`,{className:`queue-index`,"aria-hidden":`true`,children:t+1}),(0,m.jsx)(`span`,{className:`queue-text`,title:c,children:c}),(0,m.jsx)(`span`,{className:`queue-state`,"data-queue-state-label":s?`priority`:o?`sending`:`queued`,children:s?`下一轮优先`:`待发送`}),o?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`span`,{className:`queue-locked`,"data-queue-locked":e.id,children:s?`当前轮结束后优先发送`:`等待当前轮结束后发送`}),(0,m.jsx)(`button`,{type:`button`,className:`queue-cancel queue-copy`,"data-queue-copy":e.id,onClick:()=>void navigator.clipboard?.writeText(e.content),children:`复制`})]}):(0,m.jsxs)(m.Fragment,{children:[n?s?(0,m.jsx)(`span`,{className:`queue-locked`,children:`等待当前轮结束`}):(0,m.jsx)(`button`,{type:`button`,className:`queue-steer`,"data-queue-steer":e.id,title:`优先发送:当前轮到达下一个工具批次边界后,这条消息最先开始下一轮`,"aria-label":`让「${c}」在下一轮优先发送`,onClick:()=>i(e.id),children:`优先发送`}):(0,m.jsx)(`button`,{type:`button`,className:`queue-steer`,"data-queue-send":e.id,title:`立即作为下一轮发送这条已保存的消息`,"aria-label":`发送「${c}」作为下一轮`,onClick:()=>a(e.id),children:s?`发送下一轮`:`立即发送`}),(0,m.jsx)(`button`,{type:`button`,className:`queue-cancel`,"data-queue-cancel":e.id,title:`取消这条排队消息,它不会被发出`,"aria-label":`取消排队消息「${c}」`,onClick:()=>r(e.id),children:`取消`}),(0,m.jsx)(`button`,{type:`button`,className:`queue-cancel queue-copy`,"data-queue-copy":e.id,onClick:()=>void navigator.clipboard?.writeText(e.content),children:`复制`})]})]},e.id)})})})]})}function zr({question:e,index:t,expanded:n,onToggle:r,onAnswer:i}){return(0,m.jsxs)(`div`,{className:`call-block question-block-${e.status}`,"data-call-block":t,"data-call-tool":`question`,"data-call-detail":`提问`,"data-call-status":e.status===`answered`?`ok`:e.status===`cancelled`?`error`:`running`,"data-call-expanded":n?`true`:`false`,"data-question-block":t,children:[(0,m.jsx)(`button`,{type:`button`,className:`call-fold-edge`,"data-question-fold-edge":t,"aria-expanded":n,"aria-label":`${n?`折叠`:`展开`}提问`,title:n?`折叠这段提问`:`展开这段提问`,onClick:r}),(0,m.jsxs)(`div`,{className:`call-head-row`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`call-head`,"data-call-head":t,"aria-expanded":n,onClick:r,children:[(0,m.jsx)(`span`,{className:`call-chevron`,"aria-hidden":`true`,children:n?`▾`:`▸`}),(0,m.jsx)(`span`,{className:`call-glyph`,"aria-hidden":`true`,children:e.status===`answered`?`✓`:e.status===`cancelled`?`✕`:`?`}),(0,m.jsx)(`span`,{className:`call-label`,children:`提问`}),(0,m.jsxs)(`span`,{className:`call-detail`,children:[e.items.length,` 个问题`]})]}),n&&(0,m.jsx)(`button`,{type:`button`,className:`call-fold-top`,"data-call-fold-top":t,onClick:r,children:`收起`})]}),n&&(0,m.jsx)(`div`,{className:`question-body`,"data-question-body":t,children:(0,m.jsx)(Br,{question:e,onAnswer:i})})]})}function Br({question:e,onAnswer:t}){let[n,r]=(0,p.useState)(()=>Object.fromEntries((e.answers??[]).map(e=>[e.id,e.selected]))),[i,a]=(0,p.useState)(()=>e.answers?.map(e=>e.note??``).join(`
52
- `)??``),o=(e,t,n)=>{r(r=>{let i=r[e]??[];return n?{...r,[e]:i.includes(t)?i.filter(e=>e!==t):[...i,t]}:{...r,[e]:[t]}})},s=()=>{t(e.items.map(e=>({id:e.id,selected:n[e.id]??[],...e.extraNote&&i.trim()?{note:i.trim()}:{}})),!1)},c=e.items.some(e=>(n[e.id]??[]).length>0);return(0,m.jsxs)(`div`,{className:`question-block question-${e.status}`,"data-question-status":e.status,children:[e.items.map(t=>(0,m.jsxs)(`div`,{className:`question-item`,children:[(0,m.jsx)(`div`,{className:`question-text`,children:t.header?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`span`,{className:`question-header`,children:t.header}),(0,m.jsx)(`span`,{className:`question-sep`,children:` · `}),(0,m.jsx)(Ke,{source:t.question})]}):(0,m.jsx)(Ke,{source:t.question})}),(0,m.jsx)(`div`,{className:`question-options`,"data-question-options":t.id,children:t.options.map(r=>{let i=(n[t.id]??[]).includes(r),a=e.status!==`pending`;return(0,m.jsxs)(`label`,{className:i?`is-chosen`:``,"data-chosen":a&&i?`1`:void 0,children:[(0,m.jsx)(`input`,{type:t.multiple?`checkbox`:`radio`,name:`q-${t.id}`,checked:i,disabled:a,onChange:()=>o(t.id,r,t.multiple)}),(0,m.jsx)(qe,{source:r}),a&&i?(0,m.jsx)(`span`,{className:`question-mark`,"aria-label":`已选择`,children:`✓`}):null]},r)})}),e.status===`pending`&&t.extraNote?(0,m.jsx)(`textarea`,{value:i,placeholder:`补充说明(可选)`,onChange:e=>a(e.target.value)}):e.status===`answered`&&(e.answers??[]).some(e=>e.note)?(0,m.jsxs)(`div`,{className:`question-summary`,"data-question-summary":`1`,children:[`补充:`,(e.answers??[]).map(e=>e.note).filter(Boolean).join(`;`)]}):null,e.status===`cancelled`&&(0,m.jsx)(`div`,{className:`question-summary`,"data-question-summary":`1`,children:`已取消,没有作答。`})]},t.id)),e.status===`pending`&&(0,m.jsxs)(`div`,{className:`question-actions`,children:[(0,m.jsx)(`button`,{type:`button`,onClick:s,disabled:!c,children:`提交`}),(0,m.jsx)(`button`,{type:`button`,className:`question-skip`,onClick:()=>t([],!0),children:`跳过`})]})]})}function Vr({request:e,onAnswer:t,pending:n}){let[r,i]=(0,p.useState)(e.prefill??``),a=e.method===`input`||e.method===`editor`,o=(0,m.jsx)(`button`,{type:`button`,className:`extui-cancel`,onClick:()=>t({cancelled:!0}),children:`取消提问`});return(0,m.jsxs)(`article`,{className:`extui-card extui-${e.method}`,"data-request-id":e.requestId,"data-method":e.method,children:[(0,m.jsxs)(`div`,{className:`extui-head`,children:[(0,m.jsx)(`span`,{className:`extui-badge`,children:`扩展提问`}),e.timeoutMs!==void 0&&(0,m.jsxs)(`span`,{className:`extui-deadline`,children:[Math.round(e.timeoutMs/1e3),`s 超时`]})]}),n&&(0,m.jsx)(`p`,{role:`status`,children:`提交中…`}),(0,m.jsxs)(`fieldset`,{disabled:n,style:{border:0,padding:0,margin:0},children:[(0,m.jsx)(`h3`,{className:`extui-title`,children:e.title||e.method}),e.method===`confirm`&&(0,m.jsx)(`p`,{className:`extui-body`,children:e.message}),e.method===`select`&&(0,m.jsxs)(`div`,{className:`extui-options`,children:[(e.options??[]).map(e=>(0,m.jsx)(`button`,{type:`button`,className:`extui-option`,onClick:()=>t({value:e}),children:e},e)),(e.options??[]).length===0&&(0,m.jsx)(`p`,{className:`extui-body`,children:`扩展没有提供任何选项`})]}),a&&(0,m.jsxs)(`form`,{className:`extui-form`,onSubmit:e=>{e.preventDefault(),!n&&r.trim()&&t({value:r})},children:[e.method===`input`?(0,m.jsx)(`input`,{className:`extui-field`,value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&nr(e)&&e.preventDefault()},placeholder:e.placeholder??``}):(0,m.jsx)(`textarea`,{className:`extui-field`,value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&nr(e)&&e.preventDefault()}}),(0,m.jsxs)(`div`,{className:`extui-actions`,children:[(0,m.jsx)(`button`,{type:`submit`,className:`extui-primary`,disabled:!r.trim(),children:`提交`}),o]})]}),!a&&(0,m.jsxs)(`div`,{className:`extui-actions`,children:[e.method===`confirm`&&(0,m.jsx)(`button`,{type:`button`,className:`extui-primary`,onClick:()=>t({confirmed:!0}),children:`确认`}),e.method===`confirm`&&(0,m.jsx)(`button`,{type:`button`,className:`extui-no`,onClick:()=>t({confirmed:!1}),children:`拒绝`}),o]})]})]})}function Hr({initial:e,label:t,onCommit:n,onClose:r}){let[i,a]=(0,p.useState)(e),o=(0,p.useRef)(null),s=(0,p.useRef)(!1);(0,p.useEffect)(()=>{o.current?.focus(),o.current?.select()},[]);let c=!i.trim(),l=()=>{s.current=!0,r()},u=()=>{if(c)return;let t=i.trim();(t===e||n(t))&&l()};return(0,m.jsxs)(`div`,{className:`inline-edit`,"data-editing":`1`,children:[(0,m.jsx)(`input`,{ref:o,className:`inline-edit-input`,value:i,"aria-label":t,onChange:e=>a(e.target.value),onKeyDown:e=>{if(e.key===`Enter`){if(nr(e))return;e.preventDefault(),u()}else e.key===`Escape`&&(e.preventDefault(),l())},onBlur:()=>{s.current||r()}}),(0,m.jsx)(`button`,{type:`button`,className:`inline-edit-ok`,title:`确认重命名`,"aria-label":`确认重命名`,disabled:c,onMouseDown:e=>e.preventDefault(),onClick:u,children:`✓`}),(0,m.jsx)(`button`,{type:`button`,className:`inline-edit-cancel`,title:`取消重命名`,"aria-label":`取消重命名`,onMouseDown:e=>e.preventDefault(),onClick:l,children:`✗`})]})}function Ur({kind:e,label:t,value:n,detail:r,options:i,disabled:a,note:o,onChoose:s,scope:c}){let[l,u]=(0,p.useState)(!1),[d,f]=(0,p.useState)(``),[h,g]=(0,p.useState)(0),_=(0,p.useRef)(null),v=(0,p.useRef)(null),y=(0,p.useRef)(null),b=(0,p.useRef)(null),x=(0,p.useRef)([]),S=i.find(e=>e.selected),C=Tr(i,d),w=C.length===0?-1:Math.min(Math.max(h,0),C.length-1),T=w<0?void 0:C[w],ee=(e=!1)=>{u(!1),e&&v.current?.focus()},E=()=>{f(``),g(Math.max(i.findIndex(e=>e.selected),0)),u(!0)},te=e=>g(t=>C.length?Math.min(Math.max(t+e,0),C.length-1):0);(0,p.useEffect)(()=>{if(!l)return;let e=e=>{_.current?.contains(e.target)||ee()};return window.addEventListener(`mousedown`,e),()=>window.removeEventListener(`mousedown`,e)},[l]),(0,p.useEffect)(()=>{if(!l)return;let e=_.current,t=t=>{e?.contains(t.relatedTarget)||ee()};return e?.addEventListener(`focusout`,t),()=>e?.removeEventListener(`focusout`,t)},[l]),(0,p.useEffect)(()=>{l&&b.current?.focus()},[l]),(0,p.useEffect)(()=>{l&&w>=0&&x.current[w]?.scrollIntoView({block:`nearest`})},[l,w]),(0,p.useLayoutEffect)(()=>{if(!l)return;let e=y.current;if(!e)return;e.style.setProperty(`--picker-nudge`,`0px`),e.style.setProperty(`--picker-drop`,`0px`);let t=e.getBoundingClientRect(),n=Math.min(window.innerWidth-8-t.right,Math.max(8-t.left,0)),r=Math.min(window.innerHeight-8-t.bottom,Math.max(8-t.top,0));e.style.setProperty(`--picker-nudge`,`${n}px`),e.style.setProperty(`--picker-drop`,`${r}px`)},[l,C.length,i.length]);let ne=e=>{if(l){if(e.key===`Escape`)e.preventDefault(),ee(!0);else if(e.key===`ArrowDown`)e.preventDefault(),te(1);else if(e.key===`ArrowUp`)e.preventDefault(),te(-1);else if(e.key===`Home`)e.preventDefault(),g(0);else if(e.key===`End`)e.preventDefault(),g(Math.max(C.length-1,0));else if(e.key===`Enter`){if(nr(e))return;e.preventDefault(),T&&(s(T.key),ee(!0))}}},re=`picker-list-${c??e}`,ie=t=>`picker-option-${c??e}-${t}`;return(0,m.jsxs)(`div`,{className:`session-picker-item session-picker-${e}`,ref:_,onKeyDown:l?ne:void 0,children:[(0,m.jsxs)(`button`,{ref:v,type:`button`,className:`session-picker-trigger`,disabled:a,"aria-haspopup":`listbox`,"aria-expanded":l,title:o??`${yr[e]}:${r?`${t} · ${r}`:t}`,"data-picker-trigger":e,"data-picker-value":n??t,"data-picker-selected":i.some(e=>e.selected),onClick:()=>l?ee():E(),children:[(0,m.jsx)(`span`,{className:`session-picker-kind`,children:yr[e]}),(0,m.jsx)(`span`,{className:`session-picker-name`,children:t}),r&&(0,m.jsx)(`span`,{className:`session-picker-detail`,children:r}),(0,m.jsx)(`span`,{className:`session-picker-caret`,"aria-hidden":`true`,children:`▾`})]}),l&&(0,m.jsxs)(`div`,{className:`picker-menu`,ref:y,"data-picker-menu":e,"data-picker-highlight":w,children:[(0,m.jsxs)(`div`,{className:`picker-filter`,children:[(0,m.jsx)(`span`,{className:`picker-filter-icon`,"aria-hidden":`true`,children:`🔍`}),(0,m.jsx)(`input`,{ref:b,className:`picker-filter-input`,"data-picker-filter":e,role:`combobox`,"aria-label":`筛选${br[e]}选项`,"aria-expanded":`true`,"aria-controls":re,"aria-autocomplete":`list`,"aria-activedescendant":T?ie(w):void 0,value:d,placeholder:`输入以筛选…`,autoComplete:`off`,spellCheck:!1,onChange:e=>{f(e.target.value),g(0)}}),d&&(0,m.jsx)(`button`,{type:`button`,className:`picker-filter-clear`,"data-picker-filter-clear":e,title:`清空筛选`,"aria-label":`清空筛选`,onMouseDown:e=>e.preventDefault(),onClick:()=>{f(``),g(0),b.current?.focus()},children:`✕`})]}),S&&!C.some(e=>e.key===S.key)&&(0,m.jsxs)(`div`,{className:`picker-current`,"data-picker-current":e,"data-picker-current-value":S.key,children:[(0,m.jsx)(`span`,{className:`picker-current-label`,children:`当前`}),(0,m.jsx)(`span`,{className:`picker-current-value`,children:S.label})]}),(0,m.jsxs)(`div`,{className:`picker-list`,role:`listbox`,id:re,"aria-label":xr[e],children:[C.map((e,t)=>(0,m.jsxs)(`button`,{id:ie(t),type:`button`,role:`option`,"aria-selected":e.selected,ref:e=>{x.current[t]=e},className:`picker-option${e.selected?` selected`:``}${t===w&&!e.selected?` preview`:``}`,"data-picker-option":e.key,"data-picker-option-selected":e.selected,"data-picker-highlight":t===w,onClick:()=>{s(e.key),ee(!0)},children:[e.color&&(0,m.jsx)(`span`,{className:`picker-option-swatch`,style:{background:e.color},"aria-hidden":`true`}),(0,m.jsx)(`span`,{className:`picker-option-name`,children:e.label}),e.badge&&(0,m.jsx)(`span`,{className:`picker-option-badge`,"data-picker-option-badge":e.badge,children:e.badge}),e.detail&&(0,m.jsx)(`span`,{className:`picker-option-detail`,children:e.detail}),e.selected&&(0,m.jsx)(`span`,{className:`picker-option-mark`,"aria-hidden":`true`,children:`✓`})]},e.key)),C.length===0&&(0,m.jsx)(`div`,{className:`picker-empty`,"data-picker-empty":e,children:`没有匹配「${d.trim()}」的${br[e]}`})]})]})]})}var L=`__pi_default__`;function Wr({workspace:e,catalog:t,agents:n,onSave:r,onRename:i,onClose:a}){let[o,s]=(0,p.useState)(e.defaultAgentId),[c,l]=(0,p.useState)(e.defaultModel),[u,d]=(0,p.useState)(e.defaultThinkingLevel);(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&!e.defaultPrevented&&a()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[a]);let f=t?.source===`pi`,h=[{key:L,label:`跟随 Pi 默认`,detail:`不为新会话指定模型`,selected:c===void 0},...(t?.models??[]).map(e=>({key:`${e.provider}/${e.modelId}`,label:e.label,detail:e.detail,badge:Cr(e),selected:c?.provider===e.provider&&c.modelId===e.modelId}))],g=[{key:L,label:`跟随 Pi 默认`,detail:`不为新会话指定思考等级`,selected:u===void 0},...(t?.levels??[]).map(e=>({key:e,label:e,selected:e===u}))],_=e=>{if(e===L){l(void 0);return}let n=(t?.models??[]).find(t=>t.provider===e.split(`/`)[0]&&t.modelId===e.split(`/`)[1]);n&&l({provider:n.provider,modelId:n.modelId,label:n.label})},v=e=>d(e===L?void 0:e),y=[{key:L,label:`跟随 Pi 默认`,detail:`新会话不使用任何 agent 角色`,selected:o===void 0},...(n?.agents??[]).map(e=>({key:e.id,label:e.name,detail:e.toolSummary,color:e.color,selected:o===e.id}))];return(0,m.jsx)(`div`,{className:`overlay`,onClick:a,children:(0,m.jsxs)(`div`,{className:`picker settings-dialog`,"data-settings-dialog":`1`,onClick:e=>e.stopPropagation(),children:[(0,m.jsx)(`h2`,{children:`工作区设置`}),(0,m.jsxs)(`div`,{className:`settings-scope`,children:[e.name,` · 默认值只影响之后新建的会话,已有会话不变`]}),(0,m.jsxs)(`div`,{className:`settings-row`,children:[(0,m.jsx)(`span`,{className:`settings-label`,children:`默认智能体`}),(0,m.jsx)(Ur,{kind:`agent`,scope:`settings-agent`,label:n?.agents.find(e=>e.id===o)?.name??`跟随 Pi 默认`,value:o??L,detail:o?`${n?.agents.find(e=>e.id===o)?.toolSummary??``}`:void 0,options:y,disabled:!n||n.source===`unavailable`,note:Ar(n),onChoose:e=>s(e===L?void 0:e)})]}),(0,m.jsxs)(`div`,{className:`settings-row`,children:[(0,m.jsx)(`span`,{className:`settings-label`,children:`默认模型`}),(0,m.jsx)(Ur,{kind:`model`,scope:`settings-model`,label:c?.label??`跟随 Pi 默认`,value:c?`${c.provider}/${c.modelId}`:L,detail:c?`${c.provider}/${c.modelId}`:void 0,options:h,disabled:!f,note:kr(`模型`,t),onChoose:_})]}),(0,m.jsxs)(`div`,{className:`settings-row`,children:[(0,m.jsx)(`span`,{className:`settings-label`,children:`思考强度`}),(0,m.jsx)(Ur,{kind:`thinking`,scope:`settings-thinking`,label:u??`跟随 Pi 默认`,value:u??L,options:g,disabled:!f,note:kr(`思考等级`,t),onChoose:v})]}),(0,m.jsxs)(`div`,{className:`picker-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`settings-rename`,"data-settings-rename":`1`,onClick:i,children:`重命名工作区`}),(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,"data-settings-cancel":`1`,onClick:a,children:`取消`}),(0,m.jsx)(`button`,{type:`button`,className:`picker-ok`,"data-settings-save":`1`,onClick:()=>r(o,c,u),children:`保存`})]})]})})}var Gr=[{id:`main`,label:`主`,name:`主 tab`},{id:`tools`,label:`工具`,name:`工具 tab`},{id:`files`,label:`文件`,name:`文件 tab`},{id:`git`,label:`Git`,name:`Git tab`},{id:`terminal`,label:`终端`,name:`终端 tab`}],Kr={running:``,failed:`!`,question:`?`,unread:`•`,idle:``},qr={running:`进行中`,failed:`出错`,question:`等你回答`,unread:`有未读`,idle:`空闲`};function Jr(e,t,n){return t?`question`:e.status===`failed`?`failed`:e.status===`running`?`running`:e.updatedAt>(n??``)?`unread`:`idle`}function Yr({state:e}){let t=Kr[e];return e===`idle`?null:(0,m.jsx)(`i`,{className:`session-state session-state-${e}`,"data-session-state":e,title:qr[e],"aria-label":qr[e],children:(0,m.jsx)(`i`,{className:`session-state-mark`,"aria-hidden":`true`,children:t})})}var Xr={pending:`未开始`,in_progress:`进行中`,completed:`已完成`},Zr={pending:`○`,in_progress:`◐`,completed:`✓`},Qr={high:`高`,medium:`中`,low:`低`};function $r(e){let t=e.filter(e=>e.status!==`completed`).length;return e.length?t?`${t} 个未完成 / 共 ${e.length} 个`:`全部完成:${e.length} 个`:`0 个待办任务`}function ei({todo:e,index:t}){let n=e.id??`todo_${t}`;return(0,m.jsxs)(`li`,{className:`todo-item todo-${e.status}`,"data-todo-item":n,"data-todo-status":e.status,children:[(0,m.jsx)(`span`,{className:`todo-mark`,"aria-hidden":`true`,children:Zr[e.status]}),(0,m.jsx)(`span`,{className:`todo-text`,children:e.content}),(0,m.jsx)(`span`,{className:`todo-status`,"data-todo-status-text":`1`,children:Xr[e.status]}),e.priority&&(0,m.jsx)(`span`,{className:`todo-priority`,"data-todo-priority":e.priority,children:Qr[e.priority]})]})}function ti({tasks:e}){return(0,m.jsx)(`ul`,{className:`bg-list`,"data-bg-task-count":e.length,children:e.map((e,t)=>{let n=e.id||`bg_${t}`,r=e.status?.trim()||`未知状态`;return(0,m.jsxs)(`li`,{className:`bg-item`,"data-bg-task":n,"data-bg-task-status":e.status??`unknown`,children:[(0,m.jsx)(`span`,{className:`bg-title`,children:e.title?.trim()||e.id||`未命名任务 ${t+1}`}),(0,m.jsxs)(`span`,{className:`bg-meta`,children:[e.agentId?.trim()&&(0,m.jsx)(`span`,{className:`bg-agent`,"data-bg-agent":e.agentId,children:e.agentId}),(0,m.jsx)(`span`,{className:`bg-status`,children:r})]})]},n)})})}function ni({todos:e,tasks:t,collapsed:n,onToggle:r,workspaceId:i,workspaceLabel:a,connected:o,files:s,onOpenFile:c,activeTab:l,onSelectTab:u,sendCommand:d,registerTerminalSink:f,terminalAvailable:p}){let h=Gr;return h.some(e=>e.id===l)||h[0].id,(0,m.jsx)(`div`,{className:`right-rail${n?` right-rail-collapsed`:``}`,role:`complementary`,"aria-label":`右侧栏`,"data-right-rail":`1`,"data-right-rail-collapsed":n,children:n?(0,m.jsx)(`button`,{type:`button`,className:`right-rail-open`,"data-right-rail-open":`1`,title:`展开右侧栏`,"aria-label":`展开右侧栏`,onClick:r,children:`‹`}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`right-rail-head`,children:[(0,m.jsx)(`div`,{className:`right-tabs`,role:`tablist`,"aria-label":`右侧栏 tab`,"data-right-tab-count":h.length,children:h.map(e=>(0,m.jsx)(`button`,{type:`button`,role:`tab`,id:`right-tab-${e.id}`,className:`right-tab${e.id===l?` right-tab-active`:``}`,"data-right-tab":e.id,"aria-selected":e.id===l,"aria-controls":`right-panel-${e.id}`,title:e.name,"aria-label":e.name,onClick:()=>u(e.id),children:e.label},e.id))}),(0,m.jsx)(`button`,{type:`button`,className:`right-rail-collapse`,"data-right-rail-collapse":`1`,title:`收起右侧栏(聊天区会变宽)`,"aria-label":`收起右侧栏`,onClick:r,children:`›`})]}),Gr.filter(e=>e.id===l).map(n=>(0,m.jsxs)(`div`,{className:`right-rail-body`,role:`tabpanel`,id:`right-panel-${n.id}`,"aria-labelledby":`right-tab-${n.id}`,"data-right-panel":n.id,children:[n.id===`main`&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`rail-section-head`,children:[(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`待办任务`}),(0,m.jsx)(`span`,{className:`rail-section-count`,"data-todo-summary":`1`,children:$r(e)})]}),e.length>0?(0,m.jsx)(`ol`,{className:`todo-list`,"data-todo-count":e.length,children:e.map((e,t)=>(0,m.jsx)(ei,{todo:e,index:t},e.id??`todo_${t}`))}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-todo-empty":`1`,children:`暂无待办任务。Agent 开始处理多步骤任务时,会自己把待办写到这里。`}),(0,m.jsxs)(`div`,{className:`rail-section-head`,children:[(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`后台任务`}),(0,m.jsx)(`span`,{className:`rail-section-count`,"data-bg-summary":`1`,children:t.length?`${t.length} 个运行中`:`0 个`})]}),t.length>0?(0,m.jsx)(ti,{tasks:t}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-bg-empty":`1`,children:`当前没有正在运行的后台任务。`})]}),n.id===`tools`&&(0,m.jsx)(ai,{}),n.id===`files`&&(0,m.jsx)(Dt,{workspaceId:i,files:s,onOpenFile:c}),n.id===`git`&&(0,m.jsx)(ne,{workspaceId:i??null}),n.id===`terminal`&&(0,m.jsx)(er,{workspaceId:i,workspaceLabel:a,connected:o,sendCommand:d,registerSink:f})]},n.id))]})})}function ri(){try{if(typeof window>`u`)return Xt.slice();let e=en(window.localStorage.getItem(P));return window.localStorage.getItem(`super-pi:tools:v1`)===null?Xt.slice():e}catch{return Xt.slice()}}function ii(e){try{if(typeof window>`u`)return;window.localStorage.setItem(P,tn(e))}catch{}}function ai(){let[e,t]=(0,p.useState)(ri),[n,r]=(0,p.useState)([]),[i,a]=(0,p.useState)(),[o,s]=(0,p.useState)(!1),c=e.find(e=>e.id===i),l=(0,p.useCallback)(async()=>{try{let e=await fetch(`/api/tools`),t=await e.json();e.ok&&Array.isArray(t.tools)&&r(t.tools.filter(e=>e.enabled))}catch{}},[]);return(0,p.useEffect)(()=>{ii(e)},[e]),(0,p.useEffect)(()=>{l();let e=window.setInterval(()=>void l(),3e3);return()=>window.clearInterval(e)},[l]),(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`rail-section-head`,children:[(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`工具`}),(0,m.jsxs)(`span`,{className:`rail-section-count`,"data-tool-summary":`1`,children:[e.length+n.length,` 个`]})]}),e.length+n.length>0?(0,m.jsxs)(`ul`,{className:`tool-grid`,"data-tool-count":e.length+n.length,children:[e.map(e=>(0,m.jsxs)(`li`,{className:`tool-slot`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`tool-icon`,"data-tool-icon":e.id,"data-tool-kind":e.kind,"data-tool-state":e.id===i?`open`:`idle`,title:`打开${e.name}`,"aria-label":`打开${e.name}`,"aria-current":e.id===i?`true`:void 0,onClick:()=>a(t=>t===e.id?void 0:e.id),children:[(0,m.jsx)(`span`,{className:`tool-icon-glyph`,"aria-hidden":`true`,children:e.glyph}),(0,m.jsx)(`span`,{className:`tool-icon-name`,children:e.name})]}),e.kind===`link`&&(0,m.jsx)(`button`,{type:`button`,className:`tool-remove`,"data-tool-remove":e.id,title:`移除${e.name}`,"aria-label":`移除${e.name}`,onClick:()=>{t(t=>on(t,e.id)),i===e.id&&a(void 0)},children:`✕`})]},e.id)),n.map(e=>(0,m.jsxs)(`li`,{className:`tool-slot`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`tool-icon`,"data-mounted-tool-icon":e.id,title:`打开${e.name}`,onClick:()=>a(e.id),children:[(0,m.jsx)(`span`,{className:`tool-icon-glyph`,"aria-hidden":`true`,children:`🧩`}),(0,m.jsx)(`span`,{className:`tool-icon-name`,children:e.name})]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-remove`,title:`移除${e.name}`,onClick:()=>void fetch(`/api/tools?id=${encodeURIComponent(e.id)}`,{method:`DELETE`}).then(l),children:`✕`})]},e.id))]}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-tool-empty":`1`,children:`还没有工具。点下面的「工具设置」添加。`}),(0,m.jsx)(`button`,{type:`button`,className:`tools-settings-btn`,"data-tool-settings-trigger":`1`,title:`添加预置工具、网址工具与说明`,"aria-label":`打开工具设置`,onClick:()=>s(!0),children:`⚙ 工具设置`}),o&&(0,m.jsx)(oi,{tools:e,onToolsChange:t,onClose:()=>s(!1)}),c&&(0,m.jsx)(ci,{tool:c,onClose:()=>a(void 0)}),n.find(e=>e.id===i)&&(0,m.jsx)(hi,{tool:n.find(e=>e.id===i),onClose:()=>a(void 0)})]})}function oi({tools:e,onToolsChange:t,onClose:n}){let[r,i]=(0,p.useState)(``),[a,o]=(0,p.useState)(``),[s,c]=(0,p.useState)(),[l,u]=(0,p.useState)(!1),f=async()=>{let e=await gr(`请为 Super Pi 开发一个可挂载工具。不要启动独立 HTTP 服务器,也不要监听端口。
52
+ `)??``),o=(e,t,n)=>{r(r=>{let i=r[e]??[];return n?{...r,[e]:i.includes(t)?i.filter(e=>e!==t):[...i,t]}:{...r,[e]:[t]}})},s=()=>{t(e.items.map(e=>({id:e.id,selected:n[e.id]??[],...e.extraNote&&i.trim()?{note:i.trim()}:{}})),!1)},c=e.items.some(e=>(n[e.id]??[]).length>0);return(0,m.jsxs)(`div`,{className:`question-block question-${e.status}`,"data-question-status":e.status,children:[e.items.map(t=>(0,m.jsxs)(`div`,{className:`question-item`,children:[(0,m.jsx)(`div`,{className:`question-text`,children:t.header?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`span`,{className:`question-header`,children:t.header}),(0,m.jsx)(`span`,{className:`question-sep`,children:` · `}),(0,m.jsx)(Ke,{source:t.question})]}):(0,m.jsx)(Ke,{source:t.question})}),(0,m.jsx)(`div`,{className:`question-options`,"data-question-options":t.id,children:t.options.map(r=>{let i=(n[t.id]??[]).includes(r),a=e.status!==`pending`;return(0,m.jsxs)(`label`,{className:i?`is-chosen`:``,"data-chosen":a&&i?`1`:void 0,children:[(0,m.jsx)(`input`,{type:t.multiple?`checkbox`:`radio`,name:`q-${t.id}`,checked:i,disabled:a,onChange:()=>o(t.id,r,t.multiple)}),(0,m.jsx)(qe,{source:r}),a&&i?(0,m.jsx)(`span`,{className:`question-mark`,"aria-label":`已选择`,children:`✓`}):null]},r)})}),e.status===`pending`&&t.extraNote?(0,m.jsx)(`textarea`,{value:i,placeholder:`补充说明(可选)`,onChange:e=>a(e.target.value)}):e.status===`answered`&&(e.answers??[]).some(e=>e.note)?(0,m.jsxs)(`div`,{className:`question-summary`,"data-question-summary":`1`,children:[`补充:`,(e.answers??[]).map(e=>e.note).filter(Boolean).join(`;`)]}):null,e.status===`cancelled`&&(0,m.jsx)(`div`,{className:`question-summary`,"data-question-summary":`1`,children:`已取消,没有作答。`})]},t.id)),e.status===`pending`&&(0,m.jsxs)(`div`,{className:`question-actions`,children:[(0,m.jsx)(`button`,{type:`button`,onClick:s,disabled:!c,children:`提交`}),(0,m.jsx)(`button`,{type:`button`,className:`question-skip`,onClick:()=>t([],!0),children:`跳过`})]})]})}function Vr({request:e,onAnswer:t,pending:n}){let[r,i]=(0,p.useState)(e.prefill??``),a=e.method===`input`||e.method===`editor`,o=(0,m.jsx)(`button`,{type:`button`,className:`extui-cancel`,onClick:()=>t({cancelled:!0}),children:`取消提问`});return(0,m.jsxs)(`article`,{className:`extui-card extui-${e.method}`,"data-request-id":e.requestId,"data-method":e.method,children:[(0,m.jsxs)(`div`,{className:`extui-head`,children:[(0,m.jsx)(`span`,{className:`extui-badge`,children:`扩展提问`}),e.timeoutMs!==void 0&&(0,m.jsxs)(`span`,{className:`extui-deadline`,children:[Math.round(e.timeoutMs/1e3),`s 超时`]})]}),n&&(0,m.jsx)(`p`,{role:`status`,children:`提交中…`}),(0,m.jsxs)(`fieldset`,{disabled:n,style:{border:0,padding:0,margin:0},children:[(0,m.jsx)(`h3`,{className:`extui-title`,children:e.title||e.method}),e.method===`confirm`&&(0,m.jsx)(`p`,{className:`extui-body`,children:e.message}),e.method===`select`&&(0,m.jsxs)(`div`,{className:`extui-options`,children:[(e.options??[]).map(e=>(0,m.jsx)(`button`,{type:`button`,className:`extui-option`,onClick:()=>t({value:e}),children:e},e)),(e.options??[]).length===0&&(0,m.jsx)(`p`,{className:`extui-body`,children:`扩展没有提供任何选项`})]}),a&&(0,m.jsxs)(`form`,{className:`extui-form`,onSubmit:e=>{e.preventDefault(),!n&&r.trim()&&t({value:r})},children:[e.method===`input`?(0,m.jsx)(`input`,{className:`extui-field`,value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&nr(e)&&e.preventDefault()},placeholder:e.placeholder??``}):(0,m.jsx)(`textarea`,{className:`extui-field`,value:r,onChange:e=>i(e.target.value),onKeyDown:e=>{e.key===`Enter`&&nr(e)&&e.preventDefault()}}),(0,m.jsxs)(`div`,{className:`extui-actions`,children:[(0,m.jsx)(`button`,{type:`submit`,className:`extui-primary`,disabled:!r.trim(),children:`提交`}),o]})]}),!a&&(0,m.jsxs)(`div`,{className:`extui-actions`,children:[e.method===`confirm`&&(0,m.jsx)(`button`,{type:`button`,className:`extui-primary`,onClick:()=>t({confirmed:!0}),children:`确认`}),e.method===`confirm`&&(0,m.jsx)(`button`,{type:`button`,className:`extui-no`,onClick:()=>t({confirmed:!1}),children:`拒绝`}),o]})]})]})}function Hr({initial:e,label:t,onCommit:n,onClose:r}){let[i,a]=(0,p.useState)(e),o=(0,p.useRef)(null),s=(0,p.useRef)(!1);(0,p.useEffect)(()=>{o.current?.focus(),o.current?.select()},[]);let c=!i.trim(),l=()=>{s.current=!0,r()},u=()=>{if(c)return;let t=i.trim();(t===e||n(t))&&l()};return(0,m.jsxs)(`div`,{className:`inline-edit`,"data-editing":`1`,children:[(0,m.jsx)(`input`,{ref:o,className:`inline-edit-input`,value:i,"aria-label":t,onChange:e=>a(e.target.value),onKeyDown:e=>{if(e.key===`Enter`){if(nr(e))return;e.preventDefault(),u()}else e.key===`Escape`&&(e.preventDefault(),l())},onBlur:()=>{s.current||r()}}),(0,m.jsx)(`button`,{type:`button`,className:`inline-edit-ok`,title:`确认重命名`,"aria-label":`确认重命名`,disabled:c,onMouseDown:e=>e.preventDefault(),onClick:u,children:`✓`}),(0,m.jsx)(`button`,{type:`button`,className:`inline-edit-cancel`,title:`取消重命名`,"aria-label":`取消重命名`,onMouseDown:e=>e.preventDefault(),onClick:l,children:`✗`})]})}function Ur({kind:e,label:t,value:n,detail:r,options:i,disabled:a,note:o,onChoose:s,scope:c}){let[l,u]=(0,p.useState)(!1),[d,f]=(0,p.useState)(``),[h,g]=(0,p.useState)(0),_=(0,p.useRef)(null),v=(0,p.useRef)(null),y=(0,p.useRef)(null),b=(0,p.useRef)(null),x=(0,p.useRef)([]),S=i.find(e=>e.selected),C=Tr(i,d),w=C.length===0?-1:Math.min(Math.max(h,0),C.length-1),T=w<0?void 0:C[w],ee=(e=!1)=>{u(!1),e&&v.current?.focus()},E=()=>{f(``),g(Math.max(i.findIndex(e=>e.selected),0)),u(!0)},te=e=>g(t=>C.length?Math.min(Math.max(t+e,0),C.length-1):0);(0,p.useEffect)(()=>{if(!l)return;let e=e=>{_.current?.contains(e.target)||ee()};return window.addEventListener(`mousedown`,e),()=>window.removeEventListener(`mousedown`,e)},[l]),(0,p.useEffect)(()=>{if(!l)return;let e=_.current,t=t=>{e?.contains(t.relatedTarget)||ee()};return e?.addEventListener(`focusout`,t),()=>e?.removeEventListener(`focusout`,t)},[l]),(0,p.useEffect)(()=>{l&&b.current?.focus()},[l]),(0,p.useEffect)(()=>{l&&w>=0&&x.current[w]?.scrollIntoView({block:`nearest`})},[l,w]),(0,p.useLayoutEffect)(()=>{if(!l)return;let e=y.current;if(!e)return;e.style.setProperty(`--picker-nudge`,`0px`),e.style.setProperty(`--picker-drop`,`0px`);let t=e.getBoundingClientRect(),n=Math.min(window.innerWidth-8-t.right,Math.max(8-t.left,0)),r=Math.min(window.innerHeight-8-t.bottom,Math.max(8-t.top,0));e.style.setProperty(`--picker-nudge`,`${n}px`),e.style.setProperty(`--picker-drop`,`${r}px`)},[l,C.length,i.length]);let ne=e=>{if(l){if(e.key===`Escape`)e.preventDefault(),ee(!0);else if(e.key===`ArrowDown`)e.preventDefault(),te(1);else if(e.key===`ArrowUp`)e.preventDefault(),te(-1);else if(e.key===`Home`)e.preventDefault(),g(0);else if(e.key===`End`)e.preventDefault(),g(Math.max(C.length-1,0));else if(e.key===`Enter`){if(nr(e))return;e.preventDefault(),T&&(s(T.key),ee(!0))}}},re=`picker-list-${c??e}`,ie=t=>`picker-option-${c??e}-${t}`;return(0,m.jsxs)(`div`,{className:`session-picker-item session-picker-${e}`,ref:_,onKeyDown:l?ne:void 0,children:[(0,m.jsxs)(`button`,{ref:v,type:`button`,className:`session-picker-trigger`,disabled:a,"aria-haspopup":`listbox`,"aria-expanded":l,title:o??`${yr[e]}:${r?`${t} · ${r}`:t}`,"data-picker-trigger":e,"data-picker-value":n??t,"data-picker-selected":i.some(e=>e.selected),onClick:()=>l?ee():E(),children:[(0,m.jsx)(`span`,{className:`session-picker-kind`,children:yr[e]}),(0,m.jsx)(`span`,{className:`session-picker-name`,children:t}),r&&(0,m.jsx)(`span`,{className:`session-picker-detail`,children:r}),(0,m.jsx)(`span`,{className:`session-picker-caret`,"aria-hidden":`true`,children:`▾`})]}),l&&(0,m.jsxs)(`div`,{className:`picker-menu`,ref:y,"data-picker-menu":e,"data-picker-highlight":w,children:[(0,m.jsxs)(`div`,{className:`picker-filter`,children:[(0,m.jsx)(`span`,{className:`picker-filter-icon`,"aria-hidden":`true`,children:`🔍`}),(0,m.jsx)(`input`,{ref:b,className:`picker-filter-input`,"data-picker-filter":e,role:`combobox`,"aria-label":`筛选${br[e]}选项`,"aria-expanded":`true`,"aria-controls":re,"aria-autocomplete":`list`,"aria-activedescendant":T?ie(w):void 0,value:d,placeholder:`输入以筛选…`,autoComplete:`off`,spellCheck:!1,onChange:e=>{f(e.target.value),g(0)}}),d&&(0,m.jsx)(`button`,{type:`button`,className:`picker-filter-clear`,"data-picker-filter-clear":e,title:`清空筛选`,"aria-label":`清空筛选`,onMouseDown:e=>e.preventDefault(),onClick:()=>{f(``),g(0),b.current?.focus()},children:`✕`})]}),S&&!C.some(e=>e.key===S.key)&&(0,m.jsxs)(`div`,{className:`picker-current`,"data-picker-current":e,"data-picker-current-value":S.key,children:[(0,m.jsx)(`span`,{className:`picker-current-label`,children:`当前`}),(0,m.jsx)(`span`,{className:`picker-current-value`,children:S.label})]}),(0,m.jsxs)(`div`,{className:`picker-list`,role:`listbox`,id:re,"aria-label":xr[e],children:[C.map((e,t)=>(0,m.jsxs)(`button`,{id:ie(t),type:`button`,role:`option`,"aria-selected":e.selected,ref:e=>{x.current[t]=e},className:`picker-option${e.selected?` selected`:``}${t===w&&!e.selected?` preview`:``}`,"data-picker-option":e.key,"data-picker-option-selected":e.selected,"data-picker-highlight":t===w,onClick:()=>{s(e.key),ee(!0)},children:[e.color&&(0,m.jsx)(`span`,{className:`picker-option-swatch`,style:{background:e.color},"aria-hidden":`true`}),(0,m.jsx)(`span`,{className:`picker-option-name`,children:e.label}),e.badge&&(0,m.jsx)(`span`,{className:`picker-option-badge`,"data-picker-option-badge":e.badge,children:e.badge}),e.detail&&(0,m.jsx)(`span`,{className:`picker-option-detail`,children:e.detail}),e.selected&&(0,m.jsx)(`span`,{className:`picker-option-mark`,"aria-hidden":`true`,children:`✓`})]},e.key)),C.length===0&&(0,m.jsx)(`div`,{className:`picker-empty`,"data-picker-empty":e,children:`没有匹配「${d.trim()}」的${br[e]}`})]})]})]})}var L=`__pi_default__`;function Wr({workspace:e,catalog:t,agents:n,onSave:r,onRename:i,onClose:a}){let[o,s]=(0,p.useState)(e.defaultAgentId),[c,l]=(0,p.useState)(e.defaultModel),[u,d]=(0,p.useState)(e.defaultThinkingLevel);(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&!e.defaultPrevented&&a()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[a]);let f=t?.source===`pi`,h=[{key:L,label:`跟随 Pi 默认`,detail:`不为新会话指定模型`,selected:c===void 0},...(t?.models??[]).map(e=>({key:`${e.provider}/${e.modelId}`,label:e.label,detail:e.detail,badge:Cr(e),selected:c?.provider===e.provider&&c.modelId===e.modelId}))],g=[{key:L,label:`跟随 Pi 默认`,detail:`不为新会话指定思考等级`,selected:u===void 0},...(t?.levels??[]).map(e=>({key:e,label:e,selected:e===u}))],_=e=>{if(e===L){l(void 0);return}let n=(t?.models??[]).find(t=>t.provider===e.split(`/`)[0]&&t.modelId===e.split(`/`)[1]);n&&l({provider:n.provider,modelId:n.modelId,label:n.label})},v=e=>d(e===L?void 0:e),y=[{key:L,label:`跟随 Pi 默认`,detail:`新会话不使用任何 agent 角色`,selected:o===void 0},...(n?.agents??[]).map(e=>({key:e.id,label:e.name,detail:e.toolSummary,color:e.color,selected:o===e.id}))];return(0,m.jsx)(`div`,{className:`overlay`,onClick:a,children:(0,m.jsxs)(`div`,{className:`picker settings-dialog`,"data-settings-dialog":`1`,onClick:e=>e.stopPropagation(),children:[(0,m.jsx)(`h2`,{children:`工作区设置`}),(0,m.jsxs)(`div`,{className:`settings-scope`,children:[e.name,` · 默认值只影响之后新建的会话,已有会话不变`]}),(0,m.jsxs)(`div`,{className:`settings-row`,children:[(0,m.jsx)(`span`,{className:`settings-label`,children:`默认智能体`}),(0,m.jsx)(Ur,{kind:`agent`,scope:`settings-agent`,label:n?.agents.find(e=>e.id===o)?.name??`跟随 Pi 默认`,value:o??L,detail:o?`${n?.agents.find(e=>e.id===o)?.toolSummary??``}`:void 0,options:y,disabled:!n||n.source===`unavailable`,note:Ar(n),onChoose:e=>s(e===L?void 0:e)})]}),(0,m.jsxs)(`div`,{className:`settings-row`,children:[(0,m.jsx)(`span`,{className:`settings-label`,children:`默认模型`}),(0,m.jsx)(Ur,{kind:`model`,scope:`settings-model`,label:c?.label??`跟随 Pi 默认`,value:c?`${c.provider}/${c.modelId}`:L,detail:c?`${c.provider}/${c.modelId}`:void 0,options:h,disabled:!f,note:kr(`模型`,t),onChoose:_})]}),(0,m.jsxs)(`div`,{className:`settings-row`,children:[(0,m.jsx)(`span`,{className:`settings-label`,children:`思考强度`}),(0,m.jsx)(Ur,{kind:`thinking`,scope:`settings-thinking`,label:u??`跟随 Pi 默认`,value:u??L,options:g,disabled:!f,note:kr(`思考等级`,t),onChoose:v})]}),(0,m.jsxs)(`div`,{className:`picker-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`settings-rename`,"data-settings-rename":`1`,onClick:i,children:`重命名工作区`}),(0,m.jsx)(`button`,{type:`button`,className:`picker-cancel`,"data-settings-cancel":`1`,onClick:a,children:`取消`}),(0,m.jsx)(`button`,{type:`button`,className:`picker-ok`,"data-settings-save":`1`,onClick:()=>r(o,c,u),children:`保存`})]})]})})}var Gr=[{id:`main`,label:`主`,name:`主 tab`},{id:`tools`,label:`工具`,name:`工具 tab`},{id:`files`,label:`文件`,name:`文件 tab`},{id:`git`,label:`Git`,name:`Git tab`},{id:`terminal`,label:`终端`,name:`终端 tab`}],Kr={running:``,failed:`!`,question:`?`,unread:`•`,idle:``},qr={running:`进行中`,failed:`出错`,question:`等你回答`,unread:`有未读`,idle:`空闲`};function Jr(e,t,n){return t?`question`:e.status===`failed`?`failed`:e.status===`running`?`running`:e.updatedAt>(n??``)?`unread`:`idle`}function Yr({state:e}){let t=Kr[e];return e===`idle`?null:(0,m.jsx)(`i`,{className:`session-state session-state-${e}`,"data-session-state":e,title:qr[e],"aria-label":qr[e],children:(0,m.jsx)(`i`,{className:`session-state-mark`,"aria-hidden":`true`,children:t})})}var Xr={pending:`未开始`,in_progress:`进行中`,completed:`已完成`},Zr={pending:`○`,in_progress:`◐`,completed:`✓`},Qr={high:`高`,medium:`中`,low:`低`};function $r(e){let t=e.filter(e=>e.status!==`completed`).length;return e.length?t?`${t} 个未完成 / 共 ${e.length} 个`:`全部完成:${e.length} 个`:`0 个待办任务`}function ei({todo:e,index:t}){let n=e.id??`todo_${t}`;return(0,m.jsxs)(`li`,{className:`todo-item todo-${e.status}`,"data-todo-item":n,"data-todo-status":e.status,children:[(0,m.jsx)(`span`,{className:`todo-mark`,"aria-hidden":`true`,children:Zr[e.status]}),(0,m.jsx)(`span`,{className:`todo-text`,children:e.content}),(0,m.jsx)(`span`,{className:`todo-status`,"data-todo-status-text":`1`,children:Xr[e.status]}),e.priority&&(0,m.jsx)(`span`,{className:`todo-priority`,"data-todo-priority":e.priority,children:Qr[e.priority]})]})}function ti({tasks:e}){return(0,m.jsx)(`ul`,{className:`bg-list`,"data-bg-task-count":e.length,children:e.map((e,t)=>{let n=e.id||`bg_${t}`,r=e.status?.trim()||`未知状态`;return(0,m.jsxs)(`li`,{className:`bg-item`,"data-bg-task":n,"data-bg-task-status":e.status??`unknown`,children:[(0,m.jsx)(`span`,{className:`bg-title`,children:e.title?.trim()||e.id||`未命名任务 ${t+1}`}),(0,m.jsxs)(`span`,{className:`bg-meta`,children:[e.agentId?.trim()&&(0,m.jsx)(`span`,{className:`bg-agent`,"data-bg-agent":e.agentId,children:e.agentId}),(0,m.jsx)(`span`,{className:`bg-status`,children:r})]})]},n)})})}function ni({todos:e,tasks:t,collapsed:n,onToggle:r,workspaceId:i,workspaceLabel:a,connected:o,files:s,onOpenFile:c,activeTab:l,onSelectTab:u,sendCommand:d,registerTerminalSink:f,terminalAvailable:p}){let h=Gr;return h.some(e=>e.id===l)||h[0].id,(0,m.jsx)(`div`,{className:`right-rail${n?` right-rail-collapsed`:``}`,role:`complementary`,"aria-label":`右侧栏`,"data-right-rail":`1`,"data-right-rail-collapsed":n,children:n?(0,m.jsx)(`button`,{type:`button`,className:`right-rail-open`,"data-right-rail-open":`1`,title:`展开右侧栏`,"aria-label":`展开右侧栏`,onClick:r,children:`‹`}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`right-rail-head`,children:[(0,m.jsx)(`div`,{className:`right-tabs`,role:`tablist`,"aria-label":`右侧栏 tab`,"data-right-tab-count":h.length,children:h.map(e=>(0,m.jsx)(`button`,{type:`button`,role:`tab`,id:`right-tab-${e.id}`,className:`right-tab${e.id===l?` right-tab-active`:``}`,"data-right-tab":e.id,"aria-selected":e.id===l,"aria-controls":`right-panel-${e.id}`,title:e.name,"aria-label":e.name,onClick:()=>u(e.id),children:e.label},e.id))}),(0,m.jsx)(`button`,{type:`button`,className:`right-rail-collapse`,"data-right-rail-collapse":`1`,title:`收起右侧栏(聊天区会变宽)`,"aria-label":`收起右侧栏`,onClick:r,children:`›`})]}),Gr.filter(e=>e.id===l).map(n=>(0,m.jsxs)(`div`,{className:`right-rail-body`,role:`tabpanel`,id:`right-panel-${n.id}`,"aria-labelledby":`right-tab-${n.id}`,"data-right-panel":n.id,children:[n.id===`main`&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`rail-section-head`,children:[(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`待办任务`}),(0,m.jsx)(`span`,{className:`rail-section-count`,"data-todo-summary":`1`,children:$r(e)})]}),e.length>0?(0,m.jsx)(`ol`,{className:`todo-list`,"data-todo-count":e.length,children:e.map((e,t)=>(0,m.jsx)(ei,{todo:e,index:t},e.id??`todo_${t}`))}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-todo-empty":`1`,children:`暂无待办任务。Agent 开始处理多步骤任务时,会自己把待办写到这里。`}),(0,m.jsxs)(`div`,{className:`rail-section-head`,children:[(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`后台任务`}),(0,m.jsx)(`span`,{className:`rail-section-count`,"data-bg-summary":`1`,children:t.length?`${t.length} 个运行中`:`0 个`})]}),t.length>0?(0,m.jsx)(ti,{tasks:t}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-bg-empty":`1`,children:`当前没有正在运行的后台任务。`})]}),n.id===`tools`&&(0,m.jsx)(ai,{}),n.id===`files`&&(0,m.jsx)(Dt,{workspaceId:i,files:s,onOpenFile:c}),n.id===`git`&&(0,m.jsx)(ne,{workspaceId:i??null}),n.id===`terminal`&&(0,m.jsx)(er,{workspaceId:i,workspaceLabel:a,connected:o,sendCommand:d,registerSink:f})]},n.id))]})})}function ri(){try{if(typeof window>`u`)return Xt.slice();let e=en(window.localStorage.getItem(P));return window.localStorage.getItem(`super-pi:tools:v1`)===null?Xt.slice():e}catch{return Xt.slice()}}function ii(e){try{if(typeof window>`u`)return;window.localStorage.setItem(P,tn(e))}catch{}}function ai(){let[e,t]=(0,p.useState)(ri),[n,r]=(0,p.useState)([]),[i,a]=(0,p.useState)(),[o,s]=(0,p.useState)(!1),c=e.find(e=>e.id===i),l=(0,p.useCallback)(async()=>{try{let e=await fetch(`/api/tools`),t=await e.json();e.ok&&Array.isArray(t.tools)&&r(t.tools.filter(e=>e.enabled))}catch{}},[]);return(0,p.useEffect)(()=>{ii(e)},[e]),(0,p.useEffect)(()=>{l();let e=window.setInterval(()=>void l(),3e3);return()=>window.clearInterval(e)},[l]),(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`rail-section-head`,children:[(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`工具`}),(0,m.jsxs)(`span`,{className:`rail-section-count`,"data-tool-summary":`1`,children:[e.length+n.length,` 个`]})]}),e.length+n.length>0?(0,m.jsxs)(`ul`,{className:`tool-grid`,"data-tool-count":e.length+n.length,children:[e.map(e=>(0,m.jsxs)(`li`,{className:`tool-slot`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`tool-icon`,"data-tool-icon":e.id,"data-tool-kind":e.kind,"data-tool-state":e.id===i?`open`:`idle`,title:`打开${e.name}`,"aria-label":`打开${e.name}`,"aria-current":e.id===i?`true`:void 0,onClick:()=>a(t=>t===e.id?void 0:e.id),children:[(0,m.jsx)(`span`,{className:`tool-icon-glyph`,"aria-hidden":`true`,children:e.glyph}),(0,m.jsx)(`span`,{className:`tool-icon-name`,children:e.name})]}),e.kind===`link`&&(0,m.jsx)(`button`,{type:`button`,className:`tool-remove`,"data-tool-remove":e.id,title:`移除${e.name}`,"aria-label":`移除${e.name}`,onClick:()=>{t(t=>on(t,e.id)),i===e.id&&a(void 0)},children:`✕`})]},e.id)),n.map(e=>(0,m.jsxs)(`li`,{className:`tool-slot`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`tool-icon`,"data-mounted-tool-icon":e.id,title:`打开${e.name}`,onClick:()=>a(e.id),children:[(0,m.jsx)(`span`,{className:`tool-icon-glyph`,"aria-hidden":`true`,children:`🧩`}),(0,m.jsx)(`span`,{className:`tool-icon-name`,children:e.name})]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-remove`,title:`移除${e.name}`,onClick:()=>void fetch(`/api/tools?id=${encodeURIComponent(e.id)}`,{method:`DELETE`}).then(l),children:`✕`})]},e.id))]}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-tool-empty":`1`,children:`还没有工具。点下面的「工具设置」添加。`}),(0,m.jsx)(`button`,{type:`button`,className:`tools-settings-btn`,"data-tool-settings-trigger":`1`,title:`添加预置工具、网址工具与说明`,"aria-label":`打开工具设置`,onClick:()=>s(!0),children:`⚙ 工具设置`}),o&&(0,m.jsx)(oi,{tools:e,onToolsChange:t,onClose:()=>s(!1)}),c&&(0,m.jsx)(ci,{tool:c,onClose:()=>a(void 0)}),n.find(e=>e.id===i)&&(0,m.jsx)(hi,{tool:n.find(e=>e.id===i),onClose:()=>a(void 0)})]})}function oi({tools:e,onToolsChange:t,onClose:n}){let[r,i]=(0,p.useState)(``),[a,o]=(0,p.useState)(``),[s,c]=(0,p.useState)(),[l,u]=(0,p.useState)(!1),f=async()=>{let e=await gr(`请为 Super Pi 开发一个全局右侧栏工具。不要启动独立 HTTP 服务器,也不要监听端口。
53
53
 
54
- 1. 在当前工作区创建 tools/<slug>/index.html 及所需的 JS/CSS。
55
- 2. 工具只通过同源浏览器 API 工作;需要服务端能力时先说明并使用 Super Pi 已提供的接口。
56
- 3. 完成后调用 POST /api/tools,提交 JSON:{ workspaceId: "当前工作区 ID", slug, name, description, entrypoint: "tools/<slug>/index.html" }。
57
- 4. 服务端会验证 entrypoint 位于当前工作区内,并自动注册到右侧工具 Tab;不要修改 localStorage,也不要要求用户手动点击添加。
58
- 5. 不读写工作区外文件,不执行未经授权的命令,不发起未说明的远程请求。最后返回注册 id、访问地址 /tools/<id>/、文件清单和验证结果。`);u(e),window.setTimeout(()=>u(!1),1800)};return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&n()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[n]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-settings-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&n()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-settings-title`,"data-tool-settings":`1`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsx)(`h2`,{className:`tool-dialog-title`,id:`tool-settings-title`,children:`⚙ 工具设置`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-settings-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭工具设置`,onClick:n,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[(0,m.jsxs)(`div`,{className:`tool-prompt-card`,children:[(0,m.jsx)(`strong`,{children:`给 Agent 开发新工具`}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`工具会挂载到 Super Pi 的统一 HTTP 服务,不需要再启动独立服务器。`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-development-prompt":`1`,onClick:()=>void f(),children:l?`已复制`:`复制工具开发 Prompt`})]}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`从预置列表添加`})}),(0,m.jsx)(`div`,{className:`tool-presets`,"data-tool-presets":Yt.length,children:Yt.map(n=>{let r=e.some(e=>e.kind===n.kind);return(0,m.jsxs)(`div`,{className:`tool-preset`,"data-tool-preset":n.kind,children:[(0,m.jsx)(`span`,{className:`tool-preset-glyph`,"aria-hidden":`true`,children:n.glyph}),(0,m.jsxs)(`span`,{className:`tool-preset-text`,children:[(0,m.jsx)(`strong`,{children:n.name}),(0,m.jsx)(`small`,{children:n.blurb})]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-preset-add`,"data-tool-add-preset":n.kind,disabled:r,title:r?`${n.name}已经在工具栏里了`:`添加${n.name}`,"aria-label":r?`${n.name}已添加`:`添加${n.name}`,onClick:()=>t(an(e,n.kind)),children:r?`已添加`:`添加`})]},n.kind)})}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`添加网址工具`})}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`填一个网址,它会变成一个图标;点开后在弹窗里用按钮在新标签页打开。网址只接受 http / https / mailto。`}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-input":`1`,value:r,maxLength:2048,placeholder:`https://example.com/工具`,"aria-label":`工具网址`,onChange:e=>{i(e.target.value),c(void 0)}}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-name":`1`,value:a,maxLength:40,placeholder:`名称(可留空,默认用域名)`,"aria-label":`工具名称(可选)`,onChange:e=>o(e.target.value)}),(0,m.jsx)(`button`,{type:`button`,className:`tool-url-add`,"data-tool-url-add":`1`,disabled:!r.trim(),onClick:()=>{let n=$t(r);n.ok?e.length>=32?c(`工具最多 32 个,先删掉一个再加。`):(t([...e,{id:rn(`link`,e.map(e=>e.id)),kind:`link`,name:a.trim()||si(n.url),glyph:`↗`,url:n.url}]),i(``),o(``),c(void 0)):c(n.reason)},children:`添加网址工具`}),s&&(0,m.jsx)(`p`,{className:`tool-error`,"data-tool-error":`1`,role:`alert`,children:s}),(0,m.jsxs)(`p`,{className:`tool-hint`,"data-tool-scope-note":`1`,children:[`能自定义到哪:从上面挑预置工具,或自己填一个网址。`,(0,m.jsx)(`b`,{children:`没有开放「写一段脚本来执行」这一档`}),`——它在浏览器里等同于任意代码执行,会拿着本应用的权限跑。计算器是手写的算术解析器,不含 eval,也认不出变量名。`]})]})]})}),document.body)}function si(e){try{return new URL(e,typeof window>`u`?`http://localhost`:window.location.href).hostname||e.slice(0,30)}catch{return e.slice(0,30)}}function ci({tool:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-overlay":e.id,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-dialog-title`,"data-tool-dialog":e.id,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,id:`tool-dialog-title`,children:[(0,m.jsx)(`span`,{className:`tool-dialog-glyph`,"aria-hidden":`true`,children:e.glyph}),e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭${e.name}`,onClick:t,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[e.kind===`calculator`&&(0,m.jsx)(ui,{}),e.kind===`timestamp`&&(0,m.jsx)(di,{}),e.kind===`link`&&(0,m.jsx)(fi,{tool:e})]})]})}),document.body)}function li({image:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`lightbox-overlay`,"data-lightbox-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`lightbox-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`lightbox-title`,"data-lightbox-dialog":`1`,children:[(0,m.jsxs)(`div`,{className:`lightbox-head`,children:[(0,m.jsx)(`h2`,{className:`lightbox-title`,id:`lightbox-title`,children:e.name||`图片大图`}),(0,m.jsx)(`button`,{type:`button`,className:`lightbox-close`,"data-lightbox-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭大图`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`lightbox-body`,children:(0,m.jsx)(`img`,{src:e.url,alt:e.name||`图片大图`,className:`lightbox-image`})})]})}),document.body)}function ui(){let[e,t]=(0,p.useState)(`12 * (3 + 4)`),[n,r]=(0,p.useState)(!1),i=vn(e),a=e=>{r(!1),t(t=>n?e:t+e)};return(0,m.jsxs)(`div`,{className:`tool-calc`,"data-tool-calc":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input tool-calc-input`,"data-calc-input":`1`,value:e,maxLength:240,"aria-label":`算式`,spellCheck:!1,autoComplete:`off`,placeholder:`例如 (12 + 8) / 4`,onChange:e=>{r(!1),t(e.target.value)},onKeyDown:e=>{e.key===`Enter`&&e.preventDefault()}}),(0,m.jsx)(`div`,{className:`tool-calc-keys`,"data-calc-keys":`1`,children:[[`7`,`7`],[`8`,`8`],[`9`,`9`],[`÷`,`/`],[`(`,`(`],[`)`,`)`],[`4`,`4`],[`5`,`5`],[`6`,`6`],[`×`,`*`],[`^`,`^`],[`%`,`%`],[`1`,`1`],[`2`,`2`],[`3`,`3`],[`-`,`-`],[`←`,`back`],[`C`,`clear`],[`0`,`0`],[`.`,`.`],[`+`,`+`]].map(([e,n])=>(0,m.jsx)(`button`,{type:`button`,className:`tool-calc-key`,"data-calc-key":n,"aria-label":e===`C`?`清空`:e===`←`?`退格`:`输入 ${e}`,onClick:()=>{n===`clear`?(t(``),r(!1)):n===`back`?(t(e=>e.slice(0,-1)),r(!1)):a(n)},children:e},e))}),i.ok?(0,m.jsx)(`p`,{className:`tool-calc-result`,"data-calc-result":`1`,"data-calc-value":String(i.value),children:yn(i.value)}):(0,m.jsx)(`p`,{className:`tool-calc-error`,"data-calc-error":`1`,role:`alert`,children:i.reason})]})}function di(){let[e,t]=(0,p.useState)(``),n=bn(e),r=n.ok?xn(n.ms):null;return(0,m.jsxs)(`div`,{className:`tool-stamp`,"data-tool-stamp":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input`,"data-stamp-input":`1`,value:e,maxLength:64,spellCheck:!1,"aria-label":`时间戳或日期`,placeholder:`1757000000 或 2026-10-08T09:00:00`,onChange:e=>t(e.target.value)}),r?(0,m.jsxs)(`dl`,{className:`tool-stamp-out`,"data-stamp-result":`1`,children:[(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`本地时间`}),(0,m.jsx)(`dd`,{"data-stamp-local":`1`,children:r.local})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`ISO(UTC)`}),(0,m.jsx)(`dd`,{"data-stamp-iso":`1`,children:r.iso})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`秒`}),(0,m.jsx)(`dd`,{"data-stamp-seconds":`1`,children:r.seconds})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`毫秒`}),(0,m.jsx)(`dd`,{"data-stamp-ms":`1`,children:r.milliseconds})]})]}):(0,m.jsx)(`p`,{className:`tool-stamp-error`,"data-stamp-error":`1`,role:`alert`,children:n.ok?`这个日期无法显示。`:n.reason})]})}function fi({tool:e}){let[t,n]=(0,p.useState)(!1),r=async()=>{let e=await gr(`请为 Super Pi 开发一个工具并自动挂载。不要启动独立 HTTP 服务器。
54
+ 工具不属于当前 workspace;通过内置 tool_register 一次性提交完整文件(index.html、JS/CSS,必要时 .py),Super Pi 会保存为全局工具并自动出现在每个 workspace 的工具 Tab。
59
55
 
60
- 请在当前工作区创建 tools/<slug>/index.html(以及所需的 JS/CSS),只使用浏览器 API 和 Super Pi 同源接口。完成后调用 POST /api/tools,提交 { workspaceId, slug, name, description, entrypoint: "tools/<slug>/index.html" }。服务端会校验路径并统一从 /tools/<id>/ 提供页面。请限制文件在工作区内,不执行任意 shell,不监听端口,并返回注册 id 和访问地址。`);n(e),window.setTimeout(()=>n(!1),1800)};return(0,m.jsxs)(`div`,{className:`tool-link`,"data-tool-link":e.id,children:[(0,m.jsx)(`p`,{className:`tool-hint`,children:`网页工具由独立 HTTP 服务器提供,把页面挂到服务器后即可使用。`}),(0,m.jsx)(`code`,{className:`tool-link-url`,"data-tool-link-url":e.id,children:e.url}),(0,m.jsxs)(`div`,{className:`tool-link-actions`,children:[(0,m.jsx)(`a`,{className:`tool-link-open`,"data-tool-link-open":e.id,href:e.url,target:`_blank`,rel:`noopener noreferrer`,title:e.url,children:`在新标签页打开 ?`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-copy-prompt":e.id,onClick:()=>void r(),children:t?`已复制`:`复制开发 Prompt`})]}),/^https?:\/\//i.test(e.url??``)&&(0,m.jsx)(`iframe`,{className:`tool-link-frame`,"data-tool-frame":e.id,src:e.url,title:e.name,sandbox:`allow-forms allow-modals allow-pointer-lock allow-popups allow-same-origin allow-scripts`,referrerPolicy:`no-referrer`})]})}var pi=`(max-width: 1100px)`,mi=(e,t,n)=>Math.min(Math.max(e,t),Math.max(t,n));function hi({tool:e,onClose:t}){return(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,children:[`🧩 `,e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`tool-dialog-body`,children:(0,m.jsx)(`iframe`,{className:`tool-link-frame`,src:`/tools/${encodeURIComponent(e.id)}/`,title:e.name,sandbox:`allow-forms allow-modals allow-popups allow-scripts`,referrerPolicy:`no-referrer`})})]})}),document.body)}function gi(e,t,n){let r=Math.max(0,Math.round(n)-480),i=mi(520,180,Math.max(180,r-240)),a=mi(Math.round(e),180,i),o=mi(640,240,Math.max(240,r-a));return{sidebar:a,rail:mi(Math.round(t),240,o),sidebarMin:180,sidebarMax:i,railMin:240,railMax:o,sumCap:r}}var _i=`super-pi.rail-widths.v1`,vi=`super-pi.reader-state.v1`,yi=48,bi=160,xi=e=>{e.dataset.programmaticScroll=`true`,window.setTimeout(()=>{delete e.dataset.programmaticScroll},0)};function Si(){try{if(typeof window>`u`)return{conversations:{}};let e=JSON.parse(window.localStorage.getItem(`super-pi.reader-state.v1`)??`null`);if(!e||typeof e!=`object`)return{conversations:{}};let t={};if(e.conversations&&typeof e.conversations==`object`)for(let[n,r]of Object.entries(e.conversations)){let e=r;typeof e.followTail==`boolean`&&(t[n]={followTail:e.followTail,scrollTop:Number.isFinite(e.scrollTop)?Math.max(0,Number(e.scrollTop)):0,...typeof e.anchorKey==`string`?{anchorKey:e.anchorKey}:{},...Number.isFinite(e.anchorOffset)?{anchorOffset:Number(e.anchorOffset)}:{}})}let n={};if(e.readAt&&typeof e.readAt==`object`)for(let[t,r]of Object.entries(e.readAt))typeof r==`string`&&(n[t]=r);return{activeId:typeof e.activeId==`string`?e.activeId:void 0,workspaceId:typeof e.workspaceId==`string`?e.workspaceId:void 0,conversations:t,readAt:n}}catch{return{conversations:{}}}}function Ci(e){try{typeof window<`u`&&window.localStorage.setItem(vi,JSON.stringify(e))}catch{}}function wi(){try{if(typeof window>`u`)return{};let e=window.localStorage.getItem(_i);if(e===null)return{};let t=JSON.parse(e),n=e=>typeof e==`number`&&Number.isFinite(e)&&e>0?Math.round(e):void 0;return{sidebar:n(t.sidebar),rail:n(t.rail)}}catch{return{}}}function Ti(e){try{if(typeof window>`u`)return;window.localStorage.setItem(_i,JSON.stringify(e))}catch{}}function Ei({side:e,width:t,min:n,max:r,label:i,onWidth:a}){let[o,s]=(0,p.useState)(!1),c=e=>e.shiftKey?64:16;return(0,m.jsx)(`div`,{className:`rail-resizer rail-resizer-${e}${o?` rail-resizer-dragging`:``}`,"data-rail-resizer":e,"data-rail-resizer-dragging":o?`true`:`false`,"data-rail-resizer-width":t,role:`separator`,"aria-orientation":`vertical`,"aria-label":i,"aria-valuenow":t,"aria-valuemin":n,"aria-valuemax":r,tabIndex:0,onMouseDown:i=>{i.preventDefault(),i.currentTarget.focus();let o=i.clientX,c=t;s(!0);let l=document.body,u=l.style.cursor,d=l.style.userSelect;l.style.cursor=`col-resize`,l.style.userSelect=`none`;let f=t=>{let i=e===`left`?t.clientX-o:o-t.clientX;a(mi(c+i,n,r))},p=()=>{l.style.cursor=u,l.style.userSelect=d,s(!1),window.removeEventListener(`mousemove`,f),window.removeEventListener(`mouseup`,p)};window.addEventListener(`mousemove`,f),window.addEventListener(`mouseup`,p)},onKeyDown:i=>{let o=c(i);i.key===`ArrowLeft`?(i.preventDefault(),a(mi(e===`left`?t-o:t+o,n,r))):i.key===`ArrowRight`?(i.preventDefault(),a(mi(e===`left`?t+o:t-o,n,r))):i.key===`Home`?(i.preventDefault(),a(n)):i.key===`End`&&(i.preventDefault(),a(r))}})}function Di(){let[e,t]=(0,p.useState)(tr),[n,r]=(0,p.useState)([]),i=(0,p.useRef)([]),a=(0,p.useRef)(null);a.current||=Si();let o=n.reduce((e,t)=>t.apply(e),e),[s,c]=(0,p.useState)(a.current.activeId),[l,u]=(0,p.useState)(!1),[d,f]=(0,p.useState)(),[h,g]=(0,p.useState)({}),[_,v]=(0,p.useState)(a.current.workspaceId),[y,b]=(0,p.useState)(!1),x=(0,p.useRef)(void 0),S=(0,p.useRef)(a.current.conversations),C=(0,p.useRef)(a.current.readAt??{}),[w,T]=(0,p.useState)(0),ee=(0,p.useRef)(null),E=(0,p.useRef)(null),te=(0,p.useRef)(void 0),ne=(0,p.useRef)(a.current.activeId),[re,ie]=(0,p.useState)({followTail:!0,scrollTop:0}),ae=(0,p.useRef)(void 0),oe=(0,p.useRef)(new Map),se=(0,p.useRef)(void 0),D=(0,p.useCallback)(()=>{if(se.current!==void 0&&(cancelAnimationFrame(se.current),se.current=void 0),!oe.current.size)return;let e=[...oe.current.values()];oe.current.clear();let n=new Map,r=new Map;for(let t of e){let e=`${t.conversationId}\u0000${t.messageId}`;if(t.type===`message.thinking`){let n=r.get(e);r.set(e,{text:`${n?.text??``}${t.delta}`,at:n?.at??t.at})}else n.set(e,`${n.get(e)??``}${t.delta}`)}t(e=>({...e,conversations:e.conversations.map(e=>{let t=e.messages.map(t=>{let i=`${e.id}\u0000${t.id}`,a=n.get(i),o=r.get(i);return a===void 0&&o===void 0?t:{...t,...a===void 0?{}:{content:t.content+a},...o===void 0?{}:{thinking:(t.thinking??``)+o.text,thinkingBlocks:[...t.thinkingBlocks??[],{at:o.at??t.content.length,text:o.text}]}}});return t.every((t,n)=>t===e.messages[n])?e:{...e,messages:t}})}))},[]),ce=(0,p.useCallback)(e=>{let t=`${e.type}\u0000${e.conversationId}\u0000${e.messageId}`,n=oe.current.get(t);oe.current.set(t,n?{...n,delta:n.delta+e.delta}:e),se.current===void 0&&(se.current=requestAnimationFrame(D))},[D]),le=(0,p.useCallback)(e=>{ae.current=e},[]),[ue,de]=(0,p.useState)({}),[fe,pe]=(0,p.useState)(()=>{if(typeof window>`u`)return``;try{return window.localStorage.getItem(`super-pi.user-nickname`)??``}catch{return``}}),me=e=>{let t=e.slice(0,24);pe(t);try{window.localStorage.setItem(`super-pi.user-nickname`,t)}catch{}},he=fe.trim()||`你`,[ge,_e]=(0,p.useState)([]),[ve,ye]=(0,p.useState)([]),[be,xe]=(0,p.useState)(new Map),[Se,O]=(0,p.useState)(new Map),[k,Ce]=(0,p.useState)(),[we,Te]=(0,p.useState)(),[Ee,De]=(0,p.useState)(),[A,Oe]=(0,p.useState)(),[ke,Ae]=(0,p.useState)(),[je,Me]=(0,p.useState)(`idle`),[Ne,Pe]=(0,p.useState)(()=>typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(max-width: 1100px)`).matches),[Fe,Ie]=(0,p.useState)(`chat`),[Le,Re]=(0,p.useState)(()=>typeof window>`u`?1440:window.innerWidth);(0,p.useEffect)(()=>{let e=()=>Re(window.innerWidth);return window.addEventListener(`resize`,e),e(),()=>window.removeEventListener(`resize`,e)},[]);let[ze,Be]=(0,p.useState)(wi),j=gi(ze.sidebar??270,ze.rail??320,Le);(0,p.useEffect)(()=>{Ti({sidebar:j.sidebar,rail:j.rail})},[j.sidebar,j.rail]);let Ve=e=>Be(t=>({...t,sidebar:e})),He=e=>Be(t=>({...t,rail:e}));(0,p.useEffect)(()=>{if(typeof window>`u`||typeof window.matchMedia!=`function`)return;let e=window.matchMedia(pi),t=e=>{e.matches&&Pe(!0)};return e.addEventListener(`change`,t),e.matches&&Pe(!0),()=>e.removeEventListener(`change`,t)},[]);let[Ue,We]=(0,p.useState)(new Map),[Ge,qe]=(0,p.useState)({}),[Je,Ye]=(0,p.useState)({}),[Xe,Ze]=(0,p.useState)({}),[Qe,$e]=(0,p.useState)(),[et,tt]=(0,p.useState)(),[nt,it]=(0,p.useState)(),ot=(0,p.useRef)(new Map),[st,ct]=(0,p.useState)({}),lt=(0,p.useRef)(new Set),[ut,dt]=(0,p.useState)([]),[ft,pt]=(0,p.useState)([]),[mt,ht]=(0,p.useState)(),[gt,_t]=(0,p.useState)(!1),vt=(0,p.useRef)(null),yt=(0,p.useRef)(!1),bt=(0,p.useRef)(0),xt=(0,p.useRef)([]),St=(0,p.useRef)(void 0),Ct=(0,p.useRef)(!1),[Tt,Dt]=(0,p.useState)(()=>Date.now());(0,p.useEffect)(()=>{let e=window.setInterval(()=>Dt(Date.now()),3e4);return()=>window.clearInterval(e)},[]);let Ot=(0,p.useMemo)(()=>o.conversations.filter(e=>!e.archivedAt),[o.conversations]),M=(0,p.useMemo)(()=>Ot.find(e=>e.id===s)??Ot.find(e=>e.workspaceId===_)??(_&&o.workspaces.some(e=>e.id===_)?void 0:Ot[0]),[s,Ot,_,o.workspaces]),kt=(0,p.useMemo)(()=>o.workspaces.find(e=>e.id===_)??o.workspaces[0],[o.workspaces,_]),jt=(0,p.useRef)(_);jt.current=_,(0,p.useEffect)(()=>{M&&v(M.workspaceId)},[M?.id]),(0,p.useEffect)(()=>{St.current=s},[s]),(0,p.useEffect)(()=>{te.current=M?.id},[M?.id]),(0,p.useEffect)(()=>{let e=M?.id;if(!e)return;let t=o.conversations.find(t=>t.id===e);if(!t)return;let n=C.current[e];C.current[e]=t.updatedAt,n!==t.updatedAt&&T(e=>e+1),Ci({activeId:St.current,workspaceId:M?.workspaceId??_,conversations:S.current,readAt:C.current})},[M?.id,o.conversations]),(0,p.useEffect)(()=>{l&&s&&Ci({activeId:s,workspaceId:M?.workspaceId??_,conversations:S.current})},[s,M?.workspaceId,l,_]),(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=M.id,n=S.current[t]??{followTail:!0,scrollTop:0};ie(n),requestAnimationFrame(()=>{if(ee.current===e){if(n.followTail)xi(e),e.scrollTop=e.scrollHeight;else{let t=n.anchorKey?e.querySelector(`[data-transcript-key="${CSS.escape(n.anchorKey)}"]`):null;e.scrollTop=t?Math.max(0,e.scrollTop+t.getBoundingClientRect().top-(n.anchorOffset??0)-e.getBoundingClientRect().top):n.scrollTop}}}),ne.current=void 0},[M?.id]);let Mt=(0,p.useCallback)((e,t=!1,n=!1)=>{let r=te.current;if(!r)return;let i=e.scrollHeight-e.clientHeight-e.scrollTop,a=S.current[r]??{followTail:!0,scrollTop:0},o=t?!1:n?!0:a.followTail?i<=bi:i<=yi,s,c;if(!o){let t=e.getBoundingClientRect().top,n=[...e.querySelectorAll(`[data-transcript-key]`)].find(e=>e.getBoundingClientRect().bottom>t);s=n?.dataset.transcriptKey,c=n?n.getBoundingClientRect().top-t:void 0}let l={followTail:o,scrollTop:e.scrollTop,...s?{anchorKey:s,anchorOffset:c}:{}};S.current[r]=l,ie(l),Ci({activeId:r,workspaceId:jt.current,conversations:S.current})},[]),Nt=st[M?.id??``]??{text:``,images:[],attachments:[],uploading:0},Pt=Nt.text,Ft=Nt.images,It=Nt.attachments,Lt=Nt.uploading,zt=(e,t)=>ct(n=>({...n,[e]:t(n[e]??{text:``,images:[],attachments:[],uploading:0})})),Vt=e=>zt(M?.id??``,t=>({...t,text:e})),Ht=e=>zt(M?.id??``,t=>({...t,attachments:t.attachments.some(t=>t.kind===e.kind&&t.id===e.id)?t.attachments:[...t.attachments,e]})),Ut=(e,t)=>zt(M?.id??``,n=>({...n,attachments:n.attachments.filter(n=>n.kind!==e||n.id!==t)})),[Wt,Gt]=(0,p.useState)({visible:!1,query:``,rect:null}),Kt=e=>zt(M?.id??``,t=>({...t,images:typeof e==`function`?e(t.images):e})),[qt,Jt]=(0,p.useState)([]),Yt=qt.length>0?qt:M?[M.id]:[],Xt=(0,p.useMemo)(()=>Yt.map(e=>Ot.find(t=>t.id===e)).filter(e=>e!==void 0),[Yt,Ot]),Zt=(0,p.useCallback)(e=>{c(e);let t=Ot.find(t=>t.id===e)?.workspaceId;t&&v(t),an(`session`),Jt(t=>t.includes(e)?t:t.length>0?[...t,e]:[...M?.id&&M.id!==e?[M.id]:[],e])},[M]),N=(0,p.useCallback)(e=>{let t=qt.indexOf(e);if(t<0)return;let n=qt.filter(t=>t!==e);if(Jt(n),M?.id!==e)return;let r=n[Math.min(t,n.length-1)];r&&c(r)},[qt,M]),P=Et(_),[Qt,$t]=(0,p.useState)(Gr[0].id),[en,tn]=(0,p.useState)(!1);(0,p.useEffect)(()=>{let e=!1;return fetch(`/api/terminal`).then(e=>e.json()).then(t=>{e||tn(!!t?.enabled)}).catch(()=>{e||tn(!1)}),()=>{e=!0}},[]);let[rn,an]=(0,p.useState)(`session`);(0,p.useEffect)(()=>{rn===`file`&&!P.active&&an(`session`)},[rn,P.active]);let on=(0,p.useCallback)(e=>{P.setActivePath(e),an(`file`),$t(`files`)},[P.setActivePath]),sn=(0,p.useCallback)(async e=>{await P.openFile(e),an(`file`)},[P.openFile]),cn=(0,p.useMemo)(()=>Xt.map(e=>({id:`session:${e.id}`,rawId:e.id,kind:`session`,label:e.title,title:e.title,closable:Xt.length>1||P.open.length>0,badge:(0,m.jsx)(Yr,{state:Jr(e,ge.some(t=>t.conversationId===e.id),C.current[e.id])})})),[Xt,P.open.length,ge,o.conversations,w]),ln=(0,p.useMemo)(()=>P.open.map(e=>({id:`file:${e.path}`,rawId:e.path,kind:`file`,label:e.name,title:e.path,closable:!0,badge:e.dirty?(0,m.jsx)(`span`,{className:`file-tab-badge`,"data-file-tab-dirty":e.path,"aria-label":`未保存`,children:`●`}):void 0})),[P.open]),un=(0,p.useMemo)(()=>[...cn,...ln],[cn,ln]),dn=rn===`file`&&P.activePath?`file:${P.activePath}`:M?.id?`session:${M.id}`:void 0,fn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);on(t)}else if(e.startsWith(`session:`)){let t=e.slice(8);Zt(t)}},[on,Zt]),pn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);P.closeFile(t),P.activePath===t&&P.open.length<=1&&an(`session`)}else if(e.startsWith(`session:`)){let t=e.slice(8);N(t)}},[P,N]),mn=P.active,hn=M?.id;(0,p.useEffect)(()=>{hn&&!hn.startsWith(`pending:`)&&bn({type:`conversation.select`,conversationId:hn})},[hn]);let F=(e,t=`info`)=>{if(!e)return;let n=++bt.current;ye(r=>[...r,{key:n,text:e,kind:t}]),xt.current.push(window.setTimeout(()=>ye(e=>e.filter(e=>e.key!==n)),7e3))},gn=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},_n=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},vn=(e,t)=>{if(t.blocking)_e(n=>[...n.filter(n=>n.conversationId!==e||n.request.requestId!==t.requestId),{conversationId:e,request:t}]);else if(t.method===`notify`)F(t.message||t.title,t.notifyType??`info`);else if(t.method===`setStatus`){let e=t.statusKey??``;xe(n=>{let r=new Map(n);return t.statusText===void 0?r.delete(e):r.set(e,t.statusText),r})}else if(t.method===`setWidget`){let e=t.widgetKey??``;O(n=>{let r=new Map(n);return t.widgetLines===void 0?r.delete(e):r.set(e,{lines:t.widgetLines,placement:t.widgetPlacement??`aboveEditor`}),r})}else t.method===`setTitle`?Ce(t.title):t.method===`set_editor_text`&&e===St.current&&Vt(t.text??``)},yn=(e,t,n)=>{_e(n=>n.filter(n=>n.conversationId!==e||n.request.requestId!==t));let r=Dr[n];r&&F(r,`warning`)};(0,p.useEffect)(()=>{let e=!1,n=0,o,s=()=>{if(e)return;let l=location.protocol===`https:`?`wss`:`ws`,d=new WebSocket(`${l}://${location.host}/ws`);x.current=d,d.onopen=()=>{e||(n=0,u(!0),d.send(JSON.stringify({type:`stream.configure`,coalesce:!0})),d.send(JSON.stringify({type:`catalog.fetch`,conversationId:St.current})),d.send(JSON.stringify({type:`agent.catalog.fetch`})),d.send(JSON.stringify({type:`subagent.catalog.fetch`})))},d.onclose=()=>{if(!e){Ct.current=!1;for(let e of i.current)e.onFailure?.();i.current=[],r([]),u(!1),o=window.setTimeout(s,Math.min(hr,mr*2**n++))}},d.onmessage=({data:n})=>{if(e)return;let o=JSON.parse(n);if(o.type!==`message.delta`&&o.type!==`message.thinking`&&D(),o.type.startsWith(`terminal.`))ae.current?.(o);else if(o.type===`conversation.autoRename.result`){let e=ot.current.get(o.conversationId);e&&(ot.current.delete(o.conversationId),o.ok?e.resolve(o.title):e.reject(Error(o.error)))}else if(o.type===`message.thinking`)ce(o);else if(o.type===`state`)D(),Ct.current=!1,i.current=i.current.filter(e=>!e.accepted?.(o.state)),r(i.current),t(o.state),u(!0),c(e=>{let t=o.state.conversations.filter(e=>!e.archivedAt),n=a.current?.activeId,r=!!(e&&t.some(t=>t.id===e)),s=!!(e&&i.current.some(t=>t.temporaryId===e));return r||s?e:n&&t.some(e=>e.id===n)?n:t.find(e=>e.workspaceId===jt.current)?.id}),v(e=>{let t=a.current?.workspaceId;return t&&o.state.workspaces.some(e=>e.id===t)?t:e&&(o.state.workspaces.some(t=>t.id===e)||i.current.some(t=>t.temporaryId===e))?e:o.state.workspaces[0]?.id});else if(o.type===`message.delta`)ce(o);else if(o.type===`message.revert.result`)vi(void 0),$e(void 0),o.ok||f(o.message);else if(o.type===`operation.result`){let e=i.current.find(e=>e.id===o.requestId);if(i.current=i.current.filter(e=>e.id!==o.requestId),r(i.current),e?.temporaryId){let t=e.temporaryId;c(n=>n===t?o.ok?o.entityId:e.previousActiveId:n),Jt(e=>e.flatMap(e=>e===t?o.ok&&o.entityId?[o.entityId]:[]:[e])),v(e=>e===t?o.ok?o.entityId:void 0:e)}o.ok||(e?.onFailure?.(),f(o.message??`操作失败`))}else o.type===`error`?(f(o.message),Ct.current&&(Ct.current=!1,F(`重命名失败:${o.message}`,`error`))):o.type===`extension.ui`?vn(o.conversationId,o.request):o.type===`extension.ui.closed`?yn(o.conversationId,o.requestId,o.reason):o.type===`model.catalog`?(!o.conversationId||o.conversationId===St.current)&&Te(o.catalog):o.type===`agent.catalog`?De(o.catalog):o.type===`subagent.catalog`?Oe(o.catalog):o.type===`server.version`?(Ae(o.version),o.version.needsRestart&&F(`检测到 Super Pi 代码已更新(v${o.version.runningVersion} → v${o.version.diskVersion}),请执行 /super_pi restart 重启服务生效`,`warning`)):o.type===`subagent.event`?t(e=>({...e,subagentRuns:gn(e.subagentRuns??[],o.run)})):o.type===`workflow.event`?t(e=>({...e,workflows:_n(e.workflows??[],o.workflow)})):o.type===`message.tools`?t(e=>({...e,conversations:e.conversations.map(e=>e.id===o.conversationId?{...e,messages:e.messages.map(e=>e.id===o.messageId?{...e,toolCalls:o.toolCalls}:e)}:e)})):o.type===`pi.queue`?We(e=>{let t=new Map(e);return t.set(o.conversationId,{steering:o.steering,followUp:o.followUp}),t}):o.type===`conversation.fork.result`&&(Ni(e=>e===o.conversationId?void 0:e),F(o.ok?`已从「${o.title}」分叉出新会话,放在侧边栏最上面;没有切换过去。${o.messagesCopied?`复制了 ${o.messagesCopied} 条消息。`:`原会话还没有消息,只复制了会话本身。`}`:`分叉失败:${o.message}`,o.ok?`info`:`error`))}};return s(),()=>{e=!0,D(),window.clearTimeout(o);for(let e of xt.current)window.clearTimeout(e);x.current?.close()}},[ce,D]);let bn=(0,p.useCallback)(e=>x.current?.readyState===WebSocket.OPEN&&(x.current.send(JSON.stringify(e)),!0),[]),I=(e,t,n,a,o,s)=>{if(x.current?.readyState!==WebSocket.OPEN||i.current.some(e=>e.key===t))return!1;let c=nn(),l={id:c,key:t,apply:n,temporaryId:a,previousActiveId:St.current,onFailure:o,accepted:s};return i.current=[...i.current,l],r(i.current),x.current.send(JSON.stringify({...e,operationRequestId:c})),!0},xn=(e,t)=>n=>({...n,conversations:n.conversations.map(n=>n.id===e?{...n,...t}:n)}),Sn=(e,t)=>n=>({...n,workspaces:n.workspaces.map(n=>n.id===e?{...n,...t}:n)}),Cn=n.some(e=>e.key===`send:${M?.id}`||e.key===`configuration:${M?.id}`||e.temporaryId===M?.id||e.key===`archive:${M?.id}`),wn=(e,t,n,r,i)=>{e&&x.current?.send(JSON.stringify({type:`question.answer`,conversationId:e,messageId:t,callIndex:n,answers:r,cancelled:i}))},Tn=(e,t,n)=>I({type:`extension.ui.answer`,conversationId:e,requestId:t,...n},`answer:${e}:${t}`,e=>e),En=M?.model??we?.defaultModel,Dn=M?.thinkingLevel??we?.defaultThinkingLevel,On=we?.source===`pi`,An=!!we?.models.find(e=>e.provider===En?.provider&&e.modelId===En?.modelId)?.imageInput,jn=(e,t)=>!e||!t?e:{...e,models:e.models.filter(e=>(o.modelFilterRules??[]).every(n=>{try{return!new RegExp(n.workspacePattern,`i`).test(t.name)||new RegExp(n.modelPattern,`i`).test(e.label)||new RegExp(n.modelPattern,`i`).test(`${e.provider}/${e.modelId}`)}catch{return!0}}))},Mn=(jn(we,o.workspaces.find(e=>e.id===M?.workspaceId))?.models??[]).map(e=>({key:`${e.provider}/${e.modelId}`,label:e.label,detail:e.detail,badge:Cr(e),selected:En?.provider===e.provider&&En.modelId===e.modelId})),Nn=(we?.levels??[]).map(e=>({key:e,label:e,selected:e===Dn})),Pn=e=>{if(!M)return;let[t,n]=e.split(`/`);t&&n&&I({type:`conversation.model`,conversationId:M.id,provider:t,modelId:n},`configuration:${M.id}`,xn(M.id,{model:we?.models.find(e=>e.provider===t&&e.modelId===n)??{provider:t,modelId:n,label:n}}))},Fn=e=>{M&&I({type:`conversation.thinking`,conversationId:M.id,level:e},`configuration:${M.id}`,xn(M.id,{thinkingLevel:e}))},In=Ee?.agents.find(e=>e.id===M?.agentId),Rn=[{key:L,label:`跟随 Pi 默认`,detail:`不使用任何 agent 角色`,selected:!M?.agentId},...(Ee?.agents??[]).map(e=>({key:e.id,label:e.name,detail:e.toolSummary,color:e.color,selected:M?.agentId===e.id}))],zn=In?.model,Bn=!!(zn&&M?.model&&M.model.provider===zn.provider&&M.model.modelId===zn.modelId);In?.thinkingLevel&&(M?.thinkingLevel,In.thinkingLevel);let Vn=e=>{if(M){let t=Ee?.agents.find(t=>t.id===e);I({type:`conversation.agent`,conversationId:M.id,agentId:e===L?null:e},`configuration:${M.id}`,xn(M.id,{agentId:t?.id,...t?.model?{model:{...t.model,label:t.model.modelId}}:{},...t?.thinkingLevel?{thinkingLevel:t.thinkingLevel}:{}}))}},Hn=ge.filter(e=>e.conversationId===M?.id),Un=(0,p.useMemo)(()=>(o.subagentRuns??[]).filter(e=>e.conversationId===M?.id),[o.subagentRuns,M?.id]),Wn=(0,p.useMemo)(()=>(o.workflows??[]).filter(e=>e.conversationId===M?.id),[o.workflows,M?.id]),Gn=(0,p.useMemo)(()=>new Map(Wn.map(e=>[e.id,e])),[Wn]),Kn=(0,p.useMemo)(()=>new Map((A?.agents??[]).map(e=>[e.id,e.name])),[A]),qn=(0,p.useMemo)(()=>new Map((M?.messages??[]).map(e=>[e.id,e])),[M?.messages]),Jn=(0,p.useMemo)(()=>new Map(Un.map(e=>[e.id,e])),[Un]),Yn=(0,p.useMemo)(()=>{if(!M)return[];let e=Qe?.conversationId===M.id?(()=>{let e=M.messages.find(e=>e.id===Qe.messageId);if(e)return{index:M.messages.indexOf(e),at:Date.parse(e.createdAt)||0,...Qe.kind===`withdraw`?{inclusive:!0}:{inclusive:!1}}})():void 0,t=[];return M.messages.forEach((n,r)=>{e&&(r>e.index||e.inclusive&&r===e.index)||t.push({at:Date.parse(n.createdAt)||0,seq:r,key:`m:${n.id}`})}),Un.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`r:${n.id}`})}),Wn.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`w:${n.id}`})}),t.sort((e,t)=>e.at-t.at||e.seq-t.seq).map(e=>e.key)},[M,Un,Wn,Qe]);(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=()=>{S.current[M.id]?.followTail&&(xi(e),e.scrollTop=e.scrollHeight)};if(t(),typeof ResizeObserver>`u`)return;let n=new ResizeObserver(t);n.observe(e);for(let t of e.children)n.observe(t);return()=>n.disconnect()},[M?.id,M?.messages,Un,Wn,Yn]);let Xn=e=>qe(t=>({...t,[e]:!t[e]})),Zn=(e,t)=>Ye(n=>({...n,[`${e}:${t}`]:!n[`${e}:${t}`]})),Qn=(e,t)=>Ze(n=>({...n,[`${e}:${t}`]:n[`${e}:${t}`]!==!1})),[$n,er]=(0,p.useState)({}),ir=e=>er(t=>({...t,[e]:t[e]===!1})),ar=e=>{I({type:`subagent.stop`,runId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止子智能体,请重试`,`error`)},or=e=>{I({type:`workflow.stop`,workflowId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止编排,请重试`,`error`)},sr=e=>[...Se].filter(([,t])=>t.placement===e).map(([e,t])=>{let n=t.lines;return(0,m.jsxs)(`div`,{className:`extui-widget`,"data-widget-key":e,"data-widget-lines":n.length,children:[n.slice(0,Or).map((e,t)=>(0,m.jsx)(`div`,{children:e},`${t}:${e}`)),n.length>Or&&(0,m.jsx)(`div`,{children:`… (widget truncated)`})]},e)}),cr=(e,t)=>{let n=`pending:${nn()}`,r={id:n,name:e,path:t,updatedAt:new Date().toISOString()};I({type:`workspace.create`,name:e,path:t},`create-workspace`,e=>({...e,workspaces:[...e.workspaces,r]}),n)&&(v(n),c(void 0),b(!1))},[lr,yr]=(0,p.useState)(),br=(e,t)=>yr(n=>n?.kind===e&&n.id===t?n:{kind:e,id:t}),xr=()=>yr(void 0),Sr=(e,t)=>lr?.kind===e&&lr.id===t,wr=(e,t,n)=>t.startsWith(`pending:`)?!1:I(e===`workspace`?{type:`workspace.rename`,workspaceId:t,name:n}:{type:`conversation.rename`,conversationId:t,name:n},`rename:${t}`,e===`workspace`?Sn(t,{name:n}):xn(t,{title:n}))?(Ct.current=!0,xr(),!0):(F(`连接断开,改名未保存,请重试`,`error`),!1),Tr=async e=>{if(e.startsWith(`pending:`)){F(`这个会话还在创建中,稍后再试`,`warning`);return}if(ot.current.has(e))return;F(`正在根据对话内容生成标题…`,`info`);let t=new Promise((t,n)=>{ot.current.set(e,{resolve:t,reject:n}),window.setTimeout(()=>{ot.current.delete(e)&&n(Error(`模型在 30 秒内没有返回标题`))},32e3)});if(!bn({type:`conversation.autoRename`,conversationId:e}))ot.current.delete(e),F(`连接断开,无法生成标题`,`error`);else try{let n=await t;wr(`conversation`,e,n),F(`已重命名为「${n}」`,`info`)}catch(e){F(`重命名失败:${e instanceof Error?e.message:String(e)}`,`error`)}},jr=e=>{if(e.id.startsWith(`pending:`))return;let t=o.conversations.filter(t=>t.workspaceId===e.id).length;window.confirm(`删除工作区「${e.name}」?${t?`其中 ${t} 条会话将一并删除,`:``}此操作不可撤销。`)&&I({type:`workspace.delete`,workspaceId:e.id},`delete:${e.id}`,t=>({...t,workspaces:t.workspaces.filter(t=>t.id!==e.id),conversations:t.conversations.filter(t=>t.workspaceId!==e.id)}))},[Mr,Nr]=(0,p.useState)(),Fr=Mr?o.workspaces.find(e=>e.id===Mr):void 0,{theme:Br,setTheme:Kr}=kn(),[qr,Xr]=(0,p.useState)(!1),[Zr,Qr]=(0,p.useState)(!1),$r=(e,t,n,r)=>{e.startsWith(`pending:`)||(I({type:`workspace.settings`,workspaceId:e,model:n?{provider:n.provider,modelId:n.modelId}:null,thinkingLevel:r??null,agentId:t??null},`defaults:${e}`,Sn(e,{defaultModel:n,defaultThinkingLevel:r,defaultAgentId:t}))?Nr(void 0):F(`连接断开,设置未保存,请重试`,`error`))},ei=e=>{if(e.startsWith(`pending:`)||i.current.some(t=>t.key===`defaults:${e}`))return;let t=`pending:${nn()}`,n={id:t,workspaceId:e,title:`新会话`,status:`idle`,updatedAt:new Date().toISOString(),messages:[]};I({type:`conversation.create`,workspaceId:e},`create-session:${e}`,e=>({...e,conversations:[n,...e.conversations]}),t)&&(v(e),c(t),an(`session`),Jt(e=>[...e.filter(e=>e!==t),t]))},ti=e=>{e.startsWith(`pending:`)||I({type:`conversation.archive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:new Date().toISOString()}))},ri=e=>I({type:`conversation.unarchive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:void 0})),ii=async e=>{if(!M){F(`请先选择或创建会话`,`error`);return}if(!An){F(`当前模型不支持图片输入`,`error`);return}let t=[`image/png`,`image/jpeg`,`image/gif`,`image/webp`],n=e.filter(e=>t.includes(e.type)?e.size>5242880?(F(`图片「${e.name}」超过 5MB 上限`,`error`),!1):!0:(F(`不支持的文件格式「${e.name}」,仅支持 PNG/JPEG/GIF/WebP`,`error`),!1));if(n.length===0)return;let r=M.id;zt(r,e=>({...e,uploading:e.uploading+n.length}));let i=n.map(e=>({id:nn(),conversationId:r,name:e.name,url:URL.createObjectURL(e)}));pt(e=>[...e,...i]);for(let[e,t]of n.entries()){let n=i[e];try{let e=await new Promise((e,n)=>{let r=new FileReader;r.onload=()=>{let t=r.result,n=t.indexOf(`,`);e(n>=0?t.slice(n+1):t)},r.onerror=n,r.readAsDataURL(t)}),i=await fetch(`/api/upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({conversationId:r,name:t.name,mimeType:t.type,data:e})});if(!(i.headers.get(`content-type`)??``).includes(`application/json`)){F(`服务器未返回 JSON 响应,请刷新或重启服务`,`error`);continue}if(i.ok){let e=await i.json();if(lt.current.has(n.id))continue;pt(e=>e.filter(e=>e.id!==n.id)),URL.revokeObjectURL(n.url),zt(r,t=>({...t,images:[...t.images,{id:e.id,name:e.name,mimeType:e.mimeType,bytes:e.bytes}]}))}else{let e=await i.json().catch(()=>({error:`上传失败`}));F(e.error||`上传失败: ${i.status}`,`error`)}}catch(e){F(`上传错误: ${e instanceof Error?e.message:`网络错误`}`,`error`)}finally{pt(e=>e.map(e=>e.id===n.id?{...e,failed:!0}:e)),zt(r,e=>({...e,uploading:Math.max(0,e.uploading-1)}))}}},ai=e=>{let t=e.clipboardData?.items;if(!t)return;let n=[];for(let e=0;e<t.length;e++){let r=t[e];if(r.type.startsWith(`image/`)){let e=r.getAsFile();e&&n.push(e)}}if(n.length>0){if(!An){F(`当前模型不支持图片输入`,`error`);return}e.preventDefault(),ii(n)}},oi=e=>{e.dataTransfer?.types?.includes(`Files`)&&(e.preventDefault(),_t(!0))},si=e=>{e.currentTarget.contains(e.relatedTarget)||_t(!1)},ci=e=>{_t(!1);let t=Array.from(e.dataTransfer?.files??[]).filter(e=>e.type.startsWith(`image/`));if(t.length>0){if(e.preventDefault(),!An){F(`当前模型不支持图片输入`,`error`);return}ii(t)}},ui=t=>{if(t.preventDefault(),!M||Cn||!Pt.trim()&&Ft.length===0)return;if(Ft.length>0&&!An){F(`当前模型不支持图片输入,请移除图片或切换至支持图文的模型`,`error`);return}if(Lt>0){F(`图片正在上传中,请稍候…`,`info`);return}let n=M.status===`running`,r=`pending:${nn()}`,i=M.id,a=Pt,o=Ft,s=new Set([...e.conversations.find(e=>e.id===i)?.messages.map(e=>e.id)??[],...e.queuedMessages?.map(e=>e.id)??[]]);I({type:`message.send`,conversationId:M.id,content:Pt,...Ft.length>0?{images:Ft}:{},...It.length>0?{attachments:It}:{}},`send:${i}`,e=>e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())||n?e:{...e,conversations:e.conversations.map(e=>e.id===i?{...e,messages:[...e.messages,{id:r,role:`user`,content:a,images:o,createdAt:new Date().toISOString()}]}:e)},void 0,()=>{dt(e=>[...e,{id:r,conversationId:i,content:a,images:o}]),zt(i,e=>e.text||e.images.length?e:{...e,text:a,images:o})},e=>!!(e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())))?zt(M.id,e=>({...e,text:``,images:[]})):F(`连接断开,消息未发送,草稿已保留,请重试`,`error`)},di=(0,p.useMemo)(()=>(o.queuedMessages??[]).filter(e=>e.conversationId===M?.id),[o.queuedMessages,M?.id]),fi=e=>{I({type:`message.cancel`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}))||F(`连接断开,取消没有生效,请重试`,`error`)},mi=e=>{I({type:`message.steer`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.map(t=>t.id===e?{...t,priority:!0}:t)}))||F(`连接断开,优先发送没有生效,请重试`,`error`)},hi=e=>{I({type:`message.sendQueued`,conversationId:M?.id??``,queueId:e},`send-queued:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}),void 0,void 0,t=>!t.queuedMessages?.some(t=>t.id===e))||F(`连接断开,发送没有生效,请重试`,`error`)},[_i,vi]=(0,p.useState)(),Di=(e,t=!1)=>{if(!M||_i)return;let n=M.messages.find(t=>t.id===e);n&&(n&&ct(e=>{let t=e[M.id]??{text:``,images:[],uploading:0};return t.text.trim()||t.images.length?e:{...e,[M.id]:{...t,text:n.content,images:n.images??[]}}}),$e({conversationId:M.id,messageId:e,kind:`withdraw`})),bn({type:`message.revert`,conversationId:M.id,messageId:e,withdraw:!0})?vi(e):($e(void 0),F(`连接断开,回退未执行,请重试`,`error`))},[Oi,ki]=(0,p.useState)(),Ai=(0,p.useRef)(void 0),ji=async(e,t)=>{Ai.current&&clearTimeout(Ai.current);let n=await gr(t);ki({id:e,kind:n?`copied`:`error`}),n||F(`复制失败:浏览器拒绝了剪贴板写入,可以手动选中这条消息的文字复制`,`error`),Ai.current=setTimeout(()=>ki(void 0),2e3)};(0,p.useEffect)(()=>()=>{Ai.current&&clearTimeout(Ai.current)},[]);let[Mi,Ni]=(0,p.useState)(),Pi=e=>{Mi||e.startsWith(`pending:`)||(bn({type:`conversation.fork`,conversationId:e})?Ni(e):F(`连接断开,分叉没有执行,请重试`,`error`))};return(0,m.jsxs)(`main`,{className:`shell${Ne?` shell-rail-collapsed`:``} mobile-panel-${Fe}`,"data-rail-collapsed":Ne,"data-selected-workspace":_??``,style:{"--sidebar-width":`${j.sidebar}px`,"--rail-col":Ne?void 0:`${j.rail}px`},children:[(0,m.jsxs)(`aside`,{children:[(0,m.jsxs)(`div`,{className:`brand`,children:[(0,m.jsx)(`span`,{children:`π`}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`strong`,{children:`SUPER PI`}),(0,m.jsx)(`small`,{children:`agent workspace`})]})]}),(0,m.jsx)(`div`,{className:`sidebar-actions`,children:(0,m.jsx)(`button`,{className:`workspace-new`,onClick:()=>b(!0),children:`+ 工作区`})}),(0,m.jsx)(`div`,{className:`label`,children:`工作区`}),(0,m.jsx)(`nav`,{children:o.workspaces.map(e=>{let t=o.conversations.filter(t=>t.workspaceId===e.id),n=t.filter(e=>!e.archivedAt).sort(dr),r=t.filter(e=>e.archivedAt).sort(fr),i=h[e.id]??!1,a=ue[e.id]??!0;return(0,m.jsxs)(`section`,{className:`workspace-group`,children:[(0,m.jsx)(`div`,{className:`workspace-header`,children:Sr(`workspace`,e.id)?(0,m.jsx)(Hr,{initial:e.name,label:`重命名工作区 ${e.name}`,onCommit:t=>wr(`workspace`,e.id,t),onClose:xr}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`workspace-toggle`,"aria-expanded":!i,title:e.path,onContextMenu:t=>{t.preventDefault();let n=t.currentTarget.getBoundingClientRect();tt({workspace:e,x:t.clientX||n.left+24,y:t.clientY||n.top+n.height})},onClick:()=>{v(e.id),c(Ot.find(t=>t.workspaceId===e.id)?.id),g(t=>({...t,[e.id]:!i}))},children:[(0,m.jsx)(`span`,{className:`chevron chevron-folder`,"aria-hidden":`true`,children:i?`📁`:`📂`}),(0,m.jsxs)(`span`,{children:[(0,m.jsx)(`strong`,{onDoubleClick:()=>br(`workspace`,e.id),children:e.name}),(0,m.jsx)(`small`,{children:e.path})]})]}),(0,m.jsx)(`button`,{className:`workspace-add`,title:`在此工作区新建会话`,onClick:()=>ei(e.id),children:`+`}),(0,m.jsx)(`button`,{className:`workspace-settings`,title:`工作区设置(默认智能体 / 模型 / 思考强度)`,"aria-label":`工作区设置 ${e.name}`,onClick:()=>Nr(e.id),children:`⚙`}),(0,m.jsx)(`button`,{className:`workspace-delete`,title:`删除工作区`,onClick:()=>jr(e),children:`×`})]})}),!i&&(n.length?n.map(t=>(0,m.jsx)(`div`,{className:`conversation-row`,children:Sr(`conversation`,t.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:t.title,label:`重命名会话 ${t.title}`,onCommit:e=>wr(`conversation`,t.id,e),onClose:xr}),(0,m.jsx)(`small`,{className:`conversation-time`,children:ur(t.updatedAt,Tt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`conversation ${M?.id===t.id?`selected`:``}`,"data-conversation":t.id,title:t.title,"aria-label":`打开会话 ${t.title}`,onContextMenu:e=>{e.preventDefault();let n=e.currentTarget.getBoundingClientRect();it({conversation:t,x:e.clientX||n.left+24,y:e.clientY||n.top+n.height})},onClick:()=>{Zt(t.id),v(e.id)},children:[(0,m.jsx)(`i`,{className:`conversation-mark`,children:(0,m.jsx)(Yr,{state:Jr(t,ge.some(e=>e.conversationId===t.id),C.current[t.id])})}),(0,m.jsx)(`span`,{"data-conversation-title":t.id,onDoubleClick:()=>br(`conversation`,t.id),children:t.title}),(0,m.jsx)(`small`,{children:ur(t.updatedAt,Tt)})]}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":t.id,"data-fork-state":Mi===t.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从会话 ${t.title} 分叉出一个新会话`,onClick:()=>Pi(t.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-archive`,title:`归档会话`,"aria-label":`归档会话 ${t.title}`,onClick:()=>ti(t.id),children:`×`})]})},t.id)):(0,m.jsx)(`div`,{className:`conversation-empty`,children:`暂无会话`})),!i&&r.length>0&&(0,m.jsxs)(`div`,{className:`archived-group`,"data-archived-count":r.length,children:[(0,m.jsxs)(`button`,{className:`archived-toggle`,title:a?`展开已归档会话`:`收起已归档会话`,"aria-expanded":!a,onClick:()=>de(t=>({...t,[e.id]:!a})),children:[(0,m.jsx)(`span`,{className:`chevron`,"aria-hidden":`true`,children:a?`▸`:`▾`}),(0,m.jsxs)(`span`,{className:`archived-label`,children:[`Archived (`,r.length,`)`]})]}),!a&&r.map(e=>(0,m.jsx)(`div`,{className:`conversation-archived`,"data-archived-at":e.archivedAt,children:Sr(`conversation`,e.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:e.title,label:`重命名已归档会话 ${e.title}`,onCommit:t=>wr(`conversation`,e.id,t),onClose:xr}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`i`,{className:`conversation-mark`,"aria-hidden":`true`}),(0,m.jsx)(`span`,{"data-archived-title":e.id,onDoubleClick:()=>br(`conversation`,e.id),children:e.title}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":e.id,"data-fork-state":Mi===e.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从已归档会话 ${e.title} 分叉出一个新会话`,onClick:()=>Pi(e.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-restore`,title:`恢复为正常会话`,"aria-label":`恢复会话 ${e.title}`,onClick:()=>ri(e.id),children:`↑`})]})},e.id))]})]},e.id)})}),(0,m.jsxs)(`footer`,{children:[(0,m.jsxs)(`div`,{className:`footer-status`,children:[(0,m.jsx)(`i`,{className:l?`online`:`offline`}),l?`已连接`:`正在连接`,(0,m.jsx)(`span`,{children:`本地持久化`})]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-theme-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":qr,onClick:()=>Xr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`⚙`}),`设置`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-help-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":Zr,onClick:()=>Qr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`📖`}),`使用指南`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`检查 Super Pi 更新`,onClick:async()=>{Me(`updating`);try{let e=await fetch(`/api/update-check`);if(!e.ok)throw Error(`check failed`);let t=await e.json();if(t.needsRestart)F(`当前代码已更新,正在重启服务…`,`info`);else if(t.updateAvailable){if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`发现新版本 v${t.latestVersion},正在更新并重启…`,`info`)}else F(`当前已是最新版本`,`info`)}catch{F(`检查更新失败,请检查网络或服务日志`,`error`)}finally{Me(`idle`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`↻`}),je===`updating`?`正在检查更新…`:`检查更新`]}),ke?.needsRestart&&(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`代码已更新:当前运行 v${ke.runningVersion},磁盘版本 v${ke.diskVersion}。点击复制重启命令 /super_pi restart`,onClick:async()=>{Me(`updating`);try{if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`正在更新 Super Pi,服务即将重启…`,`info`)}catch{Me(`idle`),F(`更新启动失败,请检查服务日志`,`error`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`⚠️`}),je===`updating`?`正在更新并重启…`:`更新并重启服务`]})]})]}),(0,m.jsx)(Ei,{side:`left`,width:j.sidebar,min:j.sidebarMin,max:j.sidebarMax,label:`调整左侧栏宽度`,onWidth:Ve}),(0,m.jsxs)(`section`,{className:`chat`,"data-main-view":rn,"data-main-view-kind":rn,children:[un.length>0&&(0,m.jsx)(`div`,{className:`chat-tab-strip`,"data-chat-tab-strip":`1`,"data-session-count":Xt.length,"data-file-count":P.open.length,children:(0,m.jsx)(At,{tabs:un,prefix:`workspace-tab`,ariaLabel:`工作区标签页`,activeId:dn,onSelect:fn,onClose:pn,minWidth:120,maxWidth:320})}),(0,m.jsxs)(`header`,{children:[(0,m.jsxs)(`div`,{className:`chat-head`,children:[(0,m.jsx)(`h1`,{children:M?.title??`准备开始`}),be.size>0?[...be].map(([e,t])=>(0,m.jsx)(`p`,{"data-role":`run-status`,"data-status-key":e,children:`扩展状态:${e?`${e} · `:``}${t}`},e)):(0,m.jsx)(`p`,{"data-role":`run-status`,children:M?.status===`running`?`Agent 正在工作`:`Pi runtime · Web 控制台`})]}),(0,m.jsxs)(`span`,{className:`chat-view chat-view-${rn}`,"data-main-view-indicator":rn,"data-main-view-kind":rn,title:rn===`file`?`中间主画面正在显示文件 ${P.active?.path??``}`:`中间主画面正在显示这个会话的消息流`,children:[(0,m.jsx)(`span`,{className:`chat-view-label`,children:`主区显示`}),(0,m.jsx)(`span`,{className:`chat-view-kind`,children:rn===`file`?`文件`:`会话`}),rn===`file`&&P.active&&(0,m.jsx)(`span`,{className:`chat-view-name`,"data-main-view-name":P.active.name,children:P.active.name})]}),(0,m.jsx)(`div`,{className:`badge`,children:M?.status??`idle`})]}),d&&(0,m.jsx)(`div`,{className:`error`,children:d}),M?.error&&(0,m.jsx)(`div`,{className:`error`,"data-role":`conversation-error`,children:M.error}),k&&(0,m.jsxs)(`div`,{className:`extui-title-note`,children:[`扩展建议标题:`,k]}),ve.length>0&&(0,m.jsx)(`div`,{className:`toasts`,role:`status`,"aria-live":`polite`,"data-toast-count":ve.length,children:ve.map(e=>(0,m.jsx)(`div`,{className:`toast toast-${e.kind}`,"data-toast-kind":e.kind,children:e.text},e.key))}),rn===`file`?(0,m.jsx)(`div`,{className:`chat-file-view`,"data-main-file-view":mn?.path,"data-file-view-kind":`editor`,children:mn?(0,m.jsx)(wt,{file:mn,state:P.saves[mn.path]?.state??`idle`,message:P.saves[mn.path]?.message??``,external:P.external[mn.path]===!0,onChange:e=>P.updateBuffer(mn.path,e),onSave:()=>void P.save(mn.path,!1),onReload:()=>void P.reload(mn.path),onOverwrite:()=>void P.save(mn.path,!0)}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-main-file-empty":`1`,children:`没有选中的文件。`})}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`messages`,ref:ee,"data-message-scroll-container":M?.id??``,onScroll:e=>{let t=e.currentTarget;t.dataset.programmaticScroll===`true`?delete t.dataset.programmaticScroll:Mt(t)},children:[!M&&(0,m.jsxs)(`div`,{className:`empty`,children:[(0,m.jsx)(`div`,{className:`orb`,children:`π`}),(0,m.jsx)(`h2`,{children:`开始一个新会话`}),o.workspaces.length===0?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:`先创建一个工作区,再开始会话。`}),(0,m.jsx)(`button`,{onClick:()=>b(!0),children:`+ 新建工作区`})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:o.workspaces.length===1&&kt?`在「${kt.name}」中创建会话,即可向 Super Pi 发送任务。`:`选择工作区即可开始会话,向 Super Pi 发送任务。`}),o.workspaces.length>1&&(0,m.jsxs)(`div`,{className:`empty-workspace-select`,children:[(0,m.jsx)(`label`,{htmlFor:`empty-workspace-choice`,children:`工作区:`}),(0,m.jsxs)(`select`,{id:`empty-workspace-choice`,"aria-label":`选择工作区`,value:kt?.id,onChange:e=>{e.target.value===`__new__`?b(!0):v(e.target.value)},children:[o.workspaces.map(e=>(0,m.jsx)(`option`,{value:e.id,children:e.name},e.id)),(0,m.jsx)(`option`,{value:`__new__`,children:`+ 新建工作区…`})]})]}),kt&&(0,m.jsx)(`button`,{onClick:()=>ei(kt.id),children:`新建会话`}),(0,m.jsx)(`span`,{className:`empty-secondary-action`,role:`button`,tabIndex:0,onClick:()=>b(!0),onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),b(!0))},children:`+ 新建工作区`})]})]}),Yn.map(e=>{if(e.startsWith(`m:`)){let t=qn.get(e.slice(2));if(!t)return null;let n=!M?.messages.some(n=>n.id!==t.id&&n.role===`user`&&Yn.indexOf(`m:${n.id}`)>Yn.indexOf(e)),r=t.role===`user`&&M?.status!==`running`&&_i!==t.id,i=r&&n&&!t.id.startsWith(`pending:`),a=r&&!n&&!!t.piEntryId,o=t.content?(0,m.jsx)(Ke,{source:t.content}):(0,m.jsx)(`span`,{className:`typing`,children:`正在思考`}),s=t.toolCalls??[];if(t.role===`user`){let n=Oi?.id===t.id?Oi.kind:void 0,r=t.content||(t.images?.length?`[图片]`:``);return(0,m.jsxs)(`article`,{className:`message user`,"data-transcript-key":e,children:[(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsxs)(`div`,{className:`bubble`,children:[(0,m.jsxs)(`div`,{className:`meta`,children:[he,(0,m.jsx)(`time`,{children:rr(t.createdAt)})]}),t.images&&t.images.length>0&&(0,m.jsx)(`div`,{className:`message-images`,"data-message-images":`1`,children:t.images.map(e=>(0,m.jsx)(`button`,{type:`button`,className:`message-image-btn`,"data-message-image-id":e.id,onClick:()=>ht({url:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,name:e.name,bytes:e.bytes}),title:e.name?`查看大图:${e.name}`:`查看大图`,children:(0,m.jsx)(`img`,{src:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,alt:e.name||`图片`,className:`message-image-thumb`,loading:`lazy`})},e.id))}),(0,m.jsx)(`div`,{className:`content`,children:o})]}),t.attachments&&t.attachments.length>0&&(0,m.jsx)(`div`,{className:`message-attachments`,"data-message-attachments":`1`,children:t.attachments.map(e=>{let n=`att:${t.id}:${e.kind}:${e.id}`,r=Je[n]??!1;return(0,m.jsxs)(`div`,{className:`message-attachment`,"data-message-attachment":e.kind,children:[(0,m.jsxs)(`button`,{type:`button`,className:`message-attachment-head`,"data-message-attachment-toggle":e.id,"aria-expanded":r,onClick:()=>Ye(e=>({...e,[n]:!r})),children:[(0,m.jsx)(`span`,{className:`message-attachment-glyph`,"aria-hidden":`true`,children:`▤`}),(0,m.jsx)(`span`,{className:`message-attachment-label`,children:`技能`}),(0,m.jsx)(`span`,{className:`message-attachment-name`,title:e.name,children:e.name}),(0,m.jsx)(`span`,{className:`message-attachment-chevron`,"aria-hidden":`true`,children:r?`▾`:`▸`})]}),r&&(0,m.jsx)(`pre`,{className:`message-attachment-body`,"data-message-attachment-body":e.id,children:e.content})]},n)})}),(0,m.jsxs)(`div`,{className:`message-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`message-copy`,"data-copy-message":t.id,"data-copy-state":n,"aria-label":`复制这条消息:${r}`,onClick:()=>ji(t.id,r),children:n===`copied`?`已复制`:n===`error`?`复制失败`:`复制`}),i&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-withdraw-message":t.id,title:`撤回并编辑:删除这条消息并将文字恢复到输入框继续编辑`,onClick:()=>Di(t.id,!0),children:`撤回`}),a&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-revert-message":t.id,title:`回退到这里:删除后续全部内容(Pi 的记录和这里的历史一起清掉,不会新建会话,也不动任何代码文件)`,onClick:()=>Di(t.id),children:`回退`})]})]}),(0,m.jsx)(`div`,{className:`avatar`,title:he,children:he})]},e)}if(t.role===`tool`){let n=(()=>{let e=/<summary>([\s\S]*?)<\/summary>/.exec(t.content);return e?e[1].trim():`后台回传`})(),r=(()=>{let e=/<task id="[^"]+" state="([^"]+)"/.exec(t.content);return e?e[1]:`completed`})(),i=(()=>{let e=/<task_(?:result|error)>\n?([\s\S]*?)\n?<\/(?:task_result|task_error)>/.exec(t.content);return e?e[1]:t.content})(),a=`tool:${t.id}`,o=Je[a]??!1;return(0,m.jsx)(`article`,{className:`message tool`,"data-transcript-key":e,"data-tool-state":r,children:(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsx)(`div`,{className:`bg-result-head`,"data-bg-result-head":t.id,children:(0,m.jsxs)(`button`,{type:`button`,className:`bg-result-toggle`,"data-bg-result-toggle":t.id,"aria-expanded":o,title:o?`折叠后台回传`:`展开后台回传`,onClick:()=>Ye(e=>({...e,[a]:!o})),children:[(0,m.jsx)(`span`,{className:`bg-result-glyph`,"aria-hidden":`true`,children:r===`error`?`✕`:`↩`}),(0,m.jsx)(`span`,{className:`bg-result-label`,children:`后台回传`}),(0,m.jsx)(`span`,{className:`bg-result-summary`,title:n,children:n}),!o&&(0,m.jsx)(`span`,{className:`bg-result-preview`,"data-bg-result-preview":t.id,title:`展开查看完整输出`,children:i.split(`
56
+ 如需本地计算,声明 script: { path, timeoutMs, parameters }。脚本必须是 Python,参数以 JSON 从 stdin 传入,stdout 必须只输出一个 JSON 对象;通过 POST /api/tools/<toolId>/run 创建任务,再轮询 GET /api/tools/runs/<runId> 获取状态、日志和结果,DELETE 同一路径取消。不要让网页传 shell 命令、任意文件路径或未声明参数。最后返回注册 id、访问地址、文件清单和验证结果。`);u(e),window.setTimeout(()=>u(!1),1800)};return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&n()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[n]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-settings-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&n()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-settings-title`,"data-tool-settings":`1`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsx)(`h2`,{className:`tool-dialog-title`,id:`tool-settings-title`,children:`⚙ 工具设置`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-settings-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭工具设置`,onClick:n,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[(0,m.jsxs)(`div`,{className:`tool-prompt-card`,children:[(0,m.jsx)(`strong`,{children:`给 Agent 开发新工具`}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`工具会挂载到 Super Pi 的统一 HTTP 服务,不需要再启动独立服务器。`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-development-prompt":`1`,onClick:()=>void f(),children:l?`已复制`:`复制工具开发 Prompt`})]}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`从预置列表添加`})}),(0,m.jsx)(`div`,{className:`tool-presets`,"data-tool-presets":Yt.length,children:Yt.map(n=>{let r=e.some(e=>e.kind===n.kind);return(0,m.jsxs)(`div`,{className:`tool-preset`,"data-tool-preset":n.kind,children:[(0,m.jsx)(`span`,{className:`tool-preset-glyph`,"aria-hidden":`true`,children:n.glyph}),(0,m.jsxs)(`span`,{className:`tool-preset-text`,children:[(0,m.jsx)(`strong`,{children:n.name}),(0,m.jsx)(`small`,{children:n.blurb})]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-preset-add`,"data-tool-add-preset":n.kind,disabled:r,title:r?`${n.name}已经在工具栏里了`:`添加${n.name}`,"aria-label":r?`${n.name}已添加`:`添加${n.name}`,onClick:()=>t(an(e,n.kind)),children:r?`已添加`:`添加`})]},n.kind)})}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`添加网址工具`})}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`填一个网址,它会变成一个图标;点开后在弹窗里用按钮在新标签页打开。网址只接受 http / https / mailto。`}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-input":`1`,value:r,maxLength:2048,placeholder:`https://example.com/工具`,"aria-label":`工具网址`,onChange:e=>{i(e.target.value),c(void 0)}}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-name":`1`,value:a,maxLength:40,placeholder:`名称(可留空,默认用域名)`,"aria-label":`工具名称(可选)`,onChange:e=>o(e.target.value)}),(0,m.jsx)(`button`,{type:`button`,className:`tool-url-add`,"data-tool-url-add":`1`,disabled:!r.trim(),onClick:()=>{let n=$t(r);n.ok?e.length>=32?c(`工具最多 32 个,先删掉一个再加。`):(t([...e,{id:rn(`link`,e.map(e=>e.id)),kind:`link`,name:a.trim()||si(n.url),glyph:`↗`,url:n.url}]),i(``),o(``),c(void 0)):c(n.reason)},children:`添加网址工具`}),s&&(0,m.jsx)(`p`,{className:`tool-error`,"data-tool-error":`1`,role:`alert`,children:s}),(0,m.jsxs)(`p`,{className:`tool-hint`,"data-tool-scope-note":`1`,children:[`能自定义到哪:从上面挑预置工具,或自己填一个网址。`,(0,m.jsx)(`b`,{children:`没有开放「写一段脚本来执行」这一档`}),`——它在浏览器里等同于任意代码执行,会拿着本应用的权限跑。计算器是手写的算术解析器,不含 eval,也认不出变量名。`]})]})]})}),document.body)}function si(e){try{return new URL(e,typeof window>`u`?`http://localhost`:window.location.href).hostname||e.slice(0,30)}catch{return e.slice(0,30)}}function ci({tool:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-overlay":e.id,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-dialog-title`,"data-tool-dialog":e.id,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,id:`tool-dialog-title`,children:[(0,m.jsx)(`span`,{className:`tool-dialog-glyph`,"aria-hidden":`true`,children:e.glyph}),e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭${e.name}`,onClick:t,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[e.kind===`calculator`&&(0,m.jsx)(ui,{}),e.kind===`timestamp`&&(0,m.jsx)(di,{}),e.kind===`link`&&(0,m.jsx)(fi,{tool:e})]})]})}),document.body)}function li({image:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`lightbox-overlay`,"data-lightbox-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`lightbox-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`lightbox-title`,"data-lightbox-dialog":`1`,children:[(0,m.jsxs)(`div`,{className:`lightbox-head`,children:[(0,m.jsx)(`h2`,{className:`lightbox-title`,id:`lightbox-title`,children:e.name||`图片大图`}),(0,m.jsx)(`button`,{type:`button`,className:`lightbox-close`,"data-lightbox-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭大图`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`lightbox-body`,children:(0,m.jsx)(`img`,{src:e.url,alt:e.name||`图片大图`,className:`lightbox-image`})})]})}),document.body)}function ui(){let[e,t]=(0,p.useState)(`12 * (3 + 4)`),[n,r]=(0,p.useState)(!1),i=vn(e),a=e=>{r(!1),t(t=>n?e:t+e)};return(0,m.jsxs)(`div`,{className:`tool-calc`,"data-tool-calc":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input tool-calc-input`,"data-calc-input":`1`,value:e,maxLength:240,"aria-label":`算式`,spellCheck:!1,autoComplete:`off`,placeholder:`例如 (12 + 8) / 4`,onChange:e=>{r(!1),t(e.target.value)},onKeyDown:e=>{e.key===`Enter`&&e.preventDefault()}}),(0,m.jsx)(`div`,{className:`tool-calc-keys`,"data-calc-keys":`1`,children:[[`7`,`7`],[`8`,`8`],[`9`,`9`],[`÷`,`/`],[`(`,`(`],[`)`,`)`],[`4`,`4`],[`5`,`5`],[`6`,`6`],[`×`,`*`],[`^`,`^`],[`%`,`%`],[`1`,`1`],[`2`,`2`],[`3`,`3`],[`-`,`-`],[`←`,`back`],[`C`,`clear`],[`0`,`0`],[`.`,`.`],[`+`,`+`]].map(([e,n])=>(0,m.jsx)(`button`,{type:`button`,className:`tool-calc-key`,"data-calc-key":n,"aria-label":e===`C`?`清空`:e===`←`?`退格`:`输入 ${e}`,onClick:()=>{n===`clear`?(t(``),r(!1)):n===`back`?(t(e=>e.slice(0,-1)),r(!1)):a(n)},children:e},e))}),i.ok?(0,m.jsx)(`p`,{className:`tool-calc-result`,"data-calc-result":`1`,"data-calc-value":String(i.value),children:yn(i.value)}):(0,m.jsx)(`p`,{className:`tool-calc-error`,"data-calc-error":`1`,role:`alert`,children:i.reason})]})}function di(){let[e,t]=(0,p.useState)(``),n=bn(e),r=n.ok?xn(n.ms):null;return(0,m.jsxs)(`div`,{className:`tool-stamp`,"data-tool-stamp":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input`,"data-stamp-input":`1`,value:e,maxLength:64,spellCheck:!1,"aria-label":`时间戳或日期`,placeholder:`1757000000 或 2026-10-08T09:00:00`,onChange:e=>t(e.target.value)}),r?(0,m.jsxs)(`dl`,{className:`tool-stamp-out`,"data-stamp-result":`1`,children:[(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`本地时间`}),(0,m.jsx)(`dd`,{"data-stamp-local":`1`,children:r.local})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`ISO(UTC)`}),(0,m.jsx)(`dd`,{"data-stamp-iso":`1`,children:r.iso})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`秒`}),(0,m.jsx)(`dd`,{"data-stamp-seconds":`1`,children:r.seconds})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`毫秒`}),(0,m.jsx)(`dd`,{"data-stamp-ms":`1`,children:r.milliseconds})]})]}):(0,m.jsx)(`p`,{className:`tool-stamp-error`,"data-stamp-error":`1`,role:`alert`,children:n.ok?`这个日期无法显示。`:n.reason})]})}function fi({tool:e}){let[t,n]=(0,p.useState)(!1),r=async()=>{let e=await gr(`请为 Super Pi 开发一个全局右侧栏工具并自动挂载。不要启动独立 HTTP 服务器。通过内置 tool_register 提交完整的 HTML/JS/CSS 文件;工具将被 Super Pi 保存并在所有 workspace 可用。如需计算,注册一个 Python 脚本(不接受 shell 命令),参数经 JSON stdin 传入,stdout 只输出 JSON 结果。页面用 /api/tools/<toolId>/run 创建任务、轮询 /api/tools/runs/<runId> 读取状态/日志/结果,DELETE 取消。`);n(e),window.setTimeout(()=>n(!1),1800)};return(0,m.jsxs)(`div`,{className:`tool-link`,"data-tool-link":e.id,children:[(0,m.jsx)(`p`,{className:`tool-hint`,children:`这是旧版外部网页链接;新工具由 Super Pi 统一托管,并可选绑定受控 Python 脚本。`}),(0,m.jsx)(`code`,{className:`tool-link-url`,"data-tool-link-url":e.id,children:e.url}),(0,m.jsxs)(`div`,{className:`tool-link-actions`,children:[(0,m.jsx)(`a`,{className:`tool-link-open`,"data-tool-link-open":e.id,href:e.url,target:`_blank`,rel:`noopener noreferrer`,title:e.url,children:`在新标签页打开 ?`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-copy-prompt":e.id,onClick:()=>void r(),children:t?`已复制`:`复制开发 Prompt`})]}),/^https?:\/\//i.test(e.url??``)&&(0,m.jsx)(`iframe`,{className:`tool-link-frame`,"data-tool-frame":e.id,src:e.url,title:e.name,sandbox:`allow-forms allow-modals allow-pointer-lock allow-popups allow-same-origin allow-scripts`,referrerPolicy:`no-referrer`})]})}var pi=`(max-width: 1100px)`,mi=(e,t,n)=>Math.min(Math.max(e,t),Math.max(t,n));function hi({tool:e,onClose:t}){return(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,children:[`🧩 `,e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`tool-dialog-body`,children:(0,m.jsx)(`iframe`,{className:`tool-link-frame`,src:`/tools/${encodeURIComponent(e.id)}/`,title:e.name,sandbox:`allow-forms allow-modals allow-popups allow-scripts`,referrerPolicy:`no-referrer`})})]})}),document.body)}function gi(e,t,n){let r=Math.max(0,Math.round(n)-480),i=mi(520,180,Math.max(180,r-240)),a=mi(Math.round(e),180,i),o=mi(640,240,Math.max(240,r-a));return{sidebar:a,rail:mi(Math.round(t),240,o),sidebarMin:180,sidebarMax:i,railMin:240,railMax:o,sumCap:r}}var _i=`super-pi.rail-widths.v1`,vi=`super-pi.reader-state.v1`,yi=48,bi=160,xi=e=>{e.dataset.programmaticScroll=`true`,window.setTimeout(()=>{delete e.dataset.programmaticScroll},0)};function Si(){try{if(typeof window>`u`)return{conversations:{}};let e=JSON.parse(window.localStorage.getItem(`super-pi.reader-state.v1`)??`null`);if(!e||typeof e!=`object`)return{conversations:{}};let t={};if(e.conversations&&typeof e.conversations==`object`)for(let[n,r]of Object.entries(e.conversations)){let e=r;typeof e.followTail==`boolean`&&(t[n]={followTail:e.followTail,scrollTop:Number.isFinite(e.scrollTop)?Math.max(0,Number(e.scrollTop)):0,...typeof e.anchorKey==`string`?{anchorKey:e.anchorKey}:{},...Number.isFinite(e.anchorOffset)?{anchorOffset:Number(e.anchorOffset)}:{}})}let n={};if(e.readAt&&typeof e.readAt==`object`)for(let[t,r]of Object.entries(e.readAt))typeof r==`string`&&(n[t]=r);return{activeId:typeof e.activeId==`string`?e.activeId:void 0,workspaceId:typeof e.workspaceId==`string`?e.workspaceId:void 0,conversations:t,readAt:n}}catch{return{conversations:{}}}}function Ci(e){try{typeof window<`u`&&window.localStorage.setItem(vi,JSON.stringify(e))}catch{}}function wi(){try{if(typeof window>`u`)return{};let e=window.localStorage.getItem(_i);if(e===null)return{};let t=JSON.parse(e),n=e=>typeof e==`number`&&Number.isFinite(e)&&e>0?Math.round(e):void 0;return{sidebar:n(t.sidebar),rail:n(t.rail)}}catch{return{}}}function Ti(e){try{if(typeof window>`u`)return;window.localStorage.setItem(_i,JSON.stringify(e))}catch{}}function Ei({side:e,width:t,min:n,max:r,label:i,onWidth:a}){let[o,s]=(0,p.useState)(!1),c=e=>e.shiftKey?64:16;return(0,m.jsx)(`div`,{className:`rail-resizer rail-resizer-${e}${o?` rail-resizer-dragging`:``}`,"data-rail-resizer":e,"data-rail-resizer-dragging":o?`true`:`false`,"data-rail-resizer-width":t,role:`separator`,"aria-orientation":`vertical`,"aria-label":i,"aria-valuenow":t,"aria-valuemin":n,"aria-valuemax":r,tabIndex:0,onMouseDown:i=>{i.preventDefault(),i.currentTarget.focus();let o=i.clientX,c=t;s(!0);let l=document.body,u=l.style.cursor,d=l.style.userSelect;l.style.cursor=`col-resize`,l.style.userSelect=`none`;let f=t=>{let i=e===`left`?t.clientX-o:o-t.clientX;a(mi(c+i,n,r))},p=()=>{l.style.cursor=u,l.style.userSelect=d,s(!1),window.removeEventListener(`mousemove`,f),window.removeEventListener(`mouseup`,p)};window.addEventListener(`mousemove`,f),window.addEventListener(`mouseup`,p)},onKeyDown:i=>{let o=c(i);i.key===`ArrowLeft`?(i.preventDefault(),a(mi(e===`left`?t-o:t+o,n,r))):i.key===`ArrowRight`?(i.preventDefault(),a(mi(e===`left`?t+o:t-o,n,r))):i.key===`Home`?(i.preventDefault(),a(n)):i.key===`End`&&(i.preventDefault(),a(r))}})}function Di(){let[e,t]=(0,p.useState)(tr),[n,r]=(0,p.useState)([]),i=(0,p.useRef)([]),a=(0,p.useRef)(null);a.current||=Si();let o=n.reduce((e,t)=>t.apply(e),e),[s,c]=(0,p.useState)(a.current.activeId),[l,u]=(0,p.useState)(!1),[d,f]=(0,p.useState)(),[h,g]=(0,p.useState)({}),[_,v]=(0,p.useState)(a.current.workspaceId),[y,b]=(0,p.useState)(!1),x=(0,p.useRef)(void 0),S=(0,p.useRef)(a.current.conversations),C=(0,p.useRef)(a.current.readAt??{}),[w,T]=(0,p.useState)(0),ee=(0,p.useRef)(null),E=(0,p.useRef)(null),te=(0,p.useRef)(void 0),ne=(0,p.useRef)(a.current.activeId),[re,ie]=(0,p.useState)({followTail:!0,scrollTop:0}),ae=(0,p.useRef)(void 0),oe=(0,p.useRef)(new Map),se=(0,p.useRef)(void 0),D=(0,p.useCallback)(()=>{if(se.current!==void 0&&(cancelAnimationFrame(se.current),se.current=void 0),!oe.current.size)return;let e=[...oe.current.values()];oe.current.clear();let n=new Map,r=new Map;for(let t of e){let e=`${t.conversationId}\u0000${t.messageId}`;if(t.type===`message.thinking`){let n=r.get(e);r.set(e,{text:`${n?.text??``}${t.delta}`,at:n?.at??t.at})}else n.set(e,`${n.get(e)??``}${t.delta}`)}t(e=>({...e,conversations:e.conversations.map(e=>{let t=e.messages.map(t=>{let i=`${e.id}\u0000${t.id}`,a=n.get(i),o=r.get(i);return a===void 0&&o===void 0?t:{...t,...a===void 0?{}:{content:t.content+a},...o===void 0?{}:{thinking:(t.thinking??``)+o.text,thinkingBlocks:[...t.thinkingBlocks??[],{at:o.at??t.content.length,text:o.text}]}}});return t.every((t,n)=>t===e.messages[n])?e:{...e,messages:t}})}))},[]),ce=(0,p.useCallback)(e=>{let t=`${e.type}\u0000${e.conversationId}\u0000${e.messageId}`,n=oe.current.get(t);oe.current.set(t,n?{...n,delta:n.delta+e.delta}:e),se.current===void 0&&(se.current=requestAnimationFrame(D))},[D]),le=(0,p.useCallback)(e=>{ae.current=e},[]),[ue,de]=(0,p.useState)({}),[fe,pe]=(0,p.useState)(()=>{if(typeof window>`u`)return``;try{return window.localStorage.getItem(`super-pi.user-nickname`)??``}catch{return``}}),me=e=>{let t=e.slice(0,24);pe(t);try{window.localStorage.setItem(`super-pi.user-nickname`,t)}catch{}},he=fe.trim()||`你`,[ge,_e]=(0,p.useState)([]),[ve,ye]=(0,p.useState)([]),[be,xe]=(0,p.useState)(new Map),[Se,O]=(0,p.useState)(new Map),[k,Ce]=(0,p.useState)(),[we,Te]=(0,p.useState)(),[Ee,De]=(0,p.useState)(),[A,Oe]=(0,p.useState)(),[ke,Ae]=(0,p.useState)(),[je,Me]=(0,p.useState)(`idle`),[Ne,Pe]=(0,p.useState)(()=>typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(max-width: 1100px)`).matches),[Fe,Ie]=(0,p.useState)(`chat`),[Le,Re]=(0,p.useState)(()=>typeof window>`u`?1440:window.innerWidth);(0,p.useEffect)(()=>{let e=()=>Re(window.innerWidth);return window.addEventListener(`resize`,e),e(),()=>window.removeEventListener(`resize`,e)},[]);let[ze,Be]=(0,p.useState)(wi),j=gi(ze.sidebar??270,ze.rail??320,Le);(0,p.useEffect)(()=>{Ti({sidebar:j.sidebar,rail:j.rail})},[j.sidebar,j.rail]);let Ve=e=>Be(t=>({...t,sidebar:e})),He=e=>Be(t=>({...t,rail:e}));(0,p.useEffect)(()=>{if(typeof window>`u`||typeof window.matchMedia!=`function`)return;let e=window.matchMedia(pi),t=e=>{e.matches&&Pe(!0)};return e.addEventListener(`change`,t),e.matches&&Pe(!0),()=>e.removeEventListener(`change`,t)},[]);let[Ue,We]=(0,p.useState)(new Map),[Ge,qe]=(0,p.useState)({}),[Je,Ye]=(0,p.useState)({}),[Xe,Ze]=(0,p.useState)({}),[Qe,$e]=(0,p.useState)(),[et,tt]=(0,p.useState)(),[nt,it]=(0,p.useState)(),ot=(0,p.useRef)(new Map),[st,ct]=(0,p.useState)({}),lt=(0,p.useRef)(new Set),[ut,dt]=(0,p.useState)([]),[ft,pt]=(0,p.useState)([]),[mt,ht]=(0,p.useState)(),[gt,_t]=(0,p.useState)(!1),vt=(0,p.useRef)(null),yt=(0,p.useRef)(!1),bt=(0,p.useRef)(0),xt=(0,p.useRef)([]),St=(0,p.useRef)(void 0),Ct=(0,p.useRef)(!1),[Tt,Dt]=(0,p.useState)(()=>Date.now());(0,p.useEffect)(()=>{let e=window.setInterval(()=>Dt(Date.now()),3e4);return()=>window.clearInterval(e)},[]);let Ot=(0,p.useMemo)(()=>o.conversations.filter(e=>!e.archivedAt),[o.conversations]),M=(0,p.useMemo)(()=>Ot.find(e=>e.id===s)??Ot.find(e=>e.workspaceId===_)??(_&&o.workspaces.some(e=>e.id===_)?void 0:Ot[0]),[s,Ot,_,o.workspaces]),kt=(0,p.useMemo)(()=>o.workspaces.find(e=>e.id===_)??o.workspaces[0],[o.workspaces,_]),jt=(0,p.useRef)(_);jt.current=_,(0,p.useEffect)(()=>{M&&v(M.workspaceId)},[M?.id]),(0,p.useEffect)(()=>{St.current=s},[s]),(0,p.useEffect)(()=>{te.current=M?.id},[M?.id]),(0,p.useEffect)(()=>{let e=M?.id;if(!e)return;let t=o.conversations.find(t=>t.id===e);if(!t)return;let n=C.current[e];C.current[e]=t.updatedAt,n!==t.updatedAt&&T(e=>e+1),Ci({activeId:St.current,workspaceId:M?.workspaceId??_,conversations:S.current,readAt:C.current})},[M?.id,o.conversations]),(0,p.useEffect)(()=>{l&&s&&Ci({activeId:s,workspaceId:M?.workspaceId??_,conversations:S.current})},[s,M?.workspaceId,l,_]),(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=M.id,n=S.current[t]??{followTail:!0,scrollTop:0};ie(n),requestAnimationFrame(()=>{if(ee.current===e){if(n.followTail)xi(e),e.scrollTop=e.scrollHeight;else{let t=n.anchorKey?e.querySelector(`[data-transcript-key="${CSS.escape(n.anchorKey)}"]`):null;e.scrollTop=t?Math.max(0,e.scrollTop+t.getBoundingClientRect().top-(n.anchorOffset??0)-e.getBoundingClientRect().top):n.scrollTop}}}),ne.current=void 0},[M?.id]);let Mt=(0,p.useCallback)((e,t=!1,n=!1)=>{let r=te.current;if(!r)return;let i=e.scrollHeight-e.clientHeight-e.scrollTop,a=S.current[r]??{followTail:!0,scrollTop:0},o=t?!1:n?!0:a.followTail?i<=bi:i<=yi,s,c;if(!o){let t=e.getBoundingClientRect().top,n=[...e.querySelectorAll(`[data-transcript-key]`)].find(e=>e.getBoundingClientRect().bottom>t);s=n?.dataset.transcriptKey,c=n?n.getBoundingClientRect().top-t:void 0}let l={followTail:o,scrollTop:e.scrollTop,...s?{anchorKey:s,anchorOffset:c}:{}};S.current[r]=l,ie(l),Ci({activeId:r,workspaceId:jt.current,conversations:S.current})},[]),Nt=st[M?.id??``]??{text:``,images:[],attachments:[],uploading:0},Pt=Nt.text,Ft=Nt.images,It=Nt.attachments,Lt=Nt.uploading,zt=(e,t)=>ct(n=>({...n,[e]:t(n[e]??{text:``,images:[],attachments:[],uploading:0})})),Vt=e=>zt(M?.id??``,t=>({...t,text:e})),Ht=e=>zt(M?.id??``,t=>({...t,attachments:t.attachments.some(t=>t.kind===e.kind&&t.id===e.id)?t.attachments:[...t.attachments,e]})),Ut=(e,t)=>zt(M?.id??``,n=>({...n,attachments:n.attachments.filter(n=>n.kind!==e||n.id!==t)})),[Wt,Gt]=(0,p.useState)({visible:!1,query:``,rect:null}),Kt=e=>zt(M?.id??``,t=>({...t,images:typeof e==`function`?e(t.images):e})),[qt,Jt]=(0,p.useState)([]),Yt=qt.length>0?qt:M?[M.id]:[],Xt=(0,p.useMemo)(()=>Yt.map(e=>Ot.find(t=>t.id===e)).filter(e=>e!==void 0),[Yt,Ot]),Zt=(0,p.useCallback)(e=>{c(e);let t=Ot.find(t=>t.id===e)?.workspaceId;t&&v(t),an(`session`),Jt(t=>t.includes(e)?t:t.length>0?[...t,e]:[...M?.id&&M.id!==e?[M.id]:[],e])},[M]),N=(0,p.useCallback)(e=>{let t=qt.indexOf(e);if(t<0)return;let n=qt.filter(t=>t!==e);if(Jt(n),M?.id!==e)return;let r=n[Math.min(t,n.length-1)];r&&c(r)},[qt,M]),P=Et(_),[Qt,$t]=(0,p.useState)(Gr[0].id),[en,tn]=(0,p.useState)(!1);(0,p.useEffect)(()=>{let e=!1;return fetch(`/api/terminal`).then(e=>e.json()).then(t=>{e||tn(!!t?.enabled)}).catch(()=>{e||tn(!1)}),()=>{e=!0}},[]);let[rn,an]=(0,p.useState)(`session`);(0,p.useEffect)(()=>{rn===`file`&&!P.active&&an(`session`)},[rn,P.active]);let on=(0,p.useCallback)(e=>{P.setActivePath(e),an(`file`),$t(`files`)},[P.setActivePath]),sn=(0,p.useCallback)(async e=>{await P.openFile(e),an(`file`)},[P.openFile]),cn=(0,p.useMemo)(()=>Xt.map(e=>({id:`session:${e.id}`,rawId:e.id,kind:`session`,label:e.title,title:e.title,closable:Xt.length>1||P.open.length>0,badge:(0,m.jsx)(Yr,{state:Jr(e,ge.some(t=>t.conversationId===e.id),C.current[e.id])})})),[Xt,P.open.length,ge,o.conversations,w]),ln=(0,p.useMemo)(()=>P.open.map(e=>({id:`file:${e.path}`,rawId:e.path,kind:`file`,label:e.name,title:e.path,closable:!0,badge:e.dirty?(0,m.jsx)(`span`,{className:`file-tab-badge`,"data-file-tab-dirty":e.path,"aria-label":`未保存`,children:`●`}):void 0})),[P.open]),un=(0,p.useMemo)(()=>[...cn,...ln],[cn,ln]),dn=rn===`file`&&P.activePath?`file:${P.activePath}`:M?.id?`session:${M.id}`:void 0,fn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);on(t)}else if(e.startsWith(`session:`)){let t=e.slice(8);Zt(t)}},[on,Zt]),pn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);P.closeFile(t),P.activePath===t&&P.open.length<=1&&an(`session`)}else if(e.startsWith(`session:`)){let t=e.slice(8);N(t)}},[P,N]),mn=P.active,hn=M?.id;(0,p.useEffect)(()=>{hn&&!hn.startsWith(`pending:`)&&bn({type:`conversation.select`,conversationId:hn})},[hn]);let F=(e,t=`info`)=>{if(!e)return;let n=++bt.current;ye(r=>[...r,{key:n,text:e,kind:t}]),xt.current.push(window.setTimeout(()=>ye(e=>e.filter(e=>e.key!==n)),7e3))},gn=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},_n=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},vn=(e,t)=>{if(t.blocking)_e(n=>[...n.filter(n=>n.conversationId!==e||n.request.requestId!==t.requestId),{conversationId:e,request:t}]);else if(t.method===`notify`)F(t.message||t.title,t.notifyType??`info`);else if(t.method===`setStatus`){let e=t.statusKey??``;xe(n=>{let r=new Map(n);return t.statusText===void 0?r.delete(e):r.set(e,t.statusText),r})}else if(t.method===`setWidget`){let e=t.widgetKey??``;O(n=>{let r=new Map(n);return t.widgetLines===void 0?r.delete(e):r.set(e,{lines:t.widgetLines,placement:t.widgetPlacement??`aboveEditor`}),r})}else t.method===`setTitle`?Ce(t.title):t.method===`set_editor_text`&&e===St.current&&Vt(t.text??``)},yn=(e,t,n)=>{_e(n=>n.filter(n=>n.conversationId!==e||n.request.requestId!==t));let r=Dr[n];r&&F(r,`warning`)};(0,p.useEffect)(()=>{let e=!1,n=0,o,s=()=>{if(e)return;let l=location.protocol===`https:`?`wss`:`ws`,d=new WebSocket(`${l}://${location.host}/ws`);x.current=d,d.onopen=()=>{e||(n=0,u(!0),d.send(JSON.stringify({type:`stream.configure`,coalesce:!0})),d.send(JSON.stringify({type:`catalog.fetch`,conversationId:St.current})),d.send(JSON.stringify({type:`agent.catalog.fetch`})),d.send(JSON.stringify({type:`subagent.catalog.fetch`})))},d.onclose=()=>{if(!e){Ct.current=!1;for(let e of i.current)e.onFailure?.();i.current=[],r([]),u(!1),o=window.setTimeout(s,Math.min(hr,mr*2**n++))}},d.onmessage=({data:n})=>{if(e)return;let o=JSON.parse(n);if(o.type!==`message.delta`&&o.type!==`message.thinking`&&D(),o.type.startsWith(`terminal.`))ae.current?.(o);else if(o.type===`conversation.autoRename.result`){let e=ot.current.get(o.conversationId);e&&(ot.current.delete(o.conversationId),o.ok?e.resolve(o.title):e.reject(Error(o.error)))}else if(o.type===`message.thinking`)ce(o);else if(o.type===`state`)D(),Ct.current=!1,i.current=i.current.filter(e=>!e.accepted?.(o.state)),r(i.current),t(o.state),u(!0),c(e=>{let t=o.state.conversations.filter(e=>!e.archivedAt),n=a.current?.activeId,r=!!(e&&t.some(t=>t.id===e)),s=!!(e&&i.current.some(t=>t.temporaryId===e));return r||s?e:n&&t.some(e=>e.id===n)?n:t.find(e=>e.workspaceId===jt.current)?.id}),v(e=>{let t=a.current?.workspaceId;return t&&o.state.workspaces.some(e=>e.id===t)?t:e&&(o.state.workspaces.some(t=>t.id===e)||i.current.some(t=>t.temporaryId===e))?e:o.state.workspaces[0]?.id});else if(o.type===`message.delta`)ce(o);else if(o.type===`message.revert.result`)vi(void 0),$e(void 0),o.ok||f(o.message);else if(o.type===`operation.result`){let e=i.current.find(e=>e.id===o.requestId);if(i.current=i.current.filter(e=>e.id!==o.requestId),r(i.current),e?.temporaryId){let t=e.temporaryId;c(n=>n===t?o.ok?o.entityId:e.previousActiveId:n),Jt(e=>e.flatMap(e=>e===t?o.ok&&o.entityId?[o.entityId]:[]:[e])),v(e=>e===t?o.ok?o.entityId:void 0:e)}o.ok||(e?.onFailure?.(),f(o.message??`操作失败`))}else o.type===`error`?(f(o.message),Ct.current&&(Ct.current=!1,F(`重命名失败:${o.message}`,`error`))):o.type===`extension.ui`?vn(o.conversationId,o.request):o.type===`extension.ui.closed`?yn(o.conversationId,o.requestId,o.reason):o.type===`model.catalog`?(!o.conversationId||o.conversationId===St.current)&&Te(o.catalog):o.type===`agent.catalog`?De(o.catalog):o.type===`subagent.catalog`?Oe(o.catalog):o.type===`server.version`?(Ae(o.version),o.version.needsRestart&&F(`检测到 Super Pi 代码已更新(v${o.version.runningVersion} → v${o.version.diskVersion}),请执行 /super_pi restart 重启服务生效`,`warning`)):o.type===`subagent.event`?t(e=>({...e,subagentRuns:gn(e.subagentRuns??[],o.run)})):o.type===`workflow.event`?t(e=>({...e,workflows:_n(e.workflows??[],o.workflow)})):o.type===`message.tools`?t(e=>({...e,conversations:e.conversations.map(e=>e.id===o.conversationId?{...e,messages:e.messages.map(e=>e.id===o.messageId?{...e,toolCalls:o.toolCalls}:e)}:e)})):o.type===`pi.queue`?We(e=>{let t=new Map(e);return t.set(o.conversationId,{steering:o.steering,followUp:o.followUp}),t}):o.type===`conversation.fork.result`&&(Ni(e=>e===o.conversationId?void 0:e),F(o.ok?`已从「${o.title}」分叉出新会话,放在侧边栏最上面;没有切换过去。${o.messagesCopied?`复制了 ${o.messagesCopied} 条消息。`:`原会话还没有消息,只复制了会话本身。`}`:`分叉失败:${o.message}`,o.ok?`info`:`error`))}};return s(),()=>{e=!0,D(),window.clearTimeout(o);for(let e of xt.current)window.clearTimeout(e);x.current?.close()}},[ce,D]);let bn=(0,p.useCallback)(e=>x.current?.readyState===WebSocket.OPEN&&(x.current.send(JSON.stringify(e)),!0),[]),I=(e,t,n,a,o,s)=>{if(x.current?.readyState!==WebSocket.OPEN||i.current.some(e=>e.key===t))return!1;let c=nn(),l={id:c,key:t,apply:n,temporaryId:a,previousActiveId:St.current,onFailure:o,accepted:s};return i.current=[...i.current,l],r(i.current),x.current.send(JSON.stringify({...e,operationRequestId:c})),!0},xn=(e,t)=>n=>({...n,conversations:n.conversations.map(n=>n.id===e?{...n,...t}:n)}),Sn=(e,t)=>n=>({...n,workspaces:n.workspaces.map(n=>n.id===e?{...n,...t}:n)}),Cn=n.some(e=>e.key===`send:${M?.id}`||e.key===`configuration:${M?.id}`||e.temporaryId===M?.id||e.key===`archive:${M?.id}`),wn=(e,t,n,r,i)=>{e&&x.current?.send(JSON.stringify({type:`question.answer`,conversationId:e,messageId:t,callIndex:n,answers:r,cancelled:i}))},Tn=(e,t,n)=>I({type:`extension.ui.answer`,conversationId:e,requestId:t,...n},`answer:${e}:${t}`,e=>e),En=M?.model??we?.defaultModel,Dn=M?.thinkingLevel??we?.defaultThinkingLevel,On=we?.source===`pi`,An=!!we?.models.find(e=>e.provider===En?.provider&&e.modelId===En?.modelId)?.imageInput,jn=(e,t)=>!e||!t?e:{...e,models:e.models.filter(e=>(o.modelFilterRules??[]).every(n=>{try{return!new RegExp(n.workspacePattern,`i`).test(t.name)||new RegExp(n.modelPattern,`i`).test(e.label)||new RegExp(n.modelPattern,`i`).test(`${e.provider}/${e.modelId}`)}catch{return!0}}))},Mn=(jn(we,o.workspaces.find(e=>e.id===M?.workspaceId))?.models??[]).map(e=>({key:`${e.provider}/${e.modelId}`,label:e.label,detail:e.detail,badge:Cr(e),selected:En?.provider===e.provider&&En.modelId===e.modelId})),Nn=(we?.levels??[]).map(e=>({key:e,label:e,selected:e===Dn})),Pn=e=>{if(!M)return;let[t,n]=e.split(`/`);t&&n&&I({type:`conversation.model`,conversationId:M.id,provider:t,modelId:n},`configuration:${M.id}`,xn(M.id,{model:we?.models.find(e=>e.provider===t&&e.modelId===n)??{provider:t,modelId:n,label:n}}))},Fn=e=>{M&&I({type:`conversation.thinking`,conversationId:M.id,level:e},`configuration:${M.id}`,xn(M.id,{thinkingLevel:e}))},In=Ee?.agents.find(e=>e.id===M?.agentId),Rn=[{key:L,label:`跟随 Pi 默认`,detail:`不使用任何 agent 角色`,selected:!M?.agentId},...(Ee?.agents??[]).map(e=>({key:e.id,label:e.name,detail:e.toolSummary,color:e.color,selected:M?.agentId===e.id}))],zn=In?.model,Bn=!!(zn&&M?.model&&M.model.provider===zn.provider&&M.model.modelId===zn.modelId);In?.thinkingLevel&&(M?.thinkingLevel,In.thinkingLevel);let Vn=e=>{if(M){let t=Ee?.agents.find(t=>t.id===e);I({type:`conversation.agent`,conversationId:M.id,agentId:e===L?null:e},`configuration:${M.id}`,xn(M.id,{agentId:t?.id,...t?.model?{model:{...t.model,label:t.model.modelId}}:{},...t?.thinkingLevel?{thinkingLevel:t.thinkingLevel}:{}}))}},Hn=ge.filter(e=>e.conversationId===M?.id),Un=(0,p.useMemo)(()=>(o.subagentRuns??[]).filter(e=>e.conversationId===M?.id),[o.subagentRuns,M?.id]),Wn=(0,p.useMemo)(()=>(o.workflows??[]).filter(e=>e.conversationId===M?.id),[o.workflows,M?.id]),Gn=(0,p.useMemo)(()=>new Map(Wn.map(e=>[e.id,e])),[Wn]),Kn=(0,p.useMemo)(()=>new Map((A?.agents??[]).map(e=>[e.id,e.name])),[A]),qn=(0,p.useMemo)(()=>new Map((M?.messages??[]).map(e=>[e.id,e])),[M?.messages]),Jn=(0,p.useMemo)(()=>new Map(Un.map(e=>[e.id,e])),[Un]),Yn=(0,p.useMemo)(()=>{if(!M)return[];let e=Qe?.conversationId===M.id?(()=>{let e=M.messages.find(e=>e.id===Qe.messageId);if(e)return{index:M.messages.indexOf(e),at:Date.parse(e.createdAt)||0,...Qe.kind===`withdraw`?{inclusive:!0}:{inclusive:!1}}})():void 0,t=[];return M.messages.forEach((n,r)=>{e&&(r>e.index||e.inclusive&&r===e.index)||t.push({at:Date.parse(n.createdAt)||0,seq:r,key:`m:${n.id}`})}),Un.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`r:${n.id}`})}),Wn.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`w:${n.id}`})}),t.sort((e,t)=>e.at-t.at||e.seq-t.seq).map(e=>e.key)},[M,Un,Wn,Qe]);(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=()=>{S.current[M.id]?.followTail&&(xi(e),e.scrollTop=e.scrollHeight)};if(t(),typeof ResizeObserver>`u`)return;let n=new ResizeObserver(t);n.observe(e);for(let t of e.children)n.observe(t);return()=>n.disconnect()},[M?.id,M?.messages,Un,Wn,Yn]);let Xn=e=>qe(t=>({...t,[e]:!t[e]})),Zn=(e,t)=>Ye(n=>({...n,[`${e}:${t}`]:!n[`${e}:${t}`]})),Qn=(e,t)=>Ze(n=>({...n,[`${e}:${t}`]:n[`${e}:${t}`]!==!1})),[$n,er]=(0,p.useState)({}),ir=e=>er(t=>({...t,[e]:t[e]===!1})),ar=e=>{I({type:`subagent.stop`,runId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止子智能体,请重试`,`error`)},or=e=>{I({type:`workflow.stop`,workflowId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止编排,请重试`,`error`)},sr=e=>[...Se].filter(([,t])=>t.placement===e).map(([e,t])=>{let n=t.lines;return(0,m.jsxs)(`div`,{className:`extui-widget`,"data-widget-key":e,"data-widget-lines":n.length,children:[n.slice(0,Or).map((e,t)=>(0,m.jsx)(`div`,{children:e},`${t}:${e}`)),n.length>Or&&(0,m.jsx)(`div`,{children:`… (widget truncated)`})]},e)}),cr=(e,t)=>{let n=`pending:${nn()}`,r={id:n,name:e,path:t,updatedAt:new Date().toISOString()};I({type:`workspace.create`,name:e,path:t},`create-workspace`,e=>({...e,workspaces:[...e.workspaces,r]}),n)&&(v(n),c(void 0),b(!1))},[lr,yr]=(0,p.useState)(),br=(e,t)=>yr(n=>n?.kind===e&&n.id===t?n:{kind:e,id:t}),xr=()=>yr(void 0),Sr=(e,t)=>lr?.kind===e&&lr.id===t,wr=(e,t,n)=>t.startsWith(`pending:`)?!1:I(e===`workspace`?{type:`workspace.rename`,workspaceId:t,name:n}:{type:`conversation.rename`,conversationId:t,name:n},`rename:${t}`,e===`workspace`?Sn(t,{name:n}):xn(t,{title:n}))?(Ct.current=!0,xr(),!0):(F(`连接断开,改名未保存,请重试`,`error`),!1),Tr=async e=>{if(e.startsWith(`pending:`)){F(`这个会话还在创建中,稍后再试`,`warning`);return}if(ot.current.has(e))return;F(`正在根据对话内容生成标题…`,`info`);let t=new Promise((t,n)=>{ot.current.set(e,{resolve:t,reject:n}),window.setTimeout(()=>{ot.current.delete(e)&&n(Error(`模型在 30 秒内没有返回标题`))},32e3)});if(!bn({type:`conversation.autoRename`,conversationId:e}))ot.current.delete(e),F(`连接断开,无法生成标题`,`error`);else try{let n=await t;wr(`conversation`,e,n),F(`已重命名为「${n}」`,`info`)}catch(e){F(`重命名失败:${e instanceof Error?e.message:String(e)}`,`error`)}},jr=e=>{if(e.id.startsWith(`pending:`))return;let t=o.conversations.filter(t=>t.workspaceId===e.id).length;window.confirm(`删除工作区「${e.name}」?${t?`其中 ${t} 条会话将一并删除,`:``}此操作不可撤销。`)&&I({type:`workspace.delete`,workspaceId:e.id},`delete:${e.id}`,t=>({...t,workspaces:t.workspaces.filter(t=>t.id!==e.id),conversations:t.conversations.filter(t=>t.workspaceId!==e.id)}))},[Mr,Nr]=(0,p.useState)(),Fr=Mr?o.workspaces.find(e=>e.id===Mr):void 0,{theme:Br,setTheme:Kr}=kn(),[qr,Xr]=(0,p.useState)(!1),[Zr,Qr]=(0,p.useState)(!1),$r=(e,t,n,r)=>{e.startsWith(`pending:`)||(I({type:`workspace.settings`,workspaceId:e,model:n?{provider:n.provider,modelId:n.modelId}:null,thinkingLevel:r??null,agentId:t??null},`defaults:${e}`,Sn(e,{defaultModel:n,defaultThinkingLevel:r,defaultAgentId:t}))?Nr(void 0):F(`连接断开,设置未保存,请重试`,`error`))},ei=e=>{if(e.startsWith(`pending:`)||i.current.some(t=>t.key===`defaults:${e}`))return;let t=`pending:${nn()}`,n={id:t,workspaceId:e,title:`新会话`,status:`idle`,updatedAt:new Date().toISOString(),messages:[]};I({type:`conversation.create`,workspaceId:e},`create-session:${e}`,e=>({...e,conversations:[n,...e.conversations]}),t)&&(v(e),c(t),an(`session`),Jt(e=>[...e.filter(e=>e!==t),t]))},ti=e=>{e.startsWith(`pending:`)||I({type:`conversation.archive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:new Date().toISOString()}))},ri=e=>I({type:`conversation.unarchive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:void 0})),ii=async e=>{if(!M){F(`请先选择或创建会话`,`error`);return}if(!An){F(`当前模型不支持图片输入`,`error`);return}let t=[`image/png`,`image/jpeg`,`image/gif`,`image/webp`],n=e.filter(e=>t.includes(e.type)?e.size>5242880?(F(`图片「${e.name}」超过 5MB 上限`,`error`),!1):!0:(F(`不支持的文件格式「${e.name}」,仅支持 PNG/JPEG/GIF/WebP`,`error`),!1));if(n.length===0)return;let r=M.id;zt(r,e=>({...e,uploading:e.uploading+n.length}));let i=n.map(e=>({id:nn(),conversationId:r,name:e.name,url:URL.createObjectURL(e)}));pt(e=>[...e,...i]);for(let[e,t]of n.entries()){let n=i[e];try{let e=await new Promise((e,n)=>{let r=new FileReader;r.onload=()=>{let t=r.result,n=t.indexOf(`,`);e(n>=0?t.slice(n+1):t)},r.onerror=n,r.readAsDataURL(t)}),i=await fetch(`/api/upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({conversationId:r,name:t.name,mimeType:t.type,data:e})});if(!(i.headers.get(`content-type`)??``).includes(`application/json`)){F(`服务器未返回 JSON 响应,请刷新或重启服务`,`error`);continue}if(i.ok){let e=await i.json();if(lt.current.has(n.id))continue;pt(e=>e.filter(e=>e.id!==n.id)),URL.revokeObjectURL(n.url),zt(r,t=>({...t,images:[...t.images,{id:e.id,name:e.name,mimeType:e.mimeType,bytes:e.bytes}]}))}else{let e=await i.json().catch(()=>({error:`上传失败`}));F(e.error||`上传失败: ${i.status}`,`error`)}}catch(e){F(`上传错误: ${e instanceof Error?e.message:`网络错误`}`,`error`)}finally{pt(e=>e.map(e=>e.id===n.id?{...e,failed:!0}:e)),zt(r,e=>({...e,uploading:Math.max(0,e.uploading-1)}))}}},ai=e=>{let t=e.clipboardData?.items;if(!t)return;let n=[];for(let e=0;e<t.length;e++){let r=t[e];if(r.type.startsWith(`image/`)){let e=r.getAsFile();e&&n.push(e)}}if(n.length>0){if(!An){F(`当前模型不支持图片输入`,`error`);return}e.preventDefault(),ii(n)}},oi=e=>{e.dataTransfer?.types?.includes(`Files`)&&(e.preventDefault(),_t(!0))},si=e=>{e.currentTarget.contains(e.relatedTarget)||_t(!1)},ci=e=>{_t(!1);let t=Array.from(e.dataTransfer?.files??[]).filter(e=>e.type.startsWith(`image/`));if(t.length>0){if(e.preventDefault(),!An){F(`当前模型不支持图片输入`,`error`);return}ii(t)}},ui=t=>{if(t.preventDefault(),!M||Cn||!Pt.trim()&&Ft.length===0)return;if(Ft.length>0&&!An){F(`当前模型不支持图片输入,请移除图片或切换至支持图文的模型`,`error`);return}if(Lt>0){F(`图片正在上传中,请稍候…`,`info`);return}let n=M.status===`running`,r=`pending:${nn()}`,i=M.id,a=Pt,o=Ft,s=new Set([...e.conversations.find(e=>e.id===i)?.messages.map(e=>e.id)??[],...e.queuedMessages?.map(e=>e.id)??[]]);I({type:`message.send`,conversationId:M.id,content:Pt,...Ft.length>0?{images:Ft}:{},...It.length>0?{attachments:It}:{}},`send:${i}`,e=>e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())||n?e:{...e,conversations:e.conversations.map(e=>e.id===i?{...e,messages:[...e.messages,{id:r,role:`user`,content:a,images:o,createdAt:new Date().toISOString()}]}:e)},void 0,()=>{dt(e=>[...e,{id:r,conversationId:i,content:a,images:o}]),zt(i,e=>e.text||e.images.length?e:{...e,text:a,images:o})},e=>!!(e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())))?zt(M.id,e=>({...e,text:``,images:[]})):F(`连接断开,消息未发送,草稿已保留,请重试`,`error`)},di=(0,p.useMemo)(()=>(o.queuedMessages??[]).filter(e=>e.conversationId===M?.id),[o.queuedMessages,M?.id]),fi=e=>{I({type:`message.cancel`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}))||F(`连接断开,取消没有生效,请重试`,`error`)},mi=e=>{I({type:`message.steer`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.map(t=>t.id===e?{...t,priority:!0}:t)}))||F(`连接断开,优先发送没有生效,请重试`,`error`)},hi=e=>{I({type:`message.sendQueued`,conversationId:M?.id??``,queueId:e},`send-queued:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}),void 0,void 0,t=>!t.queuedMessages?.some(t=>t.id===e))||F(`连接断开,发送没有生效,请重试`,`error`)},[_i,vi]=(0,p.useState)(),Di=(e,t=!1)=>{if(!M||_i)return;let n=M.messages.find(t=>t.id===e);n&&(n&&ct(e=>{let t=e[M.id]??{text:``,images:[],uploading:0};return t.text.trim()||t.images.length?e:{...e,[M.id]:{...t,text:n.content,images:n.images??[]}}}),$e({conversationId:M.id,messageId:e,kind:`withdraw`})),bn({type:`message.revert`,conversationId:M.id,messageId:e,withdraw:!0})?vi(e):($e(void 0),F(`连接断开,回退未执行,请重试`,`error`))},[Oi,ki]=(0,p.useState)(),Ai=(0,p.useRef)(void 0),ji=async(e,t)=>{Ai.current&&clearTimeout(Ai.current);let n=await gr(t);ki({id:e,kind:n?`copied`:`error`}),n||F(`复制失败:浏览器拒绝了剪贴板写入,可以手动选中这条消息的文字复制`,`error`),Ai.current=setTimeout(()=>ki(void 0),2e3)};(0,p.useEffect)(()=>()=>{Ai.current&&clearTimeout(Ai.current)},[]);let[Mi,Ni]=(0,p.useState)(),Pi=e=>{Mi||e.startsWith(`pending:`)||(bn({type:`conversation.fork`,conversationId:e})?Ni(e):F(`连接断开,分叉没有执行,请重试`,`error`))};return(0,m.jsxs)(`main`,{className:`shell${Ne?` shell-rail-collapsed`:``} mobile-panel-${Fe}`,"data-rail-collapsed":Ne,"data-selected-workspace":_??``,style:{"--sidebar-width":`${j.sidebar}px`,"--rail-col":Ne?void 0:`${j.rail}px`},children:[(0,m.jsxs)(`aside`,{children:[(0,m.jsxs)(`div`,{className:`brand`,children:[(0,m.jsx)(`span`,{children:`π`}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`strong`,{children:`SUPER PI`}),(0,m.jsx)(`small`,{children:`agent workspace`})]})]}),(0,m.jsx)(`div`,{className:`sidebar-actions`,children:(0,m.jsx)(`button`,{className:`workspace-new`,onClick:()=>b(!0),children:`+ 工作区`})}),(0,m.jsx)(`div`,{className:`label`,children:`工作区`}),(0,m.jsx)(`nav`,{children:o.workspaces.map(e=>{let t=o.conversations.filter(t=>t.workspaceId===e.id),n=t.filter(e=>!e.archivedAt).sort(dr),r=t.filter(e=>e.archivedAt).sort(fr),i=h[e.id]??!1,a=ue[e.id]??!0;return(0,m.jsxs)(`section`,{className:`workspace-group`,children:[(0,m.jsx)(`div`,{className:`workspace-header`,children:Sr(`workspace`,e.id)?(0,m.jsx)(Hr,{initial:e.name,label:`重命名工作区 ${e.name}`,onCommit:t=>wr(`workspace`,e.id,t),onClose:xr}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`workspace-toggle`,"aria-expanded":!i,title:e.path,onContextMenu:t=>{t.preventDefault();let n=t.currentTarget.getBoundingClientRect();tt({workspace:e,x:t.clientX||n.left+24,y:t.clientY||n.top+n.height})},onClick:()=>{v(e.id),c(Ot.find(t=>t.workspaceId===e.id)?.id),g(t=>({...t,[e.id]:!i}))},children:[(0,m.jsx)(`span`,{className:`chevron chevron-folder`,"aria-hidden":`true`,children:i?`📁`:`📂`}),(0,m.jsxs)(`span`,{children:[(0,m.jsx)(`strong`,{onDoubleClick:()=>br(`workspace`,e.id),children:e.name}),(0,m.jsx)(`small`,{children:e.path})]})]}),(0,m.jsx)(`button`,{className:`workspace-add`,title:`在此工作区新建会话`,onClick:()=>ei(e.id),children:`+`}),(0,m.jsx)(`button`,{className:`workspace-settings`,title:`工作区设置(默认智能体 / 模型 / 思考强度)`,"aria-label":`工作区设置 ${e.name}`,onClick:()=>Nr(e.id),children:`⚙`}),(0,m.jsx)(`button`,{className:`workspace-delete`,title:`删除工作区`,onClick:()=>jr(e),children:`×`})]})}),!i&&(n.length?n.map(t=>(0,m.jsx)(`div`,{className:`conversation-row`,children:Sr(`conversation`,t.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:t.title,label:`重命名会话 ${t.title}`,onCommit:e=>wr(`conversation`,t.id,e),onClose:xr}),(0,m.jsx)(`small`,{className:`conversation-time`,children:ur(t.updatedAt,Tt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`conversation ${M?.id===t.id?`selected`:``}`,"data-conversation":t.id,title:t.title,"aria-label":`打开会话 ${t.title}`,onContextMenu:e=>{e.preventDefault();let n=e.currentTarget.getBoundingClientRect();it({conversation:t,x:e.clientX||n.left+24,y:e.clientY||n.top+n.height})},onClick:()=>{Zt(t.id),v(e.id)},children:[(0,m.jsx)(`i`,{className:`conversation-mark`,children:(0,m.jsx)(Yr,{state:Jr(t,ge.some(e=>e.conversationId===t.id),C.current[t.id])})}),(0,m.jsx)(`span`,{"data-conversation-title":t.id,onDoubleClick:()=>br(`conversation`,t.id),children:t.title}),(0,m.jsx)(`small`,{children:ur(t.updatedAt,Tt)})]}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":t.id,"data-fork-state":Mi===t.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从会话 ${t.title} 分叉出一个新会话`,onClick:()=>Pi(t.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-archive`,title:`归档会话`,"aria-label":`归档会话 ${t.title}`,onClick:()=>ti(t.id),children:`×`})]})},t.id)):(0,m.jsx)(`div`,{className:`conversation-empty`,children:`暂无会话`})),!i&&r.length>0&&(0,m.jsxs)(`div`,{className:`archived-group`,"data-archived-count":r.length,children:[(0,m.jsxs)(`button`,{className:`archived-toggle`,title:a?`展开已归档会话`:`收起已归档会话`,"aria-expanded":!a,onClick:()=>de(t=>({...t,[e.id]:!a})),children:[(0,m.jsx)(`span`,{className:`chevron`,"aria-hidden":`true`,children:a?`▸`:`▾`}),(0,m.jsxs)(`span`,{className:`archived-label`,children:[`Archived (`,r.length,`)`]})]}),!a&&r.map(e=>(0,m.jsx)(`div`,{className:`conversation-archived`,"data-archived-at":e.archivedAt,children:Sr(`conversation`,e.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:e.title,label:`重命名已归档会话 ${e.title}`,onCommit:t=>wr(`conversation`,e.id,t),onClose:xr}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`i`,{className:`conversation-mark`,"aria-hidden":`true`}),(0,m.jsx)(`span`,{"data-archived-title":e.id,onDoubleClick:()=>br(`conversation`,e.id),children:e.title}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":e.id,"data-fork-state":Mi===e.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从已归档会话 ${e.title} 分叉出一个新会话`,onClick:()=>Pi(e.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-restore`,title:`恢复为正常会话`,"aria-label":`恢复会话 ${e.title}`,onClick:()=>ri(e.id),children:`↑`})]})},e.id))]})]},e.id)})}),(0,m.jsxs)(`footer`,{children:[(0,m.jsxs)(`div`,{className:`footer-status`,children:[(0,m.jsx)(`i`,{className:l?`online`:`offline`}),l?`已连接`:`正在连接`,(0,m.jsx)(`span`,{children:`本地持久化`})]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-theme-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":qr,onClick:()=>Xr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`⚙`}),`设置`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-help-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":Zr,onClick:()=>Qr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`📖`}),`使用指南`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`检查 Super Pi 更新`,onClick:async()=>{Me(`updating`);try{let e=await fetch(`/api/update-check`);if(!e.ok)throw Error(`check failed`);let t=await e.json();if(t.needsRestart)F(`当前代码已更新,正在重启服务…`,`info`);else if(t.updateAvailable){if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`发现新版本 v${t.latestVersion},正在更新并重启…`,`info`)}else F(`当前已是最新版本`,`info`)}catch{F(`检查更新失败,请检查网络或服务日志`,`error`)}finally{Me(`idle`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`↻`}),je===`updating`?`正在检查更新…`:`检查更新`]}),ke?.needsRestart&&(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`代码已更新:当前运行 v${ke.runningVersion},磁盘版本 v${ke.diskVersion}。点击复制重启命令 /super_pi restart`,onClick:async()=>{Me(`updating`);try{if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`正在更新 Super Pi,服务即将重启…`,`info`)}catch{Me(`idle`),F(`更新启动失败,请检查服务日志`,`error`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`⚠️`}),je===`updating`?`正在更新并重启…`:`更新并重启服务`]})]})]}),(0,m.jsx)(Ei,{side:`left`,width:j.sidebar,min:j.sidebarMin,max:j.sidebarMax,label:`调整左侧栏宽度`,onWidth:Ve}),(0,m.jsxs)(`section`,{className:`chat`,"data-main-view":rn,"data-main-view-kind":rn,children:[un.length>0&&(0,m.jsx)(`div`,{className:`chat-tab-strip`,"data-chat-tab-strip":`1`,"data-session-count":Xt.length,"data-file-count":P.open.length,children:(0,m.jsx)(At,{tabs:un,prefix:`workspace-tab`,ariaLabel:`工作区标签页`,activeId:dn,onSelect:fn,onClose:pn,minWidth:120,maxWidth:320})}),(0,m.jsxs)(`header`,{children:[(0,m.jsxs)(`div`,{className:`chat-head`,children:[(0,m.jsx)(`h1`,{children:M?.title??`准备开始`}),be.size>0?[...be].map(([e,t])=>(0,m.jsx)(`p`,{"data-role":`run-status`,"data-status-key":e,children:`扩展状态:${e?`${e} · `:``}${t}`},e)):(0,m.jsx)(`p`,{"data-role":`run-status`,children:M?.status===`running`?`Agent 正在工作`:`Pi runtime · Web 控制台`})]}),(0,m.jsxs)(`span`,{className:`chat-view chat-view-${rn}`,"data-main-view-indicator":rn,"data-main-view-kind":rn,title:rn===`file`?`中间主画面正在显示文件 ${P.active?.path??``}`:`中间主画面正在显示这个会话的消息流`,children:[(0,m.jsx)(`span`,{className:`chat-view-label`,children:`主区显示`}),(0,m.jsx)(`span`,{className:`chat-view-kind`,children:rn===`file`?`文件`:`会话`}),rn===`file`&&P.active&&(0,m.jsx)(`span`,{className:`chat-view-name`,"data-main-view-name":P.active.name,children:P.active.name})]}),(0,m.jsx)(`div`,{className:`badge`,children:M?.status??`idle`})]}),d&&(0,m.jsx)(`div`,{className:`error`,children:d}),M?.error&&(0,m.jsx)(`div`,{className:`error`,"data-role":`conversation-error`,children:M.error}),k&&(0,m.jsxs)(`div`,{className:`extui-title-note`,children:[`扩展建议标题:`,k]}),ve.length>0&&(0,m.jsx)(`div`,{className:`toasts`,role:`status`,"aria-live":`polite`,"data-toast-count":ve.length,children:ve.map(e=>(0,m.jsx)(`div`,{className:`toast toast-${e.kind}`,"data-toast-kind":e.kind,children:e.text},e.key))}),rn===`file`?(0,m.jsx)(`div`,{className:`chat-file-view`,"data-main-file-view":mn?.path,"data-file-view-kind":`editor`,children:mn?(0,m.jsx)(wt,{file:mn,state:P.saves[mn.path]?.state??`idle`,message:P.saves[mn.path]?.message??``,external:P.external[mn.path]===!0,onChange:e=>P.updateBuffer(mn.path,e),onSave:()=>void P.save(mn.path,!1),onReload:()=>void P.reload(mn.path),onOverwrite:()=>void P.save(mn.path,!0)}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-main-file-empty":`1`,children:`没有选中的文件。`})}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`messages`,ref:ee,"data-message-scroll-container":M?.id??``,onScroll:e=>{let t=e.currentTarget;t.dataset.programmaticScroll===`true`?delete t.dataset.programmaticScroll:Mt(t)},children:[!M&&(0,m.jsxs)(`div`,{className:`empty`,children:[(0,m.jsx)(`div`,{className:`orb`,children:`π`}),(0,m.jsx)(`h2`,{children:`开始一个新会话`}),o.workspaces.length===0?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:`先创建一个工作区,再开始会话。`}),(0,m.jsx)(`button`,{onClick:()=>b(!0),children:`+ 新建工作区`})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:o.workspaces.length===1&&kt?`在「${kt.name}」中创建会话,即可向 Super Pi 发送任务。`:`选择工作区即可开始会话,向 Super Pi 发送任务。`}),o.workspaces.length>1&&(0,m.jsxs)(`div`,{className:`empty-workspace-select`,children:[(0,m.jsx)(`label`,{htmlFor:`empty-workspace-choice`,children:`工作区:`}),(0,m.jsxs)(`select`,{id:`empty-workspace-choice`,"aria-label":`选择工作区`,value:kt?.id,onChange:e=>{e.target.value===`__new__`?b(!0):v(e.target.value)},children:[o.workspaces.map(e=>(0,m.jsx)(`option`,{value:e.id,children:e.name},e.id)),(0,m.jsx)(`option`,{value:`__new__`,children:`+ 新建工作区…`})]})]}),kt&&(0,m.jsx)(`button`,{onClick:()=>ei(kt.id),children:`新建会话`}),(0,m.jsx)(`span`,{className:`empty-secondary-action`,role:`button`,tabIndex:0,onClick:()=>b(!0),onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),b(!0))},children:`+ 新建工作区`})]})]}),Yn.map(e=>{if(e.startsWith(`m:`)){let t=qn.get(e.slice(2));if(!t)return null;let n=!M?.messages.some(n=>n.id!==t.id&&n.role===`user`&&Yn.indexOf(`m:${n.id}`)>Yn.indexOf(e)),r=t.role===`user`&&M?.status!==`running`&&_i!==t.id,i=r&&n&&!t.id.startsWith(`pending:`),a=r&&!n&&!!t.piEntryId,o=t.content?(0,m.jsx)(Ke,{source:t.content}):(0,m.jsx)(`span`,{className:`typing`,children:`正在思考`}),s=t.toolCalls??[];if(t.role===`user`){let n=Oi?.id===t.id?Oi.kind:void 0,r=t.content||(t.images?.length?`[图片]`:``);return(0,m.jsxs)(`article`,{className:`message user`,"data-transcript-key":e,children:[(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsxs)(`div`,{className:`bubble`,children:[(0,m.jsxs)(`div`,{className:`meta`,children:[he,(0,m.jsx)(`time`,{children:rr(t.createdAt)})]}),t.images&&t.images.length>0&&(0,m.jsx)(`div`,{className:`message-images`,"data-message-images":`1`,children:t.images.map(e=>(0,m.jsx)(`button`,{type:`button`,className:`message-image-btn`,"data-message-image-id":e.id,onClick:()=>ht({url:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,name:e.name,bytes:e.bytes}),title:e.name?`查看大图:${e.name}`:`查看大图`,children:(0,m.jsx)(`img`,{src:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,alt:e.name||`图片`,className:`message-image-thumb`,loading:`lazy`})},e.id))}),(0,m.jsx)(`div`,{className:`content`,children:o})]}),t.attachments&&t.attachments.length>0&&(0,m.jsx)(`div`,{className:`message-attachments`,"data-message-attachments":`1`,children:t.attachments.map(e=>{let n=`att:${t.id}:${e.kind}:${e.id}`,r=Je[n]??!1;return(0,m.jsxs)(`div`,{className:`message-attachment`,"data-message-attachment":e.kind,children:[(0,m.jsxs)(`button`,{type:`button`,className:`message-attachment-head`,"data-message-attachment-toggle":e.id,"aria-expanded":r,onClick:()=>Ye(e=>({...e,[n]:!r})),children:[(0,m.jsx)(`span`,{className:`message-attachment-glyph`,"aria-hidden":`true`,children:`▤`}),(0,m.jsx)(`span`,{className:`message-attachment-label`,children:`技能`}),(0,m.jsx)(`span`,{className:`message-attachment-name`,title:e.name,children:e.name}),(0,m.jsx)(`span`,{className:`message-attachment-chevron`,"aria-hidden":`true`,children:r?`▾`:`▸`})]}),r&&(0,m.jsx)(`pre`,{className:`message-attachment-body`,"data-message-attachment-body":e.id,children:e.content})]},n)})}),(0,m.jsxs)(`div`,{className:`message-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`message-copy`,"data-copy-message":t.id,"data-copy-state":n,"aria-label":`复制这条消息:${r}`,onClick:()=>ji(t.id,r),children:n===`copied`?`已复制`:n===`error`?`复制失败`:`复制`}),i&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-withdraw-message":t.id,title:`撤回并编辑:删除这条消息并将文字恢复到输入框继续编辑`,onClick:()=>Di(t.id,!0),children:`撤回`}),a&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-revert-message":t.id,title:`回退到这里:删除后续全部内容(Pi 的记录和这里的历史一起清掉,不会新建会话,也不动任何代码文件)`,onClick:()=>Di(t.id),children:`回退`})]})]}),(0,m.jsx)(`div`,{className:`avatar`,title:he,children:he})]},e)}if(t.role===`tool`){let n=(()=>{let e=/<summary>([\s\S]*?)<\/summary>/.exec(t.content);return e?e[1].trim():`后台回传`})(),r=(()=>{let e=/<task id="[^"]+" state="([^"]+)"/.exec(t.content);return e?e[1]:`completed`})(),i=(()=>{let e=/<task_(?:result|error)>\n?([\s\S]*?)\n?<\/(?:task_result|task_error)>/.exec(t.content);return e?e[1]:t.content})(),a=`tool:${t.id}`,o=Je[a]??!1;return(0,m.jsx)(`article`,{className:`message tool`,"data-transcript-key":e,"data-tool-state":r,children:(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsx)(`div`,{className:`bg-result-head`,"data-bg-result-head":t.id,children:(0,m.jsxs)(`button`,{type:`button`,className:`bg-result-toggle`,"data-bg-result-toggle":t.id,"aria-expanded":o,title:o?`折叠后台回传`:`展开后台回传`,onClick:()=>Ye(e=>({...e,[a]:!o})),children:[(0,m.jsx)(`span`,{className:`bg-result-glyph`,"aria-hidden":`true`,children:r===`error`?`✕`:`↩`}),(0,m.jsx)(`span`,{className:`bg-result-label`,children:`后台回传`}),(0,m.jsx)(`span`,{className:`bg-result-summary`,title:n,children:n}),!o&&(0,m.jsx)(`span`,{className:`bg-result-preview`,"data-bg-result-preview":t.id,title:`展开查看完整输出`,children:i.split(`
61
57
  `).slice(-1)[0]}),(0,m.jsx)(`span`,{className:`bg-result-chevron`,"aria-hidden":`true`,children:o?`▾`:`▸`})]})}),o&&(0,m.jsx)(`div`,{className:`bg-result-body`,"data-bg-result-body":t.id,children:(0,m.jsx)(`pre`,{className:`bg-result-pre`,"data-bg-result-pre":t.id,children:i})})]})},e)}let c=t.thinking?.trim()?t.thinking:``,l=t.thinkingBlocks?.length?t.thinkingBlocks:c?[{at:0,text:c}]:[],u=(e,n)=>{let r=typeof e.at==`number`&&Number.isFinite(e.at)?e.at:n;return Math.max(0,Math.min(r,t.content.length))},d=new Set(s.map((e,t)=>c&&typeof e.thinkingAt==`number`&&e.thinkingAt<c.length?t:-1).filter(e=>e>=0)),f=[],p=0,h=s.map((e,t)=>({call:e,index:t,at:u(e,t)})).filter(({index:e})=>!d.has(e)).sort((e,t)=>e.at-t.at||e.index-t.index);for(let{call:e,index:n,at:r}of h){let i=Math.max(p,r);i>p&&f.push({text:t.content.slice(p,i)}),p=i,f.push({call:e,index:n})}f.push({text:t.content.slice(p)});let g=[];if(c){let e=0,t=s.map((e,t)=>({call:e,index:t,at:Math.max(0,Math.min(e.thinkingAt??0,c.length))})).filter(({index:e})=>d.has(e)).sort((e,t)=>e.at-t.at||e.index-t.index);for(let{call:n,index:r,at:i}of t){let t=Math.max(e,i);t>e&&g.push({text:c.slice(e,t)}),e=t,g.push({call:n,index:r})}g.push({text:c.slice(e)})}c.split(`
62
58
  `).map(e=>e.trim()).find(e=>e.length>0);let _=e=>e.call.question?(0,m.jsx)(`div`,{className:`call-blocks`,"data-call-block-index":e.index,children:(0,m.jsx)(zr,{question:e.call.question,index:e.index,expanded:`${t.id}:${e.index}`in Xe?!!Xe[`${t.id}:${e.index}`]:!0,onToggle:()=>Qn(t.id,e.index),onAnswer:(n,r)=>wn(M?.id??``,t.id,e.index,n,r)})}):(0,m.jsx)(`div`,{className:`call-blocks`,"data-call-block-index":e.index,children:(0,m.jsx)(Pr,{call:e.call,index:e.index,expanded:`${t.id}:${e.index}`in Je&&!!Je[`${t.id}:${e.index}`],onToggle:()=>Zn(t.id,e.index)})});return(0,m.jsxs)(`article`,{className:`message assistant`,"data-transcript-key":e,children:[(0,m.jsx)(`div`,{className:`avatar`,children:`π`}),(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsxs)(`div`,{className:`meta`,children:[t.agentIdentity?.name??`Super Pi`,(0,m.jsx)(`time`,{children:rr(t.createdAt)})]}),l.map((e,n)=>(0,m.jsxs)(Ir,{messageId:`${t.id}:${n}`,nestedCount:s.filter(t=>typeof t.thinkingAt==`number`&&t.thinkingAt>=e.at&&(l[n+1]?.at===void 0||t.thinkingAt<l[n+1].at)).length,preview:e.text.split(`
63
59
  `).map(e=>e.trim()).find(Boolean)??``,expanded:$n[`${t.id}:${n}`]!==!1,onToggle:()=>ir(`${t.id}:${n}`),children:[(0,m.jsx)(`div`,{className:`content thinking-text`,children:(0,m.jsx)(Ke,{source:e.text})}),s.filter(t=>typeof t.thinkingAt==`number`&&t.thinkingAt>=e.at&&(l[n+1]?.at===void 0||t.thinkingAt<l[n+1].at)).map((e,t)=>_({call:e,index:s.indexOf(e)}))]},`${t.id}:thinking:${n}`)),f.map((e,t)=>`text`in e?e.text?(0,m.jsx)(`div`,{className:`content`,children:(0,m.jsx)(Ke,{source:e.text})},`t${t}`):null:_(e))]})]},e)}if(e.startsWith(`w:`)){let t=Gn.get(e.slice(2));return t?(0,m.jsxs)(`div`,{"data-transcript-key":e,children:[n.some(e=>e.key===`stop:${t.id}`)&&(0,m.jsx)(`span`,{children:`停止中…`}),(0,m.jsx)(Rt,{workflow:t,names:Kn,stopping:n.some(e=>e.key===`stop:${t.id}`),onStop:()=>or(t.id)},e)]},e):null}let t=Jn.get(e.slice(2));return t?(0,m.jsxs)(`div`,{"data-transcript-key":e,children:[n.some(e=>e.key===`stop:${t.id}`)&&(0,m.jsx)(`span`,{children:`停止中…`}),(0,m.jsx)(Lr,{run:t,stopping:n.some(e=>e.key===`stop:${t.id}`),name:Kn.get(t.agentId)??``,expanded:!!Ge[t.id],onToggle:()=>Xn(t.id),onStop:()=>ar(t.id)},e)]},e):null})]}),(0,m.jsxs)(`div`,{className:`stream-tail`,"data-stream-tail":`1`,"data-follow-tail":re.followTail?`true`:`false`,children:[(0,m.jsx)(`span`,{className:`stream-tail-status`,"data-run-status":M?.status??`idle`,"data-stream-state":l?M?.status===`running`?`streaming`:di.length?`waiting`:M?.status===`failed`?`failed`:`ready`:`waiting`,children:l?M?.status===`running`?`Streaming · 正在生成`:di.length?`Waiting · 消息排队中`:M?.status===`failed`?`运行失败`:M?.status===`stopped`?`已停止`:`就绪`:`连接中 / 等待连接`}),!re.followTail&&(0,m.jsx)(`button`,{type:`button`,className:`stream-tail-jump`,"data-jump-to-bottom":`1`,onClick:()=>{let e=ee.current;e&&(xi(e),e.scrollTop=e.scrollHeight,Mt(e,!1,!0))},children:`↓ 跳到底部`}),(0,m.jsx)(`span`,{className:`stream-tail-mode`,"data-follow-tail":re.followTail?`true`:`false`,children:re.followTail?`贴底`:`自由浏览`})]}),(0,m.jsx)(Rr,{items:di,pi:Ue.get(M?.id??``),running:M?.status===`running`,onCancel:fi,onSteer:mi,onSendQueued:hi}),(Hn.length>0||[...Se.values()].some(e=>e.placement===`aboveEditor`))&&(0,m.jsxs)(`div`,{className:`extui`,"data-extui-count":Hn.length,children:[Hn.map(e=>(0,m.jsx)(Vr,{request:e.request,pending:n.some(t=>t.key===`answer:${e.conversationId}:${e.request.requestId}`),onAnswer:t=>Tn(e.conversationId,e.request.requestId,t)},e.request.requestId)),sr(`aboveEditor`)]}),(0,m.jsxs)(`form`,{onSubmit:ui,onPaste:ai,onDragOver:oi,onDragLeave:si,onDrop:ci,className:gt?`composer-dropzone-active`:void 0,children:[(0,m.jsx)(`textarea`,{ref:E,value:Pt,onCompositionStart:()=>{yt.current=!0},onCompositionEnd:()=>{yt.current=!1},onChange:e=>{Vt(e.target.value);let t=e.target.value,n=e.target.selectionStart??t.length,r=t.slice(0,n),i=r.lastIndexOf(`/`),a=r.slice(i+1),o=i>0?r[i-1]:``;i>=0&&(i===0||/\s/.test(o))&&/^[\w:-]*$/u.test(a)?Gt({visible:!0,query:a,rect:e.currentTarget.getBoundingClientRect()}):Gt({visible:!1,query:``,rect:null})},placeholder:M?An?`描述你要完成的任务(支持粘贴/拖放图片)…`:`描述你要完成的任务…`:`先创建会话`,disabled:!M,onKeyDown:e=>{if(!(yt.current||nr(e))){if(Wt.visible){if(e.key===`Escape`){e.preventDefault(),Gt({visible:!1,query:``,rect:null});return}if([`ArrowDown`,`ArrowUp`,`Enter`].includes(e.key)){e.preventDefault();return}}e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),e.currentTarget.form?.requestSubmit())}}}),Wt.visible&&Wt.rect&&(0,m.jsx)(at,{query:Wt.query,anchorRect:Wt.rect,workspaceId:M?.workspaceId,command:bn,onSelect:e=>{if(e.id.startsWith(`skill:`)){let t=e.id.slice(6),n=document.querySelector(`[data-selected-workspace]`)?.dataset.selectedWorkspace;n&&fetch(`/api/memory/tool`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({workspaceId:n,conversationId:M?.id,kind:`skill.read`,skillId:t})}).then(async n=>{if(!n.ok){F(`读取技能失败`,`error`);return}let r=await n.json();typeof r.content==`string`&&Ht({kind:`skill`,id:r.id??t,name:r.name??e.label,content:r.content})}).catch(()=>F(`读取技能失败`,`error`))}let t=E.current?.selectionStart??Pt.length,n=Pt.slice(0,t),r=n.lastIndexOf(`/`),i=rt(e,Wt.query),a=`${n.slice(0,r)}/${i} ${Pt.slice(t)}`;Vt(a),Gt({visible:!1,query:``,rect:null})},onClose:()=>Gt({visible:!1,query:``,rect:null})}),ut.filter(e=>e.conversationId===M?.id).map(e=>(0,m.jsxs)(`div`,{"data-failed-send":e.id,children:[(0,m.jsxs)(`span`,{children:[`发送失败:`,e.content]}),(0,m.jsx)(`button`,{type:`button`,disabled:!!(Pt||Ft.length),onClick:()=>{zt(e.conversationId,t=>({...t,text:e.content,images:e.images})),dt(t=>t.filter(t=>t.id!==e.id))},children:`恢复草稿`}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>dt(t=>t.filter(t=>t.id!==e.id)),children:`移除`})]},e.id)),ft.filter(e=>e.conversationId===M?.id).map(e=>(0,m.jsxs)(`div`,{"data-upload-preview":e.id,children:[(0,m.jsx)(`img`,{src:e.url,alt:e.name,width:48,height:48}),(0,m.jsx)(`span`,{children:e.failed?`上传失败`:`上传中…`}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>{lt.current.add(e.id),URL.revokeObjectURL(e.url),pt(t=>t.filter(t=>t.id!==e.id))},children:`移除`})]},e.id)),(Ft.length>0||Lt>0)&&(0,m.jsxs)(`div`,{className:`composer-attachments`,"data-composer-attachments":`1`,children:[Ft.map((e,t)=>(0,m.jsxs)(`div`,{className:`composer-attachment-item`,"data-composer-attachment-id":e.id,children:[(0,m.jsx)(`img`,{src:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,alt:e.name||`已附图片`,className:`composer-attachment-thumb`,onClick:()=>ht({url:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,name:e.name,bytes:e.bytes}),title:`点击查看大图`}),(0,m.jsx)(`span`,{className:`composer-attachment-name`,title:e.name,children:e.name||`图片`}),(0,m.jsx)(`button`,{type:`button`,className:`composer-attachment-remove`,"data-composer-attachment-remove":e.id,onClick:()=>Kt(e=>e.filter((e,n)=>n!==t)),"aria-label":`移除 ${e.name||`图片`}`,title:`移除图片`,children:`✕`})]},e.id)),Lt>0&&(0,m.jsx)(`div`,{className:`composer-attachment-item composer-attachment-uploading`,children:(0,m.jsxs)(`span`,{children:[`图片上传中 (`,Lt,`)…`]})}),It.map(e=>(0,m.jsxs)(`div`,{className:`composer-attachment-item composer-attachment-skill`,"data-composer-skill-id":e.id,children:[(0,m.jsx)(`span`,{className:`composer-attachment-glyph`,"aria-hidden":`true`,children:`▤`}),(0,m.jsx)(`span`,{className:`composer-attachment-name`,title:e.name,children:e.name}),(0,m.jsx)(`button`,{type:`button`,className:`composer-attachment-remove`,"data-composer-skill-remove":e.id,onClick:()=>Ut(e.kind,e.id),"aria-label":`移除 ${e.name}`,title:`移除技能`,children:`✕`})]},`${e.kind}:${e.id}`))]}),(0,m.jsxs)(`div`,{className:`composer`,children:[(0,m.jsxs)(`div`,{className:`session-picker`,"data-session-picker":`1`,children:[(0,m.jsx)(Ur,{kind:`agent`,label:In?.name??`跟随 Pi 默认`,value:M?.agentId??L,options:Rn,disabled:!Ee||Ee.source===`unavailable`||Cn,note:Ar(Ee),onChoose:Vn}),(0,m.jsx)(Ur,{kind:`model`,label:En?.label??(we?`Pi 默认`:`读取中…`),value:En?`${En.provider}/${En.modelId}`:void 0,detail:Bn?`来自 ${In?.name}`:En&&we?.source===`pi`&&!M?.model?`Pi 默认模型`:En?`${En.provider}/${En.modelId}`:void 0,options:Mn,disabled:!On||Cn,note:kr(`模型`,we),onChoose:Pn}),(0,m.jsx)(Ur,{kind:`thinking`,label:Dn??(we?`Pi 默认`:`读取中…`),value:Dn,options:Nn,disabled:!On||Cn,note:kr(`思考等级`,we),onChoose:Fn})]}),(0,m.jsxs)(`div`,{className:`composer-hints`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`composer-attach-btn`,"data-composer-attach":`1`,onClick:()=>vt.current?.click(),disabled:!M||!An||Lt>0,title:M?An?`添加图片(支持剪贴板粘贴与拖拽)`:`当前模型不支持图片输入`:`请先创建或选择会话`,"aria-label":`添加图片`,children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`+`}),` 图片`]}),(0,m.jsx)(`input`,{ref:vt,type:`file`,accept:`image/png,image/jpeg,image/gif,image/webp`,multiple:!0,style:{display:`none`},onChange:e=>{e.target.files?.length&&(ii(Array.from(e.target.files)),e.target.value=``)}}),(0,m.jsx)(`span`,{children:`Enter 发送 · Shift + Enter 换行`})]}),(0,m.jsxs)(`div`,{className:`composer-actions`,children:[M?.status===`running`&&(0,m.jsx)(`button`,{type:`button`,className:`stop`,"data-stop-run":M.id,disabled:n.some(e=>e.key===`stop:${M.id}`),onClick:()=>I({type:`run.stop`,conversationId:M.id},`stop:${M.id}`,e=>e),children:n.some(e=>e.key===`stop:${M.id}`)?`停止中…`:`停止`}),(0,m.jsx)(`button`,{className:`send ${M?.status===`running`?`send-queued`:``}`,"data-send-mode":M?.status===`running`?`queued`:`direct`,disabled:!Pt.trim()&&Ft.length===0||!M||Lt>0||!!_i||Cn,title:M?_i?`正在撤回:Pi 的历史还没截断完成,撤回结束后即可发送`:!Pt.trim()&&Ft.length===0?`请输入消息或添加图片后发送`:Lt>0?`图片上传中…`:M.status===`running`?`这一轮结束后发送(发送后可在上面的排队区取消或插队)`:void 0:`请先创建或选择会话`,children:_i?`撤回中…`:M?.status===`running`?`排队发送 ↑`:`发送 ↑`})]})]})]}),sr(`belowEditor`)]})]}),(0,m.jsx)(Ei,{side:`right`,width:Ne?30:j.rail,min:j.railMin,max:j.railMax,label:`调整右侧栏宽度`,onWidth:He}),(0,m.jsxs)(`nav`,{className:`mobile-nav`,"aria-label":`移动端主导航`,children:[(0,m.jsxs)(`button`,{type:`button`,"data-mobile-nav":`workspaces`,className:Fe===`workspaces`?`active`:``,onClick:()=>Ie(`workspaces`),children:[`☰`,(0,m.jsx)(`span`,{children:`工作区`})]}),(0,m.jsxs)(`button`,{type:`button`,"data-mobile-nav":`chat`,className:Fe===`chat`?`active`:``,onClick:()=>Ie(`chat`),children:[`◉`,(0,m.jsx)(`span`,{children:`对话`})]}),(0,m.jsxs)(`button`,{type:`button`,"data-mobile-nav":`rail`,className:Fe===`rail`?`active`:``,onClick:()=>{Ie(`rail`),Pe(!1)},children:[`▣`,(0,m.jsx)(`span`,{children:`工具`})]})]}),` `,et&&(0,m.jsx)(_r,{workspace:et.workspace,x:et.x,y:et.y,notify:F,onClose:()=>tt(void 0)}),nt&&(0,m.jsx)(vr,{conversation:nt.conversation,x:nt.x,y:nt.y,notify:F,onRename:()=>void Tr(nt.conversation.id),onClose:()=>it(void 0)}),y&&(0,m.jsx)(Er,{onClose:()=>b(!1),onCreate:cr}),qr&&(0,m.jsx)(Ln,{workspaceId:_,theme:Br,onChooseTheme:e=>Kr(e),onClose:()=>Xr(!1),agentCatalog:Ee,subagentCatalog:A,modelFilterRules:o.modelFilterRules??[],pendingSettings:n.some(e=>e.key===`model-rules`||e.key===`agent-directories`),onSetModelRules:e=>I({type:`model.rules.set`,rules:e},`model-rules`,t=>({...t,modelFilterRules:e})),customAgentDirs:o.customAgentDirs??[],onSetCustomAgentDirs:e=>I({type:`agents.setCustomDirs`,dirs:e},`agent-directories`,t=>({...t,customAgentDirs:e})),onRescanAgents:()=>bn({type:`agents.rescan`}),userNickname:fe,onSetUserNickname:me}),Zr&&(0,m.jsx)(Bt,{onClose:()=>Qr(!1)}),Fr&&(0,m.jsx)(Wr,{workspace:Fr,catalog:jn(we,Fr),agents:Ee,onClose:()=>Nr(void 0),onRename:()=>{let e=Fr.id;Nr(void 0),br(`workspace`,e)},onSave:(e,t,n)=>$r(Fr.id,e,t,n)}),mt&&(0,m.jsx)(li,{image:mt,onClose:()=>ht(void 0)}),(0,m.jsx)(ni,{todos:o.todosByConversation?.[M?.id??``]??[],tasks:o.backgroundTasks??[],collapsed:Ne,onToggle:()=>Pe(e=>!e),workspaceId:_,workspaceLabel:o.workspaces.find(e=>e.id===_)?.path,connected:l,files:P,onOpenFile:e=>void sn(e),activeTab:Qt,onSelectTab:$t,terminalAvailable:en,sendCommand:bn,registerTerminalSink:le})]})}On(Dn()),(0,f.createRoot)(document.getElementById(`root`)).render((0,m.jsx)(p.StrictMode,{children:(0,m.jsx)(Di,{})}));
@@ -8,7 +8,7 @@
8
8
  <link rel="alternate icon" href="/favicon.ico" />
9
9
  <link rel="apple-touch-icon" href="/favicon.svg" />
10
10
  <title>Super Pi</title>
11
- <script type="module" crossorigin src="/assets/index-Bi2QvT9i.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-CHRkbLTs.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-CKdlMXJt.css">
13
13
  </head>
14
14
  <body>
@@ -8,6 +8,7 @@ import { describe as describeFilesError, fileMeta, listDirectory, MAX_FILE_BYTES
8
8
  import { findUploadFile, MAX_UPLOAD_BODY_BYTES, saveUpload } from "./upload.js";
9
9
  import { checkDiskVersion } from "./version.js";
10
10
  import { gitDiff, gitLog, gitStatus } from "./git.js";
11
+ import { verifyCapability } from "./capability.js";
11
12
  const MIME_TYPES = {
12
13
  ".css": "text/css; charset=utf-8",
13
14
  ".html": "text/html; charset=utf-8",
@@ -132,17 +133,22 @@ export function isAuthorized(request, accessToken) {
132
133
  */
133
134
  export function createHttpServer(options) {
134
135
  const { context, onShutdown } = options;
135
- const { store, paths, accessToken, broadcaster, runService, catalogService, execService, toolRegistry } = context;
136
+ const { store, paths, accessToken, broadcaster, runService, catalogService, execService, toolRegistry, toolRunner } = context;
136
137
  const { uploadsDir, clientDir, rootDir } = paths;
137
138
  return createServer(async (request, response) => {
138
139
  // Internal tools use a narrow per-run capability, never the global management token.
139
- if (request.url?.split("?")[0] !== "/api/memory/tool" && !isAuthorized(request, accessToken)) {
140
+ const requestPath = request.url?.split("?")[0] ?? "";
141
+ const opaqueToolApi = requestPath.startsWith("/api/tools/") && request.headers.origin === "null";
142
+ // Tool registration is checked after its JSON body is parsed: an agent proves its turn capability
143
+ // there, while an interactive browser uses the ordinary server access token.
144
+ if (requestPath !== "/api/memory/tool" && requestPath !== "/api/tools" && !opaqueToolApi && !isAuthorized(request, accessToken)) {
140
145
  response.writeHead(401, { "www-authenticate": "Bearer" }).end("Authentication required");
141
146
  return;
142
147
  }
143
148
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
144
149
  const remote = request.socket.remoteAddress ?? "";
145
150
  const json = (status, body) => { response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }); response.end(JSON.stringify(body)); };
151
+ const toolJson = (status, body) => { response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null", "vary": "Origin" }); response.end(JSON.stringify(body)); };
146
152
  if (url.pathname === "/api/tools") {
147
153
  try {
148
154
  if (request.method === "GET") {
@@ -171,12 +177,22 @@ export function createHttpServer(options) {
171
177
  }
172
178
  }
173
179
  const body = JSON.parse(raw);
174
- const workspace = store.snapshot().workspaces.find((x) => x.id === body.workspaceId || (body.workspacePath && resolve(x.path) === resolve(body.workspacePath)));
175
- if (!workspace || !body.slug || !body.name || !body.entrypoint) {
176
- json(400, { error: "workspaceId (or workspacePath), slug, name and entrypoint are required" });
180
+ if (!isAuthorized(request, accessToken)) {
181
+ if (!body.conversationId) {
182
+ json(401, { error: "authentication or agent capability required" });
183
+ return;
184
+ }
185
+ const verdict = verifyCapability(body.conversationId, body.capability ?? request.headers["x-super-pi-capability"]);
186
+ if (!verdict.ok) {
187
+ json(403, { error: verdict.message });
188
+ return;
189
+ }
190
+ }
191
+ if (!body.slug || !body.name || !Array.isArray(body.files)) {
192
+ json(400, { error: "slug, name and files are required" });
177
193
  return;
178
194
  }
179
- const tool = await toolRegistry.register({ workspaceId: workspace.id, slug: body.slug, name: body.name, description: body.description, entrypoint: body.entrypoint }, workspace.path);
195
+ const tool = await toolRegistry.register({ slug: body.slug, name: body.name, description: body.description, entrypoint: body.entrypoint, script: body.script, files: body.files });
180
196
  broadcaster.broadcast({ type: "tools.updated", tools: await toolRegistry.list() });
181
197
  json(201, tool);
182
198
  return;
@@ -195,13 +211,8 @@ export function createHttpServer(options) {
195
211
  response.writeHead(404).end("Tool not found");
196
212
  return;
197
213
  }
198
- const workspace = store.snapshot().workspaces.find((x) => x.id === listed.workspaceId);
199
- if (!workspace) {
200
- response.writeHead(404).end("Workspace not found");
201
- return;
202
- }
203
214
  try {
204
- const resolved = await toolRegistry.resolve(id, workspace.path, resource);
215
+ const resolved = await toolRegistry.resolve(id, resource);
205
216
  if (!resolved) {
206
217
  response.writeHead(404).end("Tool not found");
207
218
  return;
@@ -215,6 +226,75 @@ export function createHttpServer(options) {
215
226
  return;
216
227
  }
217
228
  }
229
+ if (url.pathname.startsWith("/api/tools/") && url.pathname.endsWith("/run")) {
230
+ const id = decodeURIComponent(url.pathname.slice("/api/tools/".length, -"/run".length));
231
+ if (!opaqueToolApi) {
232
+ toolJson(403, { error: "tool runtime API is only available to the sandboxed tool frame" });
233
+ return;
234
+ }
235
+ if (request.method === "OPTIONS") {
236
+ response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-methods": "POST", "access-control-allow-headers": "content-type", "vary": "Origin" }).end();
237
+ return;
238
+ }
239
+ if (request.method !== "POST") {
240
+ toolJson(405, { error: "method" });
241
+ return;
242
+ }
243
+ let raw = "";
244
+ for await (const chunk of request) {
245
+ raw += chunk;
246
+ if (raw.length > 64_000) {
247
+ json(413, { error: "request too large" });
248
+ return;
249
+ }
250
+ }
251
+ try {
252
+ const body = JSON.parse(raw);
253
+ const resolved = await toolRegistry.resolve(id);
254
+ if (!resolved?.tool.script) {
255
+ toolJson(404, { error: "python script is not registered" });
256
+ return;
257
+ }
258
+ const run = toolRunner.start({ toolId: id, script: resolve(resolved.root, resolved.tool.script.path), cwd: resolved.root, definition: resolved.tool.script, params: body.params ?? {} });
259
+ toolJson(202, run);
260
+ }
261
+ catch (error) {
262
+ toolJson(400, { error: error instanceof Error ? error.message : "tool run failed" });
263
+ }
264
+ return;
265
+ }
266
+ if (url.pathname.startsWith("/api/tools/runs/")) {
267
+ const parts = url.pathname.slice("/api/tools/runs/".length).split("/");
268
+ const runId = decodeURIComponent(parts[0] ?? "");
269
+ if (!opaqueToolApi) {
270
+ toolJson(403, { error: "tool runtime API is only available to the sandboxed tool frame" });
271
+ return;
272
+ }
273
+ if (request.method === "OPTIONS") {
274
+ response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-methods": "GET, DELETE", "vary": "Origin" }).end();
275
+ return;
276
+ }
277
+ if (request.method === "GET") {
278
+ const run = [...(await toolRegistry.list())].map((tool) => toolRunner.get(runId, tool.id)).find(Boolean);
279
+ if (!run) {
280
+ toolJson(404, { error: "run not found" });
281
+ return;
282
+ }
283
+ toolJson(200, run);
284
+ return;
285
+ }
286
+ if (request.method === "DELETE") {
287
+ const run = [...(await toolRegistry.list())].map((tool) => toolRunner.cancel(runId, tool.id)).find(Boolean);
288
+ if (!run) {
289
+ toolJson(404, { error: "run not found" });
290
+ return;
291
+ }
292
+ toolJson(200, run);
293
+ return;
294
+ }
295
+ toolJson(405, { error: "method" });
296
+ return;
297
+ }
218
298
  /**
219
299
  * THE TERMINAL IS NOT AN API ENDPOINT, and the routing above deliberately cannot reach one.
220
300
  *
@@ -23,6 +23,7 @@ import { createHttpServer, getLanIps, isLocalOrPrivateAddress, parseServerArgs }
23
23
  import { WsBroadcaster, WsServer } from "./ws-server.js";
24
24
  import { initRunningVersion } from "./version.js";
25
25
  import { ToolRegistry } from "./tool-registry.js";
26
+ import { ToolRunner } from "./tool-runner.js";
26
27
  // ------------------------------------------------------------- Environment & paths
27
28
  const rootDir = resolve(import.meta.dirname, "../..");
28
29
  initRunningVersion(rootDir);
@@ -51,6 +52,7 @@ const toolsEndpoint = `http://${internalHost}:${port}/api/tools`;
51
52
  // ------------------------------------------------------------- Core singletons
52
53
  const store = await openStateStore(dataDir, process.env.SUPER_PI_PERSISTENCE ?? "sqlite");
53
54
  const toolRegistry = new ToolRegistry(dataDir);
55
+ const toolRunner = new ToolRunner();
54
56
  const initialAgentDirs = resolveAgentDirectories({
55
57
  customDirs: store.snapshot().customAgentDirs,
56
58
  rootDir,
@@ -159,6 +161,7 @@ const serverContext = {
159
161
  catalogService,
160
162
  execService,
161
163
  toolRegistry,
164
+ toolRunner,
162
165
  terminalEnabled: () => terminalService.enabled,
163
166
  terminalDisabledReason: () => terminalDisabledReason(),
164
167
  };
@@ -379,6 +379,7 @@ export class PiRpcRuntime {
379
379
  const { agents, problems } = await readAgents(this.agents.dir, { mode: "subagent" });
380
380
  for (const problem of problems)
381
381
  console.warn(`PiRpcRuntime: ${problem.file} is not a usable sub-agent - ${problem.message}`);
382
+ const capability = mintCapability(conversationId);
382
383
  return {
383
384
  [TASK_ENDPOINT_ENV]: this.options.endpoint ?? "",
384
385
  [TASK_EXEC_ENDPOINT_ENV]: this.options.execEndpoint ?? "",
@@ -389,11 +390,12 @@ export class PiRpcRuntime {
389
390
  [WORKFLOW_ENDPOINT_ENV]: this.options.workflowEndpoint ?? "",
390
391
  SUPER_PI_QUESTION_ENDPOINT: this.options.questionEndpoint ?? "",
391
392
  SUPER_PI_TOOLS_ENDPOINT: this.options.toolsEndpoint ?? "",
393
+ SUPER_PI_TOOLS_CAPABILITY: capability,
392
394
  // MINTED HERE, AT SPAWN, rather than read from anywhere: this is the only moment the value is
393
395
  // decided, so it cannot change underneath a running child. `SubagentSupervisor` never calls this
394
396
  // function, so no sub-agent process is ever handed a capability - which is what makes a sub-agent
395
397
  // unable to dispatch, however its allowlist is broken.
396
- [TASK_CAPABILITY_ENV]: mintCapability(conversationId),
398
+ [TASK_CAPABILITY_ENV]: capability,
397
399
  };
398
400
  }
399
401
  async *answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onSessionFile, onToolCall, onToolUpdate, onToolPreparing, onToolEnd, onThinking, images, memoryReview, memorySourceConversationId }) {
@@ -9,6 +9,7 @@ import type { ExtensionUiService } from "./application/extension-ui-service.js";
9
9
  import type { CatalogService } from "./application/catalog-service.js";
10
10
  import type { ExecService } from "./exec-service.js";
11
11
  import type { ToolRegistry } from "./tool-registry.js";
12
+ import type { ToolRunner } from "./tool-runner.js";
12
13
  /**
13
14
  * Common filesystem paths used across the server.
14
15
  */
@@ -49,6 +50,7 @@ export interface ServerContext {
49
50
  catalogService: CatalogService;
50
51
  execService: ExecService;
51
52
  toolRegistry: ToolRegistry;
53
+ toolRunner: ToolRunner;
52
54
  /**
53
55
  * Whether the right rail's terminal tab should exist, and why not when it should not.
54
56
  *
@@ -1,18 +1,24 @@
1
+ import type { ToolScript } from "./tool-runner.js";
2
+ export type ToolAsset = {
3
+ path: string;
4
+ content: string;
5
+ };
1
6
  export type MountedTool = {
2
7
  id: string;
3
8
  slug: string;
4
9
  name: string;
5
10
  description?: string;
6
- workspaceId: string;
7
11
  entrypoint: string;
12
+ script?: ToolScript;
8
13
  source: "user" | "builtin";
9
14
  enabled: boolean;
10
15
  createdAt: string;
11
16
  updatedAt: string;
12
17
  };
18
+ /** Global registry: a tool belongs to Super Pi, never to the workspace that created it. */
13
19
  export declare class ToolRegistry {
14
- private readonly dataDir;
15
20
  private readonly file;
21
+ readonly toolsDir: string;
16
22
  private items;
17
23
  private loaded;
18
24
  constructor(dataDir: string);
@@ -20,16 +26,17 @@ export declare class ToolRegistry {
20
26
  private save;
21
27
  list(): Promise<MountedTool[]>;
22
28
  register(input: {
23
- workspaceId: string;
24
29
  slug: string;
25
30
  name: string;
26
31
  description?: string;
27
- entrypoint: string;
28
- }, workspaceRoot: string): Promise<MountedTool>;
32
+ entrypoint?: string;
33
+ script?: ToolScript;
34
+ files: ToolAsset[];
35
+ }): Promise<MountedTool>;
29
36
  remove(id: string): Promise<boolean>;
30
- setEnabled(id: string, enabled: boolean): Promise<MountedTool | undefined>;
31
- resolve(id: string, workspaceRoot: string, resource?: string): Promise<{
37
+ resolve(id: string, resource?: string): Promise<{
32
38
  tool: MountedTool;
33
39
  file: string;
40
+ root: string;
34
41
  } | undefined>;
35
42
  }
@@ -1,74 +1,90 @@
1
- import { mkdir, readFile, rename, writeFile, access, stat } from "node:fs/promises";
1
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, join, relative, resolve, sep } from "node:path";
3
- const MAX_TOOLS = 128;
4
- const MAX_SLUG = 64;
5
- const SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
3
+ const MAX_TOOLS = 128, MAX_ASSETS = 32, MAX_ASSET_BYTES = 512_000, SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
4
+ /** Global registry: a tool belongs to Super Pi, never to the workspace that created it. */
6
5
  export class ToolRegistry {
7
- dataDir;
8
6
  file;
7
+ toolsDir;
9
8
  items = [];
10
9
  loaded = false;
11
- constructor(dataDir) {
12
- this.dataDir = dataDir;
13
- this.file = join(dataDir, "tools.json");
10
+ constructor(dataDir) { this.file = join(dataDir, "tools.json"); this.toolsDir = join(dataDir, "tools"); }
11
+ async load() { if (this.loaded)
12
+ return; this.loaded = true; try {
13
+ const parsed = JSON.parse(await readFile(this.file, "utf8"));
14
+ if (Array.isArray(parsed))
15
+ this.items = parsed.filter(isTool);
14
16
  }
15
- async load() {
16
- if (this.loaded)
17
- return;
18
- this.loaded = true;
19
- try {
20
- const parsed = JSON.parse(await readFile(this.file, "utf8"));
21
- if (Array.isArray(parsed))
22
- this.items = parsed.filter(isTool);
23
- }
24
- catch {
25
- this.items = [];
26
- }
27
- }
28
- async save() {
29
- await mkdir(this.dataDir, { recursive: true });
30
- const tmp = `${this.file}.${process.pid}.tmp`;
31
- await writeFile(tmp, JSON.stringify(this.items, null, 2), "utf8");
32
- await rename(tmp, this.file);
33
- }
34
- async list() { await this.load(); return this.items.map((x) => ({ ...x })); }
35
- async register(input, workspaceRoot) {
17
+ catch {
18
+ this.items = [];
19
+ } }
20
+ async save() { await mkdir(dirname(this.file), { recursive: true }); const tmp = `${this.file}.${process.pid}.tmp`; await writeFile(tmp, JSON.stringify(this.items, null, 2), "utf8"); await rename(tmp, this.file); }
21
+ async list() { await this.load(); return this.items.map(copy); }
22
+ async register(input) {
36
23
  await this.load();
37
24
  const slug = input.slug.trim().toLowerCase();
38
25
  if (!SLUG.test(slug))
39
- throw new Error("slug 只能包含小写字母、数字和连字符");
26
+ throw new Error("slug must contain lowercase letters, digits, and hyphens only");
40
27
  if (!input.name.trim() || input.name.length > 120)
41
- throw new Error("工具名称无效");
42
- const entrypoint = safeRelative(input.entrypoint);
43
- const root = resolve(workspaceRoot);
44
- const file = resolve(root, entrypoint);
45
- if (!isInside(root, file))
46
- throw new Error("entrypoint 必须位于工作区内");
47
- const info = await stat(file).catch(() => undefined);
48
- if (!info?.isFile())
49
- throw new Error("entrypoint 文件不存在");
50
- if (!entrypoint.endsWith(".html"))
51
- throw new Error("entrypoint 必须是 HTML 文件");
52
- if (this.items.some((x) => x.workspaceId === input.workspaceId && x.slug === slug))
53
- throw new Error("工具 slug 已存在");
28
+ throw new Error("tool name is invalid");
29
+ if (this.items.some((x) => x.slug === slug))
30
+ throw new Error("tool slug already exists");
54
31
  if (this.items.length >= MAX_TOOLS)
55
- throw new Error("工具数量已达到上限");
56
- const now = new Date().toISOString();
57
- const tool = { id: `tool_${crypto.randomUUID()}`, slug, name: input.name.trim(), description: input.description?.trim().slice(0, 500), workspaceId: input.workspaceId, entrypoint, source: "user", enabled: true, createdAt: now, updatedAt: now };
32
+ throw new Error("tool limit reached");
33
+ if (!Array.isArray(input.files) || !input.files.length || input.files.length > MAX_ASSETS)
34
+ throw new Error("tool must contain 1-32 files");
35
+ const entrypoint = safeRelative(input.entrypoint ?? "index.html");
36
+ if (!entrypoint.endsWith(".html"))
37
+ throw new Error("entrypoint must be an HTML file");
38
+ const files = normalizeAssets(input.files);
39
+ if (!files.some((file) => file.path === entrypoint))
40
+ throw new Error("entrypoint must be included in files");
41
+ const script = input.script ? normalizeScript(input.script, files) : undefined, finalDir = join(this.toolsDir, slug), stagingDir = `${finalDir}.${crypto.randomUUID()}.staging`;
42
+ if (await stat(finalDir).catch(() => undefined))
43
+ throw new Error("tool directory already exists");
44
+ try {
45
+ await writeAssets(stagingDir, files);
46
+ await rename(stagingDir, finalDir);
47
+ }
48
+ catch (error) {
49
+ await rm(stagingDir, { recursive: true, force: true });
50
+ throw error;
51
+ }
52
+ const now = new Date().toISOString(), tool = { id: `tool_${crypto.randomUUID()}`, slug, name: input.name.trim(), description: input.description?.trim().slice(0, 500), entrypoint, script, source: "user", enabled: true, createdAt: now, updatedAt: now };
58
53
  this.items.push(tool);
59
- await this.save();
60
- return { ...tool };
54
+ try {
55
+ await this.save();
56
+ }
57
+ catch (error) {
58
+ this.items.pop();
59
+ await rm(finalDir, { recursive: true, force: true });
60
+ throw error;
61
+ }
62
+ return copy(tool);
61
63
  }
62
- async remove(id) { await this.load(); const before = this.items.length; this.items = this.items.filter((x) => x.id !== id); if (this.items.length !== before)
63
- await this.save(); return before !== this.items.length; }
64
- async setEnabled(id, enabled) { await this.load(); const item = this.items.find((x) => x.id === id); if (!item)
65
- return undefined; item.enabled = enabled; item.updatedAt = new Date().toISOString(); await this.save(); return { ...item }; }
66
- async resolve(id, workspaceRoot, resource = "") { await this.load(); const tool = this.items.find((x) => x.id === id && x.enabled); if (!tool)
67
- return undefined; const root = resolve(workspaceRoot); const entrypoint = resolve(root, tool.entrypoint); if (!isInside(root, entrypoint))
68
- return undefined; const file = resource ? resolve(dirname(entrypoint), safeRelative(resource)) : entrypoint; if (!isInside(dirname(entrypoint), file))
69
- throw new Error("工具资源必须位于工具目录内"); await access(file).catch(() => { throw new Error("工具文件不存在"); }); return { tool: { ...tool }, file }; }
64
+ async remove(id) { await this.load(); const i = this.items.findIndex((x) => x.id === id); if (i < 0)
65
+ return false; const [tool] = this.items.splice(i, 1); await this.save(); await rm(join(this.toolsDir, tool.slug), { recursive: true, force: true }); return true; }
66
+ async resolve(id, resource = "") { await this.load(); const tool = this.items.find((x) => x.id === id && x.enabled); if (!tool)
67
+ return undefined; const root = join(this.toolsDir, tool.slug), file = resolve(root, resource ? safeRelative(resource) : tool.entrypoint); if (!inside(root, file))
68
+ throw new Error("tool resource must stay inside the tool directory"); const info = await stat(file).catch(() => undefined); if (!info?.isFile())
69
+ throw new Error("tool file does not exist"); return { tool: copy(tool), file, root }; }
70
70
  }
71
+ function normalizeAssets(files) { const paths = new Set(); let bytes = 0; return files.map((item) => { if (!item || typeof item.content !== "string")
72
+ throw new Error("tool file content must be text"); const path = safeRelative(item.path); if (paths.has(path))
73
+ throw new Error(`duplicate tool file: ${path}`); paths.add(path); bytes += Buffer.byteLength(item.content); if (bytes > MAX_ASSET_BYTES)
74
+ throw new Error("tool files exceed 512KB"); return { path, content: item.content }; }); }
75
+ function normalizeScript(script, files) { const path = safeRelative(script.path); if (!path.endsWith(".py") || !files.some((x) => x.path === path))
76
+ throw new Error("script must name an included .py file"); if (script.parameters && Object.values(script.parameters).some((x) => !isParam(x)))
77
+ throw new Error("script parameter schema is invalid"); return { path, timeoutMs: script.timeoutMs, parameters: script.parameters }; }
78
+ async function writeAssets(root, files) { for (const asset of files) {
79
+ const target = resolve(root, asset.path);
80
+ if (!inside(root, target))
81
+ throw new Error("tool file must stay inside its tool directory");
82
+ await mkdir(dirname(target), { recursive: true });
83
+ await writeFile(target, asset.content, "utf8");
84
+ } }
71
85
  function safeRelative(value) { const p = value.replace(/\\/g, "/").trim(); if (!p || p.startsWith("/") || p.includes("\0") || p.split("/").includes(".."))
72
- throw new Error("路径必须是工作区内的相对路径"); return p; }
73
- function isInside(root, target) { const r = relative(root, target); return r === "" || (!r.startsWith(".." + sep) && r !== ".." && !r.includes(":")); }
74
- function isTool(value) { const x = value; return !!x && typeof x === "object" && typeof x.id === "string" && typeof x.slug === "string" && typeof x.workspaceId === "string" && typeof x.entrypoint === "string" && (x.source === "user" || x.source === "builtin"); }
86
+ throw new Error("path must be relative and must not escape the tool directory"); return p; }
87
+ function inside(root, target) { const r = relative(resolve(root), resolve(target)); return r === "" || (!r.startsWith(`..${sep}`) && r !== ".." && !r.includes(":")); }
88
+ function isParam(value) { const x = value; return !!x && typeof x === "object" && ["string", "number", "boolean", "object", "array"].includes(x.type ?? "") && (x.required === undefined || typeof x.required === "boolean") && (x.enum === undefined || Array.isArray(x.enum)); }
89
+ function isTool(value) { const x = value; return !!x && typeof x === "object" && typeof x.id === "string" && typeof x.slug === "string" && typeof x.name === "string" && typeof x.entrypoint === "string" && (x.source === "user" || x.source === "builtin"); }
90
+ function copy(value) { return { ...value, script: value.script ? { ...value.script, parameters: value.script.parameters ? { ...value.script.parameters } : undefined } : undefined }; }
@@ -0,0 +1,35 @@
1
+ export type ToolParameter = {
2
+ type: "string" | "number" | "boolean" | "object" | "array";
3
+ required?: boolean;
4
+ enum?: Array<string | number | boolean>;
5
+ };
6
+ export type ToolScript = {
7
+ path: string;
8
+ timeoutMs?: number;
9
+ parameters?: Record<string, ToolParameter>;
10
+ };
11
+ export type ToolRun = {
12
+ id: string;
13
+ toolId: string;
14
+ status: "running" | "succeeded" | "failed" | "cancelled" | "timed_out";
15
+ startedAt: string;
16
+ finishedAt?: string;
17
+ logs: string;
18
+ result?: Record<string, unknown>;
19
+ error?: string;
20
+ };
21
+ /** Runs a registered Python entrypoint without a shell. It intentionally accepts no command string. */
22
+ export declare class ToolRunner {
23
+ private readonly runs;
24
+ start(input: {
25
+ toolId: string;
26
+ script: string;
27
+ cwd: string;
28
+ definition: ToolScript;
29
+ params: Record<string, unknown>;
30
+ }): ToolRun;
31
+ get(id: string, toolId: string): ToolRun | undefined;
32
+ cancel(id: string, toolId: string): ToolRun | undefined;
33
+ private finish;
34
+ }
35
+ export declare function validateParams(schema: Record<string, ToolParameter>, params: Record<string, unknown>): void;
@@ -0,0 +1,81 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ const MAX_LOG_BYTES = 512_000;
4
+ const MAX_TIMEOUT_MS = 10 * 60_000;
5
+ /** Runs a registered Python entrypoint without a shell. It intentionally accepts no command string. */
6
+ export class ToolRunner {
7
+ runs = new Map();
8
+ start(input) {
9
+ validateParams(input.definition.parameters ?? {}, input.params);
10
+ const python = process.env.SUPER_PI_PYTHON_COMMAND?.trim() || (process.platform === "win32" ? "python.exe" : "python3");
11
+ const id = `toolrun_${randomUUID()}`;
12
+ const run = { id, toolId: input.toolId, status: "running", startedAt: new Date().toISOString(), logs: "" };
13
+ // -I ignores user Python configuration and user site packages. No shell means params never become code.
14
+ const child = spawn(python, ["-I", "-u", input.script], { cwd: input.cwd, shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"], env: { PATH: process.env.PATH, SystemRoot: process.env.SystemRoot, TEMP: process.env.TEMP, TMP: process.env.TMP, PYTHONIOENCODING: "utf-8", PYTHONNOUSERSITE: "1", SUPER_PI_TOOL_ID: input.toolId } });
15
+ const active = { run, child, stdout: "", cancelled: false, timer: setTimeout(() => this.finish(id, "timed_out", `脚本超过 ${boundedTimeout(input.definition.timeoutMs)}ms 时限`), boundedTimeout(input.definition.timeoutMs)) };
16
+ active.timer.unref();
17
+ this.runs.set(id, active);
18
+ const log = (data) => { active.run.logs = clipped(`${active.run.logs}${data.toString("utf8")}`); };
19
+ child.stdout.on("data", (data) => { active.stdout = clipped(`${active.stdout}${data.toString("utf8")}`); });
20
+ child.stderr.on("data", log);
21
+ child.once("error", (error) => this.finish(id, "failed", error.message));
22
+ child.once("exit", (code) => {
23
+ if (active.run.status !== "running")
24
+ return;
25
+ if (code !== 0) {
26
+ this.finish(id, "failed", `Python exited with code ${code ?? "unknown"}`);
27
+ return;
28
+ }
29
+ try {
30
+ const parsed = JSON.parse(active.stdout);
31
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== "object")
32
+ throw new Error("stdout must be a JSON object");
33
+ active.run.result = parsed;
34
+ this.finish(id, "succeeded");
35
+ }
36
+ catch (error) {
37
+ active.run.logs = clipped(`${active.run.logs}${active.stdout}`);
38
+ this.finish(id, "failed", `脚本没有输出一个 JSON 对象:${error instanceof Error ? error.message : "invalid output"}`);
39
+ }
40
+ });
41
+ child.stdin.end(JSON.stringify(input.params));
42
+ return copy(run);
43
+ }
44
+ get(id, toolId) { const active = this.runs.get(id); return active?.run.toolId === toolId ? copy(active.run) : undefined; }
45
+ cancel(id, toolId) { const active = this.runs.get(id); if (!active || active.run.toolId !== toolId)
46
+ return undefined; if (active.run.status === "running") {
47
+ active.cancelled = true;
48
+ this.finish(id, "cancelled");
49
+ } return copy(active.run); }
50
+ finish(id, status, error) { const active = this.runs.get(id); if (!active || active.run.status !== "running")
51
+ return; clearTimeout(active.timer); active.run.status = status; active.run.finishedAt = new Date().toISOString(); if (error)
52
+ active.run.error = error; if (!active.child.killed) {
53
+ try {
54
+ active.child.kill();
55
+ }
56
+ catch { }
57
+ } }
58
+ }
59
+ function boundedTimeout(value) { return Math.max(1_000, Math.min(MAX_TIMEOUT_MS, Number.isFinite(value) ? Math.floor(value) : 60_000)); }
60
+ function clipped(text) { return text.length > MAX_LOG_BYTES ? text.slice(-MAX_LOG_BYTES) : text; }
61
+ function copy(run) { return { ...run, result: run.result ? { ...run.result } : undefined }; }
62
+ export function validateParams(schema, params) {
63
+ if (!params || Array.isArray(params) || typeof params !== "object")
64
+ throw new Error("params 必须是对象");
65
+ for (const key of Object.keys(params))
66
+ if (!schema[key])
67
+ throw new Error(`不允许的参数:${key}`);
68
+ for (const [key, rule] of Object.entries(schema)) {
69
+ const value = params[key];
70
+ if (value === undefined) {
71
+ if (rule.required)
72
+ throw new Error(`缺少参数:${key}`);
73
+ continue;
74
+ }
75
+ const actual = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
76
+ if (actual !== rule.type)
77
+ throw new Error(`参数 ${key} 必须是 ${rule.type}`);
78
+ if (rule.enum && !rule.enum.includes(value))
79
+ throw new Error(`参数 ${key} 不在允许值中`);
80
+ }
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.75",
3
+ "version": "0.1.76",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -84,6 +84,7 @@
84
84
  "scripts/agent-config-regression.test.mjs",
85
85
  "scripts/model-filter.test.mjs",
86
86
  "scripts/active-memory.test.mjs",
87
+ "scripts/tool-runner.test.mjs",
87
88
  "scripts/extension-load.test.mjs"
88
89
  ],
89
90
  "check:browser": [