dsh-runnpu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/compute-mode/preset/agent.cordis.yml +368 -0
- package/compute-mode/preset/preset.yml +3 -0
- package/compute-mode/preset/skills/runnpu/SKILL.md +233 -0
- package/compute-mode/preset/skills/runnpu/VENDOR.txt +11 -0
- package/compute-mode/preset/skills/runnpu/examples/01-dev-environment.sh +26 -0
- package/compute-mode/preset/skills/runnpu/examples/02-training-job.sh +26 -0
- package/compute-mode/preset/skills/runnpu/examples/03-inference-service.sh +24 -0
- package/compute-mode/preset/skills/runnpu/examples/04-fractional-vnpu.sh +14 -0
- package/compute-mode/preset/skills/runnpu/examples/05-troubleshoot.sh +17 -0
- package/compute-mode/preset/skills/runnpu/references/admin.md +60 -0
- package/compute-mode/preset/skills/runnpu/references/data-sources.md +35 -0
- package/compute-mode/preset/skills/runnpu/references/templates-assets.md +61 -0
- package/compute-mode/preset/skills/runnpu/references/training-runs.md +56 -0
- package/compute-mode/preset/skills/runnpu/references/troubleshooting.md +75 -0
- package/compute-mode/preset/skills/runnpu/references/workload-types.md +47 -0
- package/cordis.patch.yml +80 -0
- package/dist/chunk-JLZMOSQC.mjs +1 -0
- package/dist/chunk-JUBIWGKW.mjs +4 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-KQOFBYGE.mjs +9 -0
- package/dist/chunk-XMZG2NP7.mjs +2 -0
- package/dist/chunk-XQPU3AJN.mjs +1 -0
- package/dist/chunk-YKASEMP3.mjs +1 -0
- package/dist/chunk-YPL4VUYF.mjs +4 -0
- package/dist/compute-node.mjs +1 -0
- package/dist/compute-tools.mjs +41 -0
- package/dist/connectors.mjs +3 -0
- package/dist/dsh-runnpu.mjs +43 -0
- package/dist/json.mjs +1 -0
- package/dist/pins.mjs +1 -0
- package/dist/series.mjs +1 -0
- package/dist/training-node.mjs +2 -0
- package/dist/training-tools.mjs +30 -0
- package/dist/ui-host.mjs +1 -0
- package/lib/client.js +189 -0
- package/package.json +78 -0
- package/training-mode/preset/agent.cordis.yml +343 -0
- package/training-mode/preset/preset.yml +3 -0
- package/training-mode/preset/skills/runnpu/SKILL.md +233 -0
- package/training-mode/preset/skills/runnpu/VENDOR.txt +11 -0
- package/training-mode/preset/skills/runnpu/examples/01-dev-environment.sh +26 -0
- package/training-mode/preset/skills/runnpu/examples/02-training-job.sh +26 -0
- package/training-mode/preset/skills/runnpu/examples/03-inference-service.sh +24 -0
- package/training-mode/preset/skills/runnpu/examples/04-fractional-vnpu.sh +14 -0
- package/training-mode/preset/skills/runnpu/examples/05-troubleshoot.sh +17 -0
- package/training-mode/preset/skills/runnpu/references/admin.md +60 -0
- package/training-mode/preset/skills/runnpu/references/data-sources.md +35 -0
- package/training-mode/preset/skills/runnpu/references/templates-assets.md +61 -0
- package/training-mode/preset/skills/runnpu/references/training-runs.md +56 -0
- package/training-mode/preset/skills/runnpu/references/troubleshooting.md +75 -0
- package/training-mode/preset/skills/runnpu/references/workload-types.md +47 -0
- package/training-mode/preset/skills/swanlab-skill/LICENSE +21 -0
- package/training-mode/preset/skills/swanlab-skill/SKILL.md +163 -0
- package/training-mode/preset/skills/swanlab-skill/VENDOR.txt +9 -0
- package/training-mode/preset/skills/swanlab-skill/references/ANALYSIS_GUIDE.md +83 -0
- package/training-mode/preset/skills/swanlab-skill/references/CLI_REFERENCE.md +510 -0
- package/training-mode/preset/skills/swanlab-skill/references/SDK_QUICKSTART.md +341 -0
- package/training-mode/preset/skills/swanlab-skill/references/SWANLAB_CONCEPTS.md +523 -0
- package/training-mode/preset/skills/swanlab-skill/scripts/plot_metrics.py +353 -0
- package/training-mode/preset/skills/swanlab-skill/scripts/runs_benchmark.py +550 -0
- package/training-mode/preset/skills/training-research/SKILL.md +33 -0
- package/training-mode/preset/skills/training-research/references/pipeline.md +45 -0
- package/training-mode/preset/skills/training-research/references/research.md +76 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(n){return n===void 0?void 0:JSON.parse(JSON.stringify(n))}export{e as a};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as G}from"./chunk-JZXLCA2E.mjs";var z={};G(z,{QUERIES:()=>C,RunNpuError:()=>l,cluster:()=>E,deleteWorkload:()=>L,loginAndMintToken:()=>j,me:()=>P,nodeMetrics:()=>I,nodes:()=>S,overview:()=>b,overviewTrends:()=>x,project:()=>N,projects:()=>R,quotaDiff:()=>F,request:()=>r,runQuery:()=>Q,startWorkload:()=>D,status:()=>v,stopWorkload:()=>q,updateProject:()=>B,workload:()=>A,workloadMetrics:()=>_,workloads:()=>U});var l=class extends Error{constructor(t,n,o,s){super(n),this.name="RunNpuError",this.code=t,this.status=o,this.details=s}};function M(e){let t=String(e||"").replace(/\/+$/,"");if(!t)throw new l("not_configured","RunNPU server address is not set");return t.endsWith("/api/v1")?t:`${t}/api/v1`}var O=15e3,k=3e4;async function r(e,t,n,o={}){let s=new URL(`${M(e.server)}${n}`);for(let[i,c]of Object.entries(o.query??{}))c!==void 0&&c!==""&&s.searchParams.set(i,String(c));let m={accept:"application/json"};o.body!==void 0&&(m["content-type"]="application/json"),e.token&&(m.authorization=`Bearer ${e.token}`),e.cookie&&(m.cookie=e.cookie);let u=o.timeoutMs??O,y=AbortSignal.timeout(u),p=o.signal?AbortSignal.any([o.signal,y]):y,a,$;try{a=await fetch(s,{method:t,headers:m,body:o.body===void 0?void 0:JSON.stringify(o.body),signal:p,redirect:"manual"}),$=await a.text()}catch(i){throw y.aborted&&!o.signal?.aborted?new l("timeout",`${t} ${n} \u5728 ${u}ms \u5185\u6CA1\u6709\u54CD\u5E94\uFF1A\u5E73\u53F0\u65E0\u54CD\u5E94\u6216\u7F51\u7EDC\u4E0D\u901A`,void 0,{timeoutMs:u,origin:s.origin}):new l("unreachable",`cannot reach ${s.origin}: ${i?.message??i}`,void 0,{cause:String(i?.message??i)})}if(a.status>=300&&a.status<400){let i=a.headers.get("location"),c=i??void 0;try{i&&(c=new URL(i,s).href)}catch{}throw new l("redirected",c?`\u670D\u52A1\u5668\u8981\u6C42\u91CD\u5B9A\u5411\u5230 ${c}\uFF08HTTP ${a.status}\uFF09\uFF1A\u8BF7\u76F4\u63A5\u4F7F\u7528\u8BE5\u5730\u5740\uFF0C\u6700\u5E38\u89C1\u7684\u539F\u56E0\u662F\u767B\u5F55\u5730\u5740\u5199\u6210\u4E86 http:// \u800C\u670D\u52A1\u7AEF\u53EA\u63D0\u4F9B https://`:`\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${a.status} \u91CD\u5B9A\u5411\u4F46\u6CA1\u6709\u7ED9\u51FA Location \u5934\uFF1A\u8BF7\u786E\u8BA4\u767B\u5F55\u5730\u5740\u662F\u5426\u6B63\u786E`,a.status,{location:c})}let d;try{d=$?JSON.parse($):void 0}catch{d=void 0}if(!a.ok)throw new l(d?.error??`http_${a.status}`,d?.message??`${t} ${n} failed with ${a.status}`,a.status,d?.details);return{data:d,headers:a.headers}}async function j(e,t,n,o="DeepSeek Harness"){let s=await r({server:e},"POST","/auth/login",{body:{login:t,password:n}}),u=(s.headers.getSetCookie?.()??[]).map(a=>a.split(";")[0]).join("; ");if(!u)throw new l("no_session","login succeeded but the server returned no session cookie");let y=s.data??(await r({server:e,cookie:u},"GET","/me")).data,p;try{let a=await r({server:e,cookie:u},"POST","/me/access-keys",{body:{name:`${o} ${new Date().toISOString().slice(0,10)}`}});p=typeof a.data?.token=="string"?a.data.token:void 0}catch(a){throw new l("mint_failed",`logged in, but could not create an access key: ${a.message}`,a.status,a.details)}if(!p)throw new l("mint_failed","access key created but the response carried no plaintext token");return{token:p,me:y,mustChangePassword:!!y?.must_change_password}}async function v(e,t){return(await r(e,"GET","/system/status",{signal:t})).data}async function P(e,t){return(await r(e,"GET","/me",{signal:t})).data}async function b(e,t){return(await r(e,"GET","/overview",{signal:t})).data}async function x(e,t="24h",n){return(await r(e,"GET","/overview/trends",{query:{range:t},signal:n})).data}async function E(e,t){return(await r(e,"GET","/cluster",{signal:t})).data}async function S(e,t){return(await r(e,"GET","/nodes",{signal:t})).data}async function I(e,t,n,o){return(await r(e,"GET",`/nodes/${encodeURIComponent(t)}/metrics`,{query:{range:n},signal:o})).data}async function U(e,t={},n){return(await r(e,"GET","/workloads",{query:t,signal:n})).data}async function A(e,t,n,o){return(await r(e,"GET",`/workloads/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,{signal:o})).data}async function _(e,t,n,o,s){return(await r(e,"GET",`/workloads/${encodeURIComponent(t)}/${encodeURIComponent(n)}/metrics`,{query:{range:o},signal:s})).data}async function q(e,t,n,o){return(await r(e,"POST",`/workloads/${encodeURIComponent(t)}/${encodeURIComponent(n)}/stop`,{signal:o,timeoutMs:k})).data}async function D(e,t,n,o){return(await r(e,"POST",`/workloads/${encodeURIComponent(t)}/${encodeURIComponent(n)}/start`,{signal:o,timeoutMs:k})).data}async function L(e,t,n,o){return await r(e,"DELETE",`/workloads/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,{signal:o,timeoutMs:k}),{deleted:!0}}async function R(e,t){return(await r(e,"GET","/projects",{signal:t})).data}async function N(e,t,n){return(await r(e,"GET",`/projects/${encodeURIComponent(t)}`,{signal:n,timeoutMs:k})).data}async function B(e,t,n,o){return(await r(e,"PATCH",`/projects/${encodeURIComponent(t)}`,{body:n,signal:o,timeoutMs:k})).data}var H=[["guaranteed","\u4FDD\u5E95"," \u5361"],["limit","\u53EF\u501F"," \u5361"],["lending_limit","\u53EF\u501F\u51FA\u4E0A\u9650"," \u5361"]],W=[["limit_gib","\u4E0A\u9650"," GiB"],["alert_pct","\u544A\u8B66\u9608\u503C","%"]];function J(e){return e==null?"\u672A\u8BBE\u7F6E":String(e)}function T(e,t,n){let o=Number(e);if(!Number.isInteger(o)||o<0)throw new Error(`${t} \u7684${n}\u5FC5\u987B\u662F \u2265 0 \u7684\u6574\u6570\uFF0C\u6536\u5230 ${JSON.stringify(e)}`);return o}function F(e,t){let n=Array.isArray(e?.pools)?e.pools.map(u=>({...u})):[],o=Array.isArray(e?.storage)?e.storage.map(u=>({...u})):[],s=[],m=(u,y,p,a,$)=>{for(let d of y??[]){let i=String(d?.pool??"").trim();if(i==="")throw new Error(`${a}\u53D8\u66F4\u7F3A\u5C11 pool\uFF08\u6C60\u540D\uFF09`);let c=u.find(w=>w.pool===i);if(c===void 0){c={pool:i,...$};for(let[f,g]of p)d[f]!==void 0&&(c[f]=T(d[f],i,g));u.push(c);let w=p.filter(([f])=>c[f]!==void 0).map(([f,g,h])=>`${g} ${c[f]}${h}`);s.push(`${a} ${i}\uFF1A\u65B0\u589E\uFF0C${w.join("\u3001")}`);continue}for(let[w,f,g]of p){if(d[w]===void 0)continue;let h=T(d[w],i,f);c[w]!==h&&(s.push(`${a} ${i}\uFF1A${f} ${J(c[w])} \u2192 ${h}${g}`),c[w]=h)}}};return m(n,t?.pools,H,"\u7B97\u529B\u6C60",{guaranteed:0}),m(o,t?.storage,W,"\u5B58\u50A8\u6C60",{limit_gib:0}),{next:{...e,pools:n,storage:o,defaults:e?.defaults??{}},lines:s}}var C={overview:{label:"\u5E73\u53F0\u603B\u89C8",run:(e,t,n)=>b(e,n)},"overview.trends":{label:"\u603B\u89C8\u8D8B\u52BF",run:(e,t,n)=>x(e,t?.range??"24h",n)},cluster:{label:"\u96C6\u7FA4\u7EC4\u4EF6\u4E0E\u8D44\u6E90\u6C60",run:(e,t,n)=>E(e,n)},nodes:{label:"\u8282\u70B9\u5217\u8868",run:(e,t,n)=>S(e,n)},"node.metrics":{label:"\u5355\u8282\u70B9\u6307\u6807",run:(e,t,n)=>I(e,t.node??t.name,t.range,n)},workloads:{label:"\u8D1F\u8F7D\u5217\u8868",run:(e,t,n)=>U(e,t??{},n)},"workload.metrics":{label:"\u5355\u8D1F\u8F7D\u6307\u6807",run:(e,t,n)=>_(e,t.project,t.name,t.range,n)},projects:{label:"\u9879\u76EE\u5217\u8868",run:(e,t,n)=>R(e,n)}};async function Q(e,t,n,o){let s=C[t];if(!s)throw new l("unknown_query",`no such query: ${t}`);return await s.run(e,n??{},o)}export{l as a,j as b,v as c,P as d,q as e,D as f,L as g,R as h,N as i,B as j,F as k,C as l,Q as m,z as n};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{c as y}from"./chunk-JUBIWGKW.mjs";function g(e){if(!e)return;let s=new Date(e).getTime();return Number.isFinite(s)?Math.max(0,Math.round((Date.now()-s)/1e3)):void 0}var l=e=>Math.round(e/3600*10)/10;function w(e){return typeof e=="string"?e:e?`${e.project??"?"}/${e.name??"?"}`:"\u672A\u77E5\u8D1F\u8F7D"}function F(e){let s=e?.kpi??{},r=(e?.projects??[]).map(n=>({slug:n.slug,name:n.name,quotaCards:n.quota_cards??0,usedCards:n.used_cards??0,running:n.running??0,queued:n.queued??0,idleHeld:Math.max(0,(n.used_cards??0)-(n.running??0))})),a=(e?.idle??[]).map(n=>({workload:w(n.workload),type:typeof n.workload=="object"?n.workload?.display_type:void 0,idleCards:n.idle_cards??0,idleHours:n.idle_seconds===void 0?void 0:l(n.idle_seconds),source:n.source}));return{cards:{total:s.cards_total??0,allocated:s.cards_allocated??0,available:s.cards_available??0,idleAllocated:s.cards_idle_allocated??0},workloads:{running:s.workloads_running??0,queued:s.workloads_queued??0,failed:s.workloads_failed??0},nodes:{ready:s.nodes_ready??0,total:s.nodes_total??0},projects:r.sort((n,d)=>d.usedCards-n.usedCards||d.idleHeld-n.idleHeld),idle:a.sort((n,d)=>d.idleCards-n.idleCards||(d.idleHours??0)-(n.idleHours??0)),metricsAvailable:e?.metrics?.available!==!1}}function N(e,s,r={}){let a=r.idleHours??2,n=r.queuedHours??1,d=r.notReadyMinutes??5,c=r.terminatingHours??.5,u=[];e?.metrics?.available===!1&&u.push({severity:"warn",kind:"metrics-source-down",subject:"\u6307\u6807\u6E90",detail:`\u6307\u6807\u6E90\u672A\u63A5\u5165\uFF08${e.metrics.reason??"\u539F\u56E0\u672A\u7ED9\u51FA"}\uFF09\u3002\u5229\u7528\u7387\u7C7B\u5224\u65AD\u6B64\u65F6\u5168\u90E8\u4E0D\u53EF\u4FE1\u2014\u2014\u5B83\u4EEC\u7F3A\u6570\u636E\uFF0C\u4E0D\u662F\u4E3A\u96F6\u3002`});for(let t of e?.idle??[]){let o=t.idle_seconds===void 0?void 0:l(t.idle_seconds);o!==void 0&&o<a||u.push({severity:(t.idle_cards??0)>=2?"high":"warn",kind:"idle-allocation",subject:w(t.workload),detail:`\u5360\u7740 ${t.idle_cards??"?"} \u5F20\u5361\u4F46\u95F2\u7F6E`+(o===void 0?"":` ${o} \u5C0F\u65F6`)+`\uFF08\u5224\u5B9A\u6765\u6E90\uFF1A${t.source??"\u5E73\u53F0"}\uFF09\u3002\u786E\u8BA4\u65E0\u4EBA\u4F7F\u7528\u540E\u53EF\u4EE5\u505C\u6B62\u4EE5\u91CA\u653E\u914D\u989D\u3002`})}for(let t of e?.queued??[]){let o=g(t.created_at);o===void 0||o<n*3600||u.push({severity:"warn",kind:"queued-too-long",subject:`${t.project}/${t.name}`,detail:`\u5DF2\u6392\u961F ${l(o)} \u5C0F\u65F6\uFF08${t.xpu_count??"?"} \u5361\uFF0C\u6C60 ${t.pool??"\u672A\u6307\u5B9A"}\uFF09\u3002\u5148\u770B\u9879\u76EE\u914D\u989D\uFF0C\u518D\u770B\u96C6\u7FA4\u662F\u5426\u771F\u7684\u6CA1\u6709\u7A7A\u95F2\u5361\u3002`})}for(let t of s?.items??[]){let o=`${t.project}/${t.name}`,_=t.pods_ready??0,f=t.pods_total??0,m=g(t.admitted_at??t.started_at??t.created_at);if((t.status==="admitted"||t.status==="running")&&f>0&&_<f&&m!==void 0&&m>=d*60&&u.push({severity:"high",kind:"not-ready",subject:o,detail:`\u72B6\u6001 ${t.status} \u4F46\u53EA\u6709 ${_}/${f} \u4E2A Pod \u5C31\u7EEA\uFF0C\u5360\u7740 ${t.xpu_count??"?"} \u5F20\u5361\u3002\u5148\u770B events \u4E0E logs\uFF1B\u5D29\u6E83\u5FAA\u73AF\u4F1A\u4E00\u76F4\u5360\u7740\u914D\u989D\u5374\u4E0D\u4EA7\u51FA\u7B97\u529B\u3002`}),t.status==="failed"&&u.push({severity:"warn",kind:"failed",subject:o,detail:"\u8D1F\u8F7D\u5DF2\u5931\u8D25\u3002\u786E\u8BA4\u539F\u56E0\u540E\u5220\u9664\uFF0C\u5426\u5219\u5B83\u4F1A\u4E00\u76F4\u7559\u5728\u5217\u8868\u91CC\u5E72\u6270\u5224\u65AD\u3002"}),t.phase==="terminating"||t.status==="terminating"){let p=g(t.last_activity_at??t.created_at);p!==void 0&&p>=c*3600&&u.push({severity:"warn",kind:"stuck-terminating",subject:o,detail:`\u5904\u4E8E Terminating \u5DF2 ${l(p)} \u5C0F\u65F6\u3002\u8FD9\u662F\u5DF2\u77E5\u7684\u4E0A\u6E38\u884C\u4E3A\u2014\u2014\u4E0D\u8981\u53CD\u590D\u5220\u9664\u3001\u4E0D\u8981\u7528 --force \u5FAA\u73AF\uFF0C\u7B49\u5F85\u6216\u8BA9\u8FD0\u7EF4\u4ECB\u5165\u3002`})}}let i={high:0,warn:1};return u.sort((t,o)=>(i[t.severity]??9)-(i[o.severity]??9))}import{appendFile as b,mkdir as O,open as R,rename as T,stat as j}from"node:fs/promises";import{dirname as q,join as S}from"node:path";function H(){return S(y(),"operations.jsonl")}var E=256*1024,L=5*1024*1024;async function A(e,s=E){let r;try{r=await R(e,"r")}catch{return[]}try{let{size:a}=await r.stat(),n=Math.max(0,a-s),d=a-n;if(d<=0)return[];let c=Buffer.allocUnsafe(d),{bytesRead:u}=await r.read(c,0,d,n),i=c.subarray(0,u).toString("utf8");if(n>0){let t=i.indexOf(`
|
|
2
|
+
`);i=t===-1?"":i.slice(t+1)}return i.split(`
|
|
3
|
+
`).filter(t=>t.trim()!=="")}catch{return[]}finally{await r.close().catch(()=>{})}}async function C(e,s,r=L){await O(q(e),{recursive:!0});try{let{size:a}=await j(e);a>=r&&await T(e,`${e}.1`)}catch{}await b(e,`${s}
|
|
4
|
+
`,"utf8")}async function V(e){let s=JSON.stringify({at:new Date().toISOString(),...e});try{await C(H(),s)}catch{}}async function D(e=50){let s=await A(H()),r=[];for(let a=s.length-1;a>=0&&r.length<e;a-=1)try{r.push(JSON.parse(s[a]))}catch{}return r}async function P(e,s=10*6e4){let r=Date.now()-s;return(await D(200)).find(n=>n.action==="delete"&&n.target===e&&n.outcome==="executed"&&new Date(n.at).getTime()>=r)}var M="delete|stop|start|create",x=new RegExp(String.raw`(?:^|[\s;&|(){}\`])(?:[^\s;&|(){}]*[/\\])?runnpu\s+workload\s+(${M})\b`,"gi"),v=/^[A-Za-z0-9][\w.-]*\/[\w.-]+$/,$=new RegExp(String.raw`(?:^|[\s;&|(){}\`])(?:[^\s;&|(){}]*[/\\])?runnpu\s+project\s+quota\s+set\b`,"gi"),I=[["project","project"],["name","name"],["type","type"],["from-template","template"],["image","image"],["xpu","xpu"],["nodes","nodes"],["replicas","replicas"]],U=120;function h(e){let s=e.at(0);return(s==='"'||s==="'")&&e.at(-1)===s?e.slice(1,-1):e}function B(e,s){let r=new RegExp(String.raw`--${s}(?:=|\s+)("[^"]*"|'[^']*'|[^\s;&|]+)`,"i").exec(e);if(!r)return;let a=h(r[1]).trim();return a===""?void 0:a.slice(0,U)}function k(e){let s=e.search(/[;&|\n]/),r=(s===-1?e:e.slice(0,s)).trim();if(r!=="")for(let a of r.split(/\s+/)){let n=h(a);if(v.test(n))return n}}function Y(e){if(typeof e!="string"||e==="")return{};$.lastIndex=0;let s=$.exec(e);if(s){let i=h(e.slice(s.index+s[0].length).trim().split(/\s+/)[0]??""),t=i!==""&&!i.startsWith("-")&&!i.includes("=")?i:void 0;return{deny:"\u4E0D\u8981\u5728 shell \u91CC\u6539\u914D\u989D\uFF1A\u8BF7\u6539\u7528 cluster_quota_set \u5DE5\u5177\uFF08\u53C2\u6570 project + pools/storage\uFF0C\u53EA\u5199\u8981\u53D8\u7684\u5B57\u6BB5\uFF09\u3002\u533A\u522B\u4E0D\u662F\u987A\u624B\u4E0E\u5426\u2014\u2014CLI \u7684 quota set \u662F\u6574\u4EFD\u66FF\u6362\uFF0C\u6F0F\u4E00\u4E2A\u6C60\u5C31\u6E05\u6389\u4E00\u4E2A\u6C60\uFF0C\u800C\u4E14\u6CA1\u6709\u5BA1\u6279\uFF1A\u5DE5\u5177\u4F1A\u5148\u8BFB\u5F53\u524D\u503C\u3001\u751F\u6210 diff \u9001\u8FDB harness \u5BA1\u6279\u5F39\u7A97\uFF0C\u7528\u6237\u6279\u51C6\u7684\u5C31\u662F\u968F\u540E\u6267\u884C\u7684\u90A3\u4EFD\u53D8\u66F4\uFF0C\u5E76\u4E14\u65E0\u8BBA\u6279\u4E0D\u6279\u90FD\u5199\u8FDB\u64CD\u4F5C\u65E5\u5FD7\u3002\u53EA\u8BFB\u7684 quota get \u4E0D\u53D7\u5F71\u54CD\uFF0C\u7167\u5E38\u6267\u884C\u3002",record:{action:"quota_set",outcome:"rejected",...t===void 0?{}:{target:t},error:"shell \u8DEF\u5F84\u88AB guard \u62E6\u622A\uFF0C\u672A\u6267\u884C\uFF1B\u5E94\u6539\u7528 cluster_quota_set"}}}x.lastIndex=0;let r=[...e.matchAll(x)];if(r.length===0)return{};let a=r.find(i=>i[1].toLowerCase()!=="create");if(a){let i=a[1].toLowerCase(),t=k(e.slice(a.index+a[0].length));return{deny:`\u4E0D\u8981\u5728 shell \u91CC ${i} \u8D1F\u8F7D${t?`\uFF08${t}\uFF09`:""}\uFF1A\u8BF7\u6539\u7528 cluster_workload_action \u5DE5\u5177\uFF08\u53C2\u6570 project / name / action / reason\uFF09\u3002\u533A\u522B\u4E0D\u662F\u987A\u624B\u4E0E\u5426\u2014\u2014\u5DE5\u5177\u4F1A\u628A delete \u9001\u8FDB harness \u7684\u5BA1\u6279\u5F39\u7A97\uFF0C\u7528\u6237\u6279\u51C6\u7684\u5C31\u662F\u968F\u540E\u771F\u6B63\u6267\u884C\u7684\u90A3\u4E2A\u7ED3\u6784\u5316\u52A8\u4F5C\uFF0C\u5E76\u4E14\u65E0\u8BBA\u6279\u4E0D\u6279\u90FD\u5199\u8FDB\u64CD\u4F5C\u65E5\u5FD7\uFF1Bshell \u547D\u4EE4\u4E24\u6837\u90FD\u7ED5\u8FC7\u4E86\u3002\u53EA\u8BFB\u5B50\u547D\u4EE4\uFF08list / logs / events / endpoints / get / exec \u2026\uFF09\u4E0D\u53D7\u5F71\u54CD\uFF0C\u7167\u5E38\u6267\u884C\u3002`,record:{action:i,outcome:"rejected",...t===void 0?{}:{target:t},error:"shell \u8DEF\u5F84\u88AB guard \u62E6\u622A\uFF0C\u672A\u6267\u884C\uFF1B\u5E94\u6539\u7528 cluster_workload_action"}}}let n=r[0],d=e.slice(n.index+n[0].length),c={};for(let[i,t]of I){let o=B(d,i);o!==void 0&&(c[t]=o)}let u=c.project&&c.name?`${c.project}/${c.name}`:c.name??k(d);return{record:{action:"create",outcome:"attempted",...u===void 0?{}:{target:u},...Object.keys(c).length===0?{}:{details:c}}}}export{F as a,N as b,A as c,C as d,V as e,D as f,P as g,Y as h};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as O,b as o,c as v,d as b,e as f,f as j}from"./chunk-KQOFBYGE.mjs";import{b as _,e as P,f as x,g as M}from"./chunk-YPL4VUYF.mjs";import{k as N}from"./chunk-XMZG2NP7.mjs";import"./chunk-JLZMOSQC.mjs";import{b as T,h as k}from"./chunk-JUBIWGKW.mjs";import"./chunk-JZXLCA2E.mjs";var C="runnpu-compute-node",H=["webServer"],R={apiPath:"/runnpu-api",refreshMs:3e4},F=200;function X(w,A){let q={...R,...A??{}},u=q.apiPath,G=w.get("webServer"),S=j({refresh:()=>Promise.all([$(),D()]),intervalMs:q.refreshMs}),y=S.hub;O(w);let g=new Map;w.effect(()=>w.provide("runnpuFollowups",{set(n,e){if(n){for(g.delete(n),g.set(n,e);g.size>F;)g.delete(g.keys().next().value);y.publish("followups",{sessionId:n,questions:e})}},get:n=>g.get(n)??[]}),"runnpu-compute-node: follow-up store");let i=new N;w.effect(()=>w.provide("runnpuPins",i),"runnpu-compute-node: pin store");let d=()=>{let n=w.get("runnpu");if(!n){let e=new Error("RunNPU \u8FDE\u63A5\u5668\u672A\u6302\u8F7D");throw e.code="connector_missing",e.status=503,e}return n};async function U(n){try{let e=await d().identity?.(n);return e?{operator:e}:{}}catch{return{}}}async function $(n){let e=await i.refreshAll(d(),n);return e.length>0&&y.publish("pins",e),e}async function D(n){try{let e=await d().query(void 0,"workloads",{limit:50},n);y.publish("workloads",{items:e?.items??[],at:Date.now()})}catch{}}let E=Promise.resolve();w.effect(()=>i.onChange(()=>{E=E.then(async()=>{await i.refreshMissing(d()),y.publish("list",await i.list())}).catch(()=>{})}),"runnpu-compute-node: pin change fanout");let l=(n,e,t)=>{w.effect(()=>G.register({kind:n,path:e,handler:t}),`runnpu-compute-node: ${e}`)};l("exact",`${u}/state`,f({async GET(n,e,t){let a=t.searchParams.get("cwd")||void 0,s=d(),[r,c,p,h]=await Promise.all([s.describe(a),s.health(a),i.list(),i.range()]);o(e,200,{connection:r,health:c,queries:s.queries,pins:p,range:h,refreshMs:q.refreshMs})}})),l("exact",`${u}/login`,f({async POST(n,e){let t=await b(n),a=String(t?.server??"").trim(),s=String(t?.email??"").trim(),r=String(t?.password??"");if(!a||!s||!r){v(e,422,"validation_failed","\u767B\u5F55\u5730\u5740\u3001\u8D26\u53F7\u3001\u5BC6\u7801\u90FD\u4E0D\u80FD\u4E3A\u7A7A");return}let c=await d().login({server:a,email:s,password:r,scope:t?.scope==="project"?"project":"global",cwd:t?.cwd||void 0}),p=await d().health(t?.cwd||void 0);y.publish("health",p),o(e,200,{stored:c.stored,identity:c.identity,health:p})}})),l("exact",`${u}/logout`,f({async POST(n,e){let a=(await b(n))?.cwd||void 0,s=d(),r=w.get("credentials");r&&await r.unset(T.runnpuToken),a&&await k(a,{[T.runnpuToken]:void 0});let[c,p]=await Promise.all([s.describe(a),s.health(a)]),h=c?.runnpu?.tokenSource;y.publish("health",p),o(e,200,{health:p,residual:h,reason:h?h==="env"?"\u8FDB\u7A0B\u73AF\u5883\u4ECD\u6709 RUNNPU_TOKEN\uFF0C\u672C\u6B21\u767B\u51FA\u672A\u5B8C\u5168\u751F\u6548\uFF1A\u8BE5\u53D8\u91CF\u7531\u542F\u52A8 dsh \u7684\u8FDB\u7A0B\u6301\u6709\uFF0C\u53EA\u80FD\u5728\u5916\u90E8\u6E05\u9664\u540E\u91CD\u542F\u3002":`${h} \u5C42\u4ECD\u80FD\u89E3\u6790\u51FA RUNNPU_TOKEN\uFF0C\u672C\u6B21\u767B\u51FA\u672A\u5B8C\u5168\u751F\u6548\u3002`:void 0})}})),l("exact",`${u}/followups`,f({GET(n,e,t){let a=t.searchParams.get("sessionId")??"";o(e,200,{sessionId:a,questions:g.get(a)??[]})}})),l("exact",`${u}/query`,f({async GET(n,e,t){let a=t.searchParams.get("id");if(!a){v(e,422,"validation_failed","query id \u7F3A\u5931");return}let s={};for(let[c,p]of t.searchParams)c!=="id"&&c!=="cwd"&&(s[c]=p);let r=await d().query(t.searchParams.get("cwd")||void 0,a,s);o(e,200,{id:a,params:s,at:Date.now(),data:r})}})),l("exact",`${u}/anomalies`,f({async GET(n,e,t){let a=t.searchParams.get("cwd")||void 0,s=d(),[r,c]=await Promise.all([s.query(a,"overview",{}),s.query(a,"workloads",{limit:100})]);o(e,200,{findings:_(r,c)})}})),l("exact",`${u}/workloads`,f({async GET(n,e,t){let a=t.searchParams.get("cwd")||void 0;o(e,200,await d().query(a,"workloads",{limit:50}))}})),l("exact",`${u}/workloads/action`,f({async POST(n,e){let t=await b(n),{project:a,name:s,action:r}=t??{};if(!a||!s||!["stop","start","delete"].includes(r)){v(e,422,"validation_failed","\u9700\u8981 project\u3001name \u548C\u5408\u6CD5\u7684 action");return}let c=`${a}/${s}`,p={action:r,target:c,via:"panel",reason:t?.reason??"\u4ECE\u7B97\u529B\u9762\u677F\u64CD\u4F5C"},h=U(t?.cwd||void 0);if(r==="delete"){let m=await M(c);if(m){await P({...p,...await h,outcome:"skipped",error:`\u5DF2\u4E8E ${m.at} \u5220\u9664`}),o(e,200,{outcome:"skipped",detail:`${c} \u5DF2\u7ECF\u5220\u8FC7\u4E86\uFF08${m.at}\uFF09\u3002\u8D1F\u8F7D\u53EF\u80FD\u957F\u65F6\u95F4\u505C\u7559\u5728 Terminating\uFF0C\u8BF7\u7B49\u5F85\uFF0C\u4E0D\u8981\u53CD\u590D\u5220\u3002`});return}}try{let m=await d().act(t?.cwd||void 0,r,a,s);await P({...p,...await h,outcome:"executed"}),o(e,200,{outcome:"executed",status:m?.status})}catch(m){await P({...p,...await h,outcome:"failed",error:m.message}),v(e,m.status??500,m.code??"action_failed",m.message)}}})),l("exact",`${u}/operations`,f({async GET(n,e){o(e,200,{operations:await x(30)})}})),l("exact",`${u}/pins`,f({async GET(n,e){o(e,200,{pins:await i.list(),range:await i.range()})},async POST(n,e){let t=await b(n);if(!t?.query){v(e,422,"validation_failed","pin \u5FC5\u987B\u5E26 query");return}if(t.params!==void 0&&(typeof t.params!="object"||t.params===null||Array.isArray(t.params))){v(e,422,"validation_failed","params \u5FC5\u987B\u662F\u5BF9\u8C61");return}let{pin:a,created:s}=await i.add({title:t.title,query:String(t.query),params:t.params,series:Array.isArray(t.series)?t.series:void 0,view:t.view&&typeof t.view=="object"?t.view:void 0,workspace:t.cwd||void 0},{sessionId:t.sessionId,question:t.question,note:t.note});if(!s){o(e,200,{pin:{...a,latest:i.latestOf(a.id)},duplicate:!0});return}let r=await i.refresh(a,d());o(e,201,{pin:{...a,latest:r},duplicate:!1})},async DELETE(n,e,t){let a=t.searchParams.get("id"),s=await i.remove(a);s&&y.publish("unpinned",{id:a}),o(e,200,{id:a,removed:s})}})),l("exact",`${u}/range`,f({async POST(n,e){let t=await b(n),a=await i.setRange(String(t?.range??""),d());y.publish("list",await i.list()),o(e,200,{range:a.range})}})),l("exact",`${u}/pins/refresh`,f({async POST(n,e){o(e,200,{results:await $()})}})),l("exact",`${u}/events`,(n,e)=>{S.handler(n,e,{seed(t){i.list().then(async a=>{let s=a.map(r=>r.latest).filter(Boolean);s.length>0&&t("pins",s),s.length<a.length&&(await i.refreshMissing(d()),y.publish("list",await i.list()))}).catch(()=>{})}})}),S.install(w,"runnpu-compute-node")}export{X as apply,H as inject,C as name};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import{k as H}from"./chunk-YKASEMP3.mjs";import{a as _,b as P,c as C,d as T,e as g,g as L,h as N}from"./chunk-YPL4VUYF.mjs";import{a as y}from"./chunk-XQPU3AJN.mjs";import{b as j,d as W,g as $}from"./chunk-XMZG2NP7.mjs";import{a as v,d as I,f as U}from"./chunk-JLZMOSQC.mjs";import{c as O}from"./chunk-JUBIWGKW.mjs";import"./chunk-JZXLCA2E.mjs";import{defineTool as f}from"@deepseek-ai/dsh-tools";import{join as F}from"node:path";function M(){return F(O(),"reports.jsonl")}var J=1024*1024,A=r=>`${r.kind}:${r.subject}`;function q(r,d){if(!(typeof d!="number"||typeof r!="number"))return r-d}function x(r,d,l){let t=_(r),e=P(r,d),o=new Map((l?.findings??[]).map(c=>[A(c),c])),i=new Map(e.map(c=>[A(c),c])),s=[...o.values()].filter(c=>!i.has(A(c))),u=e.filter(c=>!o.has(A(c))),n=e.filter(c=>o.has(A(c))),a=new Map((l?.projects??[]).map(c=>[c.slug,c])),p={usedCards:0,idleHeld:0},h=t.projects.map(c=>{let m=l?a.get(c.slug)??p:void 0;return{...c,usedDelta:q(c.usedCards,m?.usedCards),idleHeldDelta:q(c.idleHeld,m?.idleHeld)}}),k=h.filter(c=>l?c.usedDelta!==0||c.idleHeldDelta!==0:c.usedCards>0);return{at:new Date().toISOString(),since:l?.at,cards:{...t.cards,allocatedDelta:q(t.cards.allocated,l?.cards?.allocated),idleAllocatedDelta:q(t.cards.idleAllocated,l?.cards?.idleAllocated)},workloads:{...t.workloads,runningDelta:q(t.workloads.running,l?.workloads?.running),queuedDelta:q(t.workloads.queued,l?.workloads?.queued),failedDelta:q(t.workloads.failed,l?.workloads?.failed)},metricsAvailable:t.metricsAvailable,projects:k,allProjects:h,findings:e,changes:{appeared:u,ongoing:n,resolved:s},quiet:l!==void 0&&u.length===0&&s.length===0&&k.length===0}}async function B(){let r=await C(M(),J);for(let d=r.length-1;d>=0;d-=1)try{return JSON.parse(r[d])}catch{}}async function Q(r){let d={at:r.at,cards:r.cards,workloads:r.workloads,projects:(r.allProjects??r.projects).map(l=>({slug:l.slug,usedCards:l.usedCards,idleHeld:l.idleHeld})),findings:r.findings};try{await T(M(),JSON.stringify(d))}catch{}}var ye="runnpu-compute-tools",me=["tools"],Y=4;function w(r,d,l){let t=r.get(d);if(!t)throw new Error(`${l} \u672A\u6302\u8F7D\uFF1A\u786E\u8BA4 dsh-runnpu bundle \u5DF2\u88C5\u8FDB\u5F53\u524D profile \u5E76\u91CD\u542F dsh`);return t}var b=r=>r?.agent?.session?.header?.cwd;function G(r){return{limit:200,...r.project===void 0?{}:{project:r.project},...r.status===void 0?{}:{status:r.status}}}function V(r,d){if($(d)){if(!v.includes(r.query))throw new Error(`${r.query} \u6CA1\u6709\u65F6\u95F4\u8F74\uFF0C\u753B\u4E0D\u4E86 ${d}\u3002\u8981\u4E48\u6362 view: "table" / "bar"\uFF0C\u8981\u4E48\u6362\u4E00\u4E2A\u65F6\u5E8F\u67E5\u8BE2\uFF08${v.join(" / ")}\uFF09`);if(r.query==="node.metrics"&&!r.node)throw new Error("node.metrics \u9700\u8981 `node`\uFF08\u8282\u70B9\u540D\uFF09");if(r.query==="workload.metrics"&&(!r.project||!r.name))throw new Error("workload.metrics \u9700\u8981 `project` \u4E0E `name`");return}if(!j[r.query])throw new Error(`${r.query} \u4E0D\u662F\u5217\u8868\u67E5\u8BE2\uFF0C\u505A\u4E0D\u4E86 ${d}\u3002\u53EF\u5217\u8868\u5316\u7684\u662F\uFF1A${Object.keys(j).join(" / ")}`)}function S(r){return{kind:r.view??"line",source:r.source,columns:r.columns,dimension:r.dimension,measures:r.measures,sort:r.sort,desc:r.desc,limit:r.limit}}function K(r){if(r.kind==="table"){if(r.rows.length===0)return"\u6CA1\u6709\u6570\u636E\u884C";let d=r.columns.map(t=>t.label).join(" | "),l=r.rows.slice(0,5).map(t=>t.map(e=>e.text).join(" | ")).join(`
|
|
2
|
+
`);return`\u5171 ${r.total} \u884C\uFF0C\u5217\uFF1A${d}
|
|
3
|
+
${l}`+(r.total>5?`
|
|
4
|
+
\u2026\uFF08\u5176\u4F59 ${r.total-5} \u884C\u53EA\u5728\u754C\u9762\u4E0A\uFF0C\u4E0D\u5728\u8FD9\u91CC\uFF09`:"")}if(r.kind==="bar"){if(r.categories.length===0)return"\u6CA1\u6709\u6570\u636E";let d=r.categories.slice(0,5).map((l,t)=>`${l}\uFF1A${r.series.map(e=>`${e.name} ${e.data[t]??"\u2014"}`).join("\uFF0C")}`).join("\uFF1B");return`\u6309 ${r.dimension} \u5206\u7EC4\uFF0C\u524D\u51E0\u9879\uFF1A${d}`}return`\u6700\u65B0\u503C\uFF1A${X(r.series??[])||"\u65E0\u6570\u636E"}`}function X(r){return r.map(d=>{let l=U(d);if(l===void 0)return`${d.name}\uFF1A\u65E0\u6570\u636E`;let t=Number(Number(l).toPrecision(4));return`${d.name}\uFF1A${t}${d.unit?` ${d.unit}`:""}`}).join("\uFF1B")}var Z=["delete"],D="\u5BA1\u6279\u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u7834\u574F\u6027\u64CD\u4F5C\u4E00\u5F8B\u62D2\u7EDD\u6267\u884C",z="rejected",ee=new Set(["bash","pwsh"]);async function E(r,d){try{let t=await r.get("runnpu")?.identity?.(d);return t?{operator:t}:{}}catch{return{}}}async function te(r,d,l){await g({...l,via:"shell",sessionId:d?.agent?.session?.id,...await E(r,b(d))})}function ge(r,d){let l=new Set(Array.isArray(d?.requireApproval)?d.requireApproval:Z);r.effect(()=>r.tools.guard(t=>{if(!ee.has(t?.name))return;let e=t.arguments?.command;if(typeof e!="string")return;let o=N(e);return o.record&&te(r,t,o.record).catch(()=>{}),o.deny}),"runnpu-compute-tools: shell workload guard"),r.tools.register(f({name:"cluster_chart",description:'Render a cluster query as a VIEW in the conversation \u2014 a line chart, a bar chart, or a TABLE \u2014 with a Pin button that sends it to the right column, where it keeps refreshing itself. Choose the kind by the shape of the question, not by habit: "how did it CHANGE" is a line (utilization over the day, queue depth, storage growth); "who has the MOST" is a bar (cards per project, free cards per node); "which ones, with their numbers" is a table (quota vs used per project, what is queued, node inventory). A table is a first-class answer here, not a fallback \u2014 it is the right shape for a list, and pasting the same list as prose is worse for the reader and cannot be pinned. Write your reply about what the view SHOWS (trend, outlier, what to do), not by restating its values. A metric source that is not wired up reports as unavailable, never as zero: say so rather than showing a flat line as if it meant idle.',parameters:{title:{type:"string",required:!0,description:'Human title, e.g. "\u8FD1 24 \u5C0F\u65F6 NPU \u7B97\u529B\u5229\u7528\u7387" or "\u5404\u9879\u76EE\u914D\u989D\u4E0E\u5360\u7528". Becomes the pinned card\u2019s title.'},query:{type:"string",required:!0,enum:[...v,...Object.keys(j)],description:"TIME SERIES (view line/area): overview.trends = whole cluster; node.metrics = one node (needs `node`); workload.metrics = one workload (needs `project` and `name`). LISTS (view bar/table): overview = the dashboard lists (source: projects | idle | queued | nodes); projects = the project dimension (source: items | usage | storage | queues \u2014 per project PER POOL); workloads / nodes / cluster = that endpoint\u2019s own list."},view:{type:"string",enum:["line","area","bar","table"],description:"How to render it. Default line. bar and table need a LIST query; line and area need a time-series query."},source:{type:"string",description:'Which list inside the payload, for the queries that hold several. overview: projects | idle | queued | nodes. projects: items (the plain project list) | usage | storage | queues \u2014 the last three are the PROJECT DIMENSION the platform keeps nested, one row per project PER POOL: `usage` carries guaranteed vs used cards and a signed `over_guarantee` (positive = borrowing preemptible capacity, negative = paying for idle guarantee); `storage` carries limit/used/used_pct per storage pool; `queues` carries admission and its reason. Sort `usage` by over_guarantee to answer "who is over and who is wasting" in one view. workloads / nodes / cluster have exactly one list; omit it there.'},group:{type:"string",description:"line/area only. Which group to plot; the vocabulary depends on the query. overview.trends: utilization | allocation | storage | workloads. node.metrics: cpu | memory | net | disk. workload.metrics: npu_util | npu_hbm | cpu | mem | net. A group that does not exist falls back to the first one and the result says so, so guessing is cheap."},columns:{type:"array",items:{type:"string"},description:"table only. Column keys in the order you want them. Omit for a curated default per list \u2014 that default is usually right, so name columns only when the question is about something it leaves out."},dimension:{type:"string",description:'bar only. The key whose values become the bars, e.g. "slug" or "name". Defaults to the obvious identifier.'},measures:{type:"array",items:{type:"string"},description:'bar only. Numeric keys to draw as bar height, e.g. ["quota_cards","used_cards"]. At most five.'},sort:{type:"string",description:"bar/table. Sort rows by this key, descending. Sorting by the number the question is about is almost always what you want."},desc:{type:"boolean",description:"bar/table. Set false for ascending order."},limit:{type:"integer",description:"bar/table. Rows to show, default 50. A bar chart is unreadable past about 15."},node:{type:"string",description:'node.metrics: the node name, e.g. "910b1".'},project:{type:"string",description:'workload.metrics: project slug. ALSO a real server-side filter for the workloads list \u2014 pass it for "just this project\u2019s workloads" rather than fetching everything and describing a subset.'},status:{type:"string",description:'workloads list only: narrow to one status server-side, e.g. "running" / "admitted" / "failed".'},name:{type:"string",description:"workload.metrics: workload name."},range:{type:"string",enum:["1h","6h","24h","7d"],description:"line/area only. Time window for THIS chart. Default 24h. (A pinned chart follows the panel\u2019s own window instead.)"},series:{type:"array",items:{type:"string"},description:"line/area only. Series keys to narrow to. Omit for every series in the group (capped at five)."}},output:{schema:{type:"object",additionalProperties:!1,properties:{title:{type:"string",required:!0},query:{type:"string",required:!0},kind:{type:"string",required:!0},params:{type:"object",required:!0,additionalProperties:!0,properties:{}},summary:{type:"string",required:!0},note:{type:"string"},view:{type:"object",required:!0,additionalProperties:!0,properties:{kind:{type:"string",required:!0}}},groupsAvailable:{type:"array",required:!0,items:{type:"string"}},sourcesAvailable:{type:"array",required:!0,items:{type:"string"}}}},render:(t,e)=>[{type:"text",text:`\u5DF2\u5728\u5BF9\u8BDD\u4E2D\u663E\u793A${e.kind==="table"?"\u8868\u683C":"\u56FE\u8868"}\u300C${e.title}\u300D\uFF0C\u7528\u6237\u5DF2\u7ECF\u770B\u5230\u4E86\uFF08\u4ED6\u4EEC\u53EF\u4EE5\u70B9\u300C\u56FA\u5B9A\u5230\u53F3\u680F\u300D\uFF09\u3002**\u4E0D\u8981\u628A\u5B83\u518D\u6284\u4E00\u904D\u5230\u56DE\u590D\u91CC** \u2014\u2014 \u4E0B\u9762\u8FD9\u51E0\u884C\u662F\u7ED9\u4F60\u5224\u65AD\u7528\u7684\uFF0C\u4E0D\u662F\u7ED9\u4F60\u8F6C\u8FF0\u7684\uFF1B\u4F60\u7684\u56DE\u590D\u5E94\u8BE5\u8BF4\u8FD9\u5F20\u89C6\u56FE\u610F\u5473\u7740\u4EC0\u4E48\u3002
|
|
5
|
+
`+e.summary+(e.note?`
|
|
6
|
+
\u6CE8\u610F\uFF1A${e.note}`:"")+(e.groupsAvailable.length>1?`
|
|
7
|
+
\u8FD9\u4E2A\u67E5\u8BE2\u8FD8\u80FD\u753B\uFF1A${e.groupsAvailable.join(" / ")}`:"")+(e.sourcesAvailable.length>1?`
|
|
8
|
+
\u8FD9\u4E2A\u67E5\u8BE2\u8FD8\u6709\u8FD9\u4E9B\u5217\u8868\uFF1A${e.sourcesAvailable.join(" / ")}`:"")}],presentationMeta:(t,e)=>y({card:"runnpu-chart",title:e.title,query:e.query,params:e.params,note:e.note,view:e.view,spec:{...S(t),group:t.group,series:t.series}})},timeoutMs:6e4,async execute(t,e){let o=w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668"),i=b(e),s=t.view??"line";V(t,s);let u=$(s)?{range:t.range??"24h",...t.group===void 0?{}:{group:t.group},...t.node===void 0?{}:{node:t.node},...t.project===void 0?{}:{project:t.project},...t.name===void 0?{}:{name:t.name}}:G(t),n=await o.query(i,t.query,u,e.signal),a=S(t),p=W(t.query,n,a);return y({title:t.title,query:t.query,kind:p.kind,params:{...u,...p.group===void 0?{}:{group:p.group}},summary:K(p),note:p.note,view:p,groupsAvailable:$(s)?I(t.query,n):[],sourcesAvailable:$(s)?[]:(j[t.query]??[]).map(h=>h.key)})}})),r.tools.register(f({name:"cluster_pin",description:'Pin a chart to the right column, where it refreshes itself and keeps showing current data. Pin when the user says to, or when a chart answers a question they will clearly ask again \u2014 a pinned chart is worth more than a number in the transcript. ALWAYS pass `question`: the pin records why it exists, and a chart nobody can explain three weeks later is clutter. Pinning a view that is already pinned is refused and reported, so it is safe to call. The column holds at most 16 pins; a full column REFUSES rather than evicting \u2014 list what is there with cluster_pins and let the user choose what to give up. A pin stores the QUERY and the VIEW \u2014 the time window is a live control on the panel that the user sets for all pinned charts at once, so never pin "the same chart at another range": tell them to change the range instead. Tables and bar charts pin exactly like line charts do, and a quota table that refreshes itself in the corner is usually worth more than the same numbers said once in a reply.',parameters:{title:{type:"string",required:!0,description:'Card title. Do NOT put a time window in it ("\u8FD1 1 \u5C0F\u65F6 \u2026"): the window is a live control the user changes for the whole column, so a baked-in window makes the card lie the moment they change it. Name what it shows \u2014 "NPU \u7B97\u529B\u5229\u7528\u7387", "\u5361\u5206\u914D".'},query:{type:"string",required:!0,enum:[...v,...Object.keys(j)],description:"The query to replay on every refresh. Same vocabulary as cluster_chart."},view:{type:"string",enum:["line","area","bar","table"],description:"How the pinned card renders. Default line. Pass the same value you passed to cluster_chart."},source:{type:"string",description:"bar/table: which list inside the payload. overview: projects | idle | queued | nodes. projects: items | usage | storage | queues (per project per pool). Pass the same value you passed to cluster_chart."},columns:{type:"array",items:{type:"string"},description:"table: column keys, in order. Omit for the curated default."},dimension:{type:"string",description:"bar: the key whose values become the bars."},measures:{type:"array",items:{type:"string"},description:"bar: numeric keys drawn as bar height."},sort:{type:"string",description:"bar/table: sort rows by this key, descending."},desc:{type:"boolean",description:"bar/table: set false for ascending."},limit:{type:"integer",description:"bar/table: rows to show, default 50."},group:{type:"string",description:"line/area: which group to plot; vocabulary depends on the query (see cluster_chart)."},node:{type:"string",description:"node.metrics: the node name."},project:{type:"string",description:"workload.metrics: project slug. Also filters a workloads list view \u2014 pass the same value you passed to cluster_chart."},status:{type:"string",description:'workloads list view: narrow to one status, e.g. "running".'},name:{type:"string",description:"workload.metrics: workload name."},series:{type:"array",items:{type:"string"},description:"line/area: series keys to narrow to."},question:{type:"string",required:!0,description:"What the user was asking when this chart mattered. Shown under the card as its provenance."},note:{type:"string",description:"Anything else worth remembering about this chart \u2014 caveats, the reading you gave."}},output:{schema:{type:"object",additionalProperties:!1,properties:{id:{type:"string",required:!0},title:{type:"string",required:!0},refreshed:{type:"boolean",required:!0},duplicate:{type:"boolean",required:!0},error:{type:"string"}}},render:(t,e)=>[{type:"text",text:e.duplicate?`\u53F3\u680F\u5DF2\u7ECF\u6709\u540C\u6837\u7684\u89C6\u56FE\u4E86\uFF08\u300C${e.title}\u300D\uFF09\uFF0C\u6CA1\u6709\u91CD\u590D\u56FA\u5B9A\u3002\u6362\u5206\u7EC4\u3001\u6362\u89C6\u56FE\u7C7B\u578B\u90FD\u662F\u53E6\u4E00\u5F20\u5361\uFF1B\u6362\u65F6\u95F4\u8303\u56F4\u4E0D\u7528\u91CD\u65B0\u56FA\u5B9A\u2014\u2014\u53F3\u680F\u9876\u90E8\u6709\u65F6\u95F4\u8303\u56F4\u9009\u62E9\u5668\uFF0C\u7528\u6237\u81EA\u5DF1\u9009\uFF0C\u6240\u6709\u56FA\u5B9A\u56FE\u8868\u4E00\u8D77\u8DDF\u7740\u53D8\u3002`:e.refreshed?`\u5DF2\u56FA\u5B9A\u5230\u53F3\u680F\uFF1A\u300C${e.title}\u300D\uFF0C\u4E4B\u540E\u4F1A\u81EA\u52A8\u5237\u65B0\u4E3A\u6700\u65B0\u6570\u636E\u3002`:`\u5DF2\u56FA\u5B9A\u300C${e.title}\u300D\uFF0C\u4F46\u9996\u6B21\u53D6\u6570\u5931\u8D25\uFF1A${e.error}`}]},async execute(t,e){let o=w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668"),i=w(r,"runnpuPins","\u56FE\u8868\u56FA\u5B9A\u533A");V(t,t.view??"line");let s;try{s=await i.add({title:t.title,query:t.query,params:$(t.view)?{...t.group===void 0?{}:{group:t.group},...t.node===void 0?{}:{node:t.node},...t.project===void 0?{}:{project:t.project},...t.name===void 0?{}:{name:t.name}}:G(t),series:t.series,view:S(t),workspace:b(e)},{sessionId:e?.agent?.session?.id,question:t.question,note:t.note})}catch(p){throw p.code==="pin_limit"?new Error(`${p.message}\u3002\u7528 cluster_pins \u5217\u51FA\u73B0\u6709\u56FE\u8868\uFF0C\u95EE\u7528\u6237\u60F3\u8BA9\u51FA\u54EA\u4E00\u5F20\uFF0C\u7ECF\u4ED6\u4EEC\u540C\u610F\u540E cluster_unpin\uFF0C\u518D\u91CD\u65B0\u56FA\u5B9A\u3002\u4E0D\u8981\u81EA\u4F5C\u4E3B\u5F20\u79FB\u9664\u3002`):p}let{pin:u,created:n}=s;if(!n)return y({id:u.id,title:u.title,refreshed:!0,duplicate:!0});let a=await i.refresh(u,o,e.signal);return y({id:u.id,title:u.title,refreshed:!!a.ok,duplicate:!1,error:a.ok?void 0:a.error})}})),r.tools.register(f({name:"cluster_pins",description:`List what is currently pinned in the right column, with each pin's query and the question it came from. Read it before pinning something new, and when the user refers to "that chart" without saying which.`,parameters:{},output:{schema:{type:"object",additionalProperties:!1,properties:{range:{type:"string",required:!0},pins:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{id:{type:"string",required:!0},title:{type:"string",required:!0},query:{type:"string",required:!0},group:{type:"string"},question:{type:"string"},createdAt:{type:"string"},healthy:{type:"boolean",required:!0}}}}}},render:(t,e)=>[{type:"text",text:e.pins.length===0?"\u53F3\u680F\u8FD8\u6CA1\u6709\u56FA\u5B9A\u4EFB\u4F55\u56FE\u8868\u3002":`\u53F3\u680F\u56FA\u5B9A\u4E86 ${e.pins.length} \u5F20\u56FE\u8868\uFF0C\u5F53\u524D\u65F6\u95F4\u8303\u56F4 ${e.range}\uFF1A
|
|
9
|
+
`+e.pins.map(o=>`- ${o.title}\uFF08${o.query} / ${o.group??"\u9ED8\u8BA4\u5206\u7EC4"}\uFF09${o.healthy?"":"\uFF08\u6700\u8FD1\u4E00\u6B21\u5237\u65B0\u5931\u8D25\uFF09"}`).join(`
|
|
10
|
+
`)}]},async execute(){let t=w(r,"runnpuPins","\u56FE\u8868\u56FA\u5B9A\u533A"),e=await t.list();return y({range:await t.range(),pins:e.map(o=>({id:o.id,title:o.title,query:o.query,group:o.params?.group,question:o.provenance?.question,createdAt:o.provenance?.createdAt,healthy:o.latest===void 0?!0:!!o.latest.ok}))})}})),r.tools.register(f({name:"cluster_usage",description:`Who is holding the cards: headline capacity, per-project attribution (quota vs held vs actually running), and the platform's own list of idle allocations. Use this for "\u914D\u989D\u7528\u4E86\u591A\u5C11 / \u8C01\u5360\u4E86\u6700\u591A\u7684\u5361 / \u6709\u6CA1\u6709\u95F2\u7F6E" instead of assembling it from several CLI calls \u2014 the numbers are computed here, not by you, so they do not need checking. The gap between a project's held cards and its running cards is what starts most reclamation conversations. Quota is in CARDS; a null metric means the source is not wired up, not zero.`,parameters:{},output:{schema:{type:"object",additionalProperties:!0,properties:{}},render:(t,e)=>{let o=e.projects.slice(0,6).map(s=>`- ${s.slug}\uFF1A\u6301\u6709 ${s.usedCards} / \u914D\u989D ${s.quotaCards} \u5361\uFF0C\u8FD0\u884C ${s.running}${s.queued?`\uFF0C\u6392\u961F ${s.queued}`:""}${s.idleHeld?`\uFF0C\u5176\u4E2D ${s.idleHeld} \u5F20\u672A\u5728\u8DD1`:""}`).join(`
|
|
11
|
+
`),i=e.idle.slice(0,6).map(s=>`- ${s.workload}\uFF1A${s.idleCards} \u5361\u95F2\u7F6E${s.idleHours===void 0?"":` ${s.idleHours} \u5C0F\u65F6`}`).join(`
|
|
12
|
+
`);return[{type:"text",text:`\u5361\uFF1A${e.cards.allocated}/${e.cards.total} \u5DF2\u5206\u914D\uFF0C${e.cards.available} \u7A7A\u95F2\uFF0C\u5176\u4E2D ${e.cards.idleAllocated} \u5F20\u5DF2\u5206\u914D\u4F46\u95F2\u7F6E\u3002
|
|
13
|
+
\u8D1F\u8F7D\uFF1A\u8FD0\u884C ${e.workloads.running}\uFF0C\u6392\u961F ${e.workloads.queued}\uFF0C\u5931\u8D25 ${e.workloads.failed}\u3002
|
|
14
|
+
\u8282\u70B9\uFF1A${e.nodes.ready}/${e.nodes.total} \u5C31\u7EEA\u3002
|
|
15
|
+
|
|
16
|
+
\u6309\u9879\u76EE\uFF1A
|
|
17
|
+
${o}`+(i?`
|
|
18
|
+
|
|
19
|
+
\u95F2\u7F6E\u5360\u7528\uFF1A
|
|
20
|
+
${i}`:"")+(e.metricsAvailable?"":`
|
|
21
|
+
|
|
22
|
+
\u6CE8\u610F\uFF1A\u6307\u6807\u6E90\u672A\u63A5\u5165\uFF0C\u5229\u7528\u7387\u7C7B\u5224\u65AD\u6B64\u65F6\u4E0D\u53EF\u4FE1\u3002`)}]}},async execute(t,e){let i=await w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668").query(b(e),"overview",{},e.signal);return y(_(i))}})),r.tools.register(f({name:"cluster_anomalies",description:'Everything that looks wrong right now, by deterministic rules: idle allocations, workloads queued too long, admitted-but-not-ready (how a crash loop looks from here), failed workloads, and anything stuck terminating. Call it when the user asks what is wrong, and at the start of any "\u96C6\u7FA4\u600E\u4E48\u6837" answer \u2014 it is cheap and it catches the things nobody thinks to ask about. Each finding carries the evidence it fired on, so say what the evidence is rather than just repeating the verdict.',parameters:{idleHours:{type:"number",description:"How long an allocation must be idle to report it. Default 2."},queuedHours:{type:"number",description:"How long a workload must wait to report it. Default 1."},notReadyMinutes:{type:"number",description:"Grace period after admission before an admitted-but-not-ready workload counts as a crash loop. Default 5. Lower it only when hunting a suspected crash loop \u2014 pulling an image and starting a container legitimately takes minutes, so a small value reports every normal launch."},terminatingHours:{type:"number",description:"How long a workload must sit in Terminating before it counts as stuck. Default 0.5. Anything shorter is the ordinary delete path still finishing, not a problem."}},output:{schema:{type:"object",additionalProperties:!0,properties:{}},render:(t,e)=>[{type:"text",text:e.findings.length===0?"\u6309\u5F53\u524D\u89C4\u5219\u6CA1\u6709\u53D1\u73B0\u5F02\u5E38\u3002":`\u53D1\u73B0 ${e.findings.length} \u9879\uFF1A
|
|
23
|
+
`+e.findings.map(o=>`- [${o.severity==="high"?"\u91CD\u8981":"\u6CE8\u610F"}] ${o.subject}\uFF1A${o.detail}`).join(`
|
|
24
|
+
`)}]},async execute(t,e){let o=w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668"),i=b(e),[s,u]=await Promise.all([o.query(i,"overview",{},e.signal),o.query(i,"workloads",{limit:100},e.signal)]);return y({findings:P(s,u,{idleHours:t.idleHours,queuedHours:t.queuedHours,notReadyMinutes:t.notReadyMinutes,terminatingHours:t.terminatingHours})})}})),r.tools.register(f({name:"cluster_workload_action",description:'Stop, start, or delete one workload. This is the ONLY way you should perform these actions \u2014 not `runnpu workload stop/start/delete` through the shell. The difference is not convenience: every call is written to the operation log whether it went ahead or not, which is what later answers "who stopped this and why", and a delete additionally asks the user through the harness so what they approve is the exact structured action that then runs. A shell command asks in prose and executes something the user never saw. Explain what you are about to do BEFORE calling, in your own words, including which workload and why \u2014 stop and start go ahead without a prompt, so your sentence is the only warning the user gets.',parameters:{project:{type:"string",required:!0,description:"Project slug owning the workload."},name:{type:"string",required:!0,description:"Workload name."},action:{type:"string",required:!0,enum:["stop","start","delete"],description:"stop suspends it and frees its cards; start resumes a stopped one; delete removes it permanently. Prefer stop when the user only wants the cards back."},reason:{type:"string",required:!0,description:"Why this is being done, in one line. Shown in the approval prompt and kept in the operation log."}},output:{schema:{type:"object",additionalProperties:!1,properties:{target:{type:"string",required:!0},action:{type:"string",required:!0},outcome:{type:"string",required:!0,enum:["executed","rejected","failed","skipped"]},detail:{type:"string"},status:{type:"string"}}},render:(t,e)=>[{type:"text",text:e.outcome==="executed"?`\u5DF2\u5BF9 ${e.target} \u6267\u884C ${e.action}${e.status?`\uFF0C\u5F53\u524D\u72B6\u6001 ${e.status}`:""}\u3002`:e.outcome==="rejected"?e.detail===z?`\u7528\u6237\u6CA1\u6709\u6279\u51C6\u5BF9 ${e.target} \u7684 ${e.action}\uFF0C\u672A\u6267\u884C\u3002\u4E0D\u8981\u6362\u4E2A\u8BF4\u6CD5\u518D\u95EE\u4E00\u6B21\u3002`:e.detail===D?`\u6CA1\u6709\u5BF9 ${e.target} \u6267\u884C ${e.action}\uFF1A\u5BA1\u6279\u901A\u9053\u4E0D\u53EF\u7528\uFF08\u5BA1\u6279\u670D\u52A1\u672A\u6302\u8F7D\uFF09\uFF0C\u7834\u574F\u6027\u64CD\u4F5C\u6309\u62D2\u7EDD\u5904\u7406\u3002**\u8FD9\u4E0D\u662F\u7528\u6237\u7684\u62D2\u7EDD** \u2014\u2014 \u4ED6\u4EEC\u6839\u672C\u6CA1\u6709\u6536\u5230\u8FC7\u5F39\u7A97\uFF0C\u4E0D\u8981\u5411\u4ED6\u4EEC\u590D\u8FF0\u4E00\u4E2A\u6CA1\u53D1\u751F\u8FC7\u7684\u5426\u51B3\u3002\u5982\u5B9E\u8BF4\u660E\u662F harness \u914D\u7F6E\u95EE\u9898\uFF0C\u8BF7\u4ED6\u4EEC\u68C0\u67E5 dsh-runnpu bundle \u662F\u5426\u88C5\u8FDB\u5F53\u524D profile \u5E76\u91CD\u542F dsh\u3002`:`\u6CA1\u6709\u5BF9 ${e.target} \u6267\u884C ${e.action}\uFF1A\u5BA1\u6279\u8BF7\u6C42\u6CA1\u6709\u62FF\u5230\u653E\u884C\u7ED3\u679C\uFF08${e.detail}\uFF09\uFF0C\u6309\u62D2\u7EDD\u5904\u7406\u3002**\u8FD9\u4E0D\u662F\u7528\u6237\u70B9\u4E86\u62D2\u7EDD** \u2014\u2014 \u8BF7\u6C42\u53EF\u80FD\u88AB\u53D6\u6D88\u3001\u4E2D\u65AD\uFF0C\u6216\u5BA1\u6279\u901A\u9053\u5F53\u65F6\u4E0D\u53EF\u7528\u3002\u5982\u5B9E\u8BF4\u660E\u53D1\u751F\u4E86\u4EC0\u4E48\u518D\u8BA9\u7528\u6237\u51B3\u5B9A\u8981\u4E0D\u8981\u91CD\u6765\uFF0C\u4E0D\u8981\u6362\u4E2A\u8BF4\u6CD5\u628A\u540C\u4E00\u4E2A\u64CD\u4F5C\u518D\u63D0\u4E00\u6B21\u3002`:e.outcome==="skipped"?`\u672A\u6267\u884C\uFF1A${e.detail}`:`\u5BF9 ${e.target} \u6267\u884C ${e.action} \u5931\u8D25\uFF1A${e.detail}`}]},timeoutMs:12e4,async execute(t,e){let o=w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668"),i=`${t.project}/${t.name}`,s=e?.agent?.session?.id,u={action:t.action,target:i,via:"agent",sessionId:s,reason:t.reason},n=E(r,b(e));if(t.action==="delete"){let a=await L(i);if(a){let p=`${i} \u5728 ${a.at} \u5DF2\u7ECF\u5220\u8FC7\u4E86\u3002\u5220\u9664\u662F\u5E42\u7B49\u7684\uFF0C\u518D\u5220\u4E00\u6B21\u540C\u6837\u8FD4\u56DE\u6210\u529F\u4F46\u4EC0\u4E48\u90FD\u4E0D\u4F1A\u53D1\u751F\uFF1B\u8D1F\u8F7D\u53EF\u80FD\u505C\u7559\u5728 Terminating \u6570\u5206\u949F\u4E43\u81F3\u66F4\u4E45\uFF08\u4E0A\u6E38\u5DF2\u77E5\u95EE\u9898\uFF09\u3002\u7B49\u5F85\u6216\u8BA9\u8FD0\u7EF4\u4ECB\u5165\uFF0C\u4E0D\u8981\u53CD\u590D\u5220\u3002`;return await g({...u,...await n,outcome:"skipped",error:p}),y({target:i,action:t.action,outcome:"skipped",detail:p})}}if(l.has(t.action)){let a=r.get("approval");if(!a){let h=D;return await g({...u,...await n,outcome:"rejected",error:h}),y({target:i,action:t.action,outcome:"rejected",detail:h})}let p=await a.request({agent:e.agent,toolName:"cluster_workload_action",callId:e.callId,reason:`${t.action} ${i}\uFF1A${t.reason}`,signal:e.signal});if(p!=="allowed-once")return await g({...u,...await n,outcome:"rejected",error:p}),y({target:i,action:t.action,outcome:"rejected",detail:p})}try{let a=await o.act(b(e),t.action,t.project,t.name,e.signal);return await g({...u,...await n,outcome:"executed"}),y({target:i,action:t.action,outcome:"executed",status:typeof a?.status=="string"?a.status:void 0})}catch(a){return await g({...u,...await n,outcome:"failed",error:a.message}),y({target:i,action:t.action,outcome:"failed",detail:a.message})}}})),r.tools.register(f({name:"cluster_quota_set",description:'Change a project\'s quota: guaranteed / borrowable cards per compute pool, and the size cap per storage pool. Use this instead of `runnpu project quota set`, which REPLACES the whole quota block \u2014 with the CLI, changing one pool means reading the quota, retyping every other pool by hand, and living with whatever a slip drops; a pool left out is a pool set to nothing. This tool reads the current quota, applies only the fields you name, and leaves everything else exactly as it was, in one write. Name ONLY what changes. What the user approves is a DIFF ("\u4FDD\u5E95 2 \u2192 4 \u5361"), so say the same diff in your own words before you call \u2014 agreeing to a change is only possible if the change is what was shown. Quota is in CARDS. Removing a pool is not offered on purpose: to take capacity away, keep the pool and set its numbers to 0 (the platform refuses a quota that empties the pools while the cluster still holds a QuotaGrant, because deleting that object fails every new workload in the project). Editing quota needs the system-level `projects:quota` permission \u2014 a 403 here is a permission fact to report, not something to retry.',parameters:{project:{type:"string",required:!0,description:'Project slug, e.g. "main". The same identifier used everywhere else in this mode.'},pools:{type:"array",description:"Compute pools to change, one entry per pool, and ONLY the pools that change \u2014 every pool you leave out keeps its current numbers. A pool name that does not exist in the quota yet is ADDED. Pool names come from `cluster_chart` (query `projects`, source `usage`) or `runnpu cluster get`.",items:{type:"object",additionalProperties:!1,properties:{pool:{type:"string",required:!0,description:'Accelerator pool name, e.g. "ascend910b4".'},guaranteed:{type:"integer",description:"Guaranteed cards (Kueue nominalQuota): capacity this project always gets. Omit to leave unchanged."},limit:{type:"integer",description:"Extra cards this project may BORROW on top of the guarantee (Kueue borrowingLimit \u2014 an increment, not a total). Borrowed capacity is preemptible. 0 means no borrowing."},lending_limit:{type:"integer",description:"Cap on how much of the guarantee may be lent OUT to other projects. Omit to leave unchanged."}}}},storage:{type:"array",description:"Storage pools to change. Same rule: only what changes, everything else survives untouched.",items:{type:"object",additionalProperties:!1,properties:{pool:{type:"string",required:!0,description:'Storage pool name, e.g. "shared-cephfs".'},limit_gib:{type:"integer",description:"Size cap in GiB. 0 means nothing is allocated from this pool."},alert_pct:{type:"integer",description:"Usage percentage that raises an alert. Platform default is 80."}}}},reason:{type:"string",required:!0,description:"Why, in one line. Shown in the approval prompt beside the diff and kept in the operation log."}},output:{schema:{type:"object",additionalProperties:!1,properties:{project:{type:"string",required:!0},outcome:{type:"string",required:!0,enum:["executed","rejected","failed","skipped"]},changes:{type:"array",required:!0,items:{type:"string"}},detail:{type:"string"}}},render:(t,e)=>[{type:"text",text:e.outcome==="executed"?`\u5DF2\u66F4\u65B0 ${e.project} \u7684\u914D\u989D\uFF1A
|
|
25
|
+
${e.changes.map(o=>`- ${o}`).join(`
|
|
26
|
+
`)}`:e.outcome==="skipped"?`\u6CA1\u6709\u6539\u52A8\uFF1A${e.detail}`:e.outcome==="rejected"?e.detail===z?`\u7528\u6237\u6CA1\u6709\u6279\u51C6\u8FD9\u6B21 ${e.project} \u7684\u914D\u989D\u4FEE\u6539\uFF0C\u672A\u6267\u884C\u3002\u4E0D\u8981\u6362\u4E2A\u8BF4\u6CD5\u518D\u95EE\u4E00\u6B21\u3002`:e.detail===D?`\u6CA1\u6709\u4FEE\u6539 ${e.project} \u7684\u914D\u989D\uFF1A\u5BA1\u6279\u901A\u9053\u4E0D\u53EF\u7528\uFF08\u5BA1\u6279\u670D\u52A1\u672A\u6302\u8F7D\uFF09\uFF0C\u6309\u62D2\u7EDD\u5904\u7406\u3002**\u8FD9\u4E0D\u662F\u7528\u6237\u7684\u62D2\u7EDD** \u2014\u2014 \u4ED6\u4EEC\u6839\u672C\u6CA1\u6709\u6536\u5230\u8FC7\u5F39\u7A97\u3002\u5982\u5B9E\u8BF4\u660E\u662F harness \u914D\u7F6E\u95EE\u9898\uFF0C\u8BF7\u4ED6\u4EEC\u68C0\u67E5 dsh-runnpu bundle \u662F\u5426\u88C5\u8FDB\u5F53\u524D profile \u5E76\u91CD\u542F dsh\u3002`:`\u6CA1\u6709\u4FEE\u6539 ${e.project} \u7684\u914D\u989D\uFF1A\u5BA1\u6279\u8BF7\u6C42\u6CA1\u6709\u62FF\u5230\u653E\u884C\u7ED3\u679C\uFF08${e.detail}\uFF09\uFF0C\u6309\u62D2\u7EDD\u5904\u7406\u3002**\u8FD9\u4E0D\u662F\u7528\u6237\u70B9\u4E86\u62D2\u7EDD** \u2014\u2014 \u8BF7\u6C42\u53EF\u80FD\u88AB\u53D6\u6D88\u3001\u4E2D\u65AD\uFF0C\u6216\u5BA1\u6279\u901A\u9053\u5F53\u65F6\u4E0D\u53EF\u7528\u3002\u5982\u5B9E\u8BF4\u660E\u53D1\u751F\u4E86\u4EC0\u4E48\u518D\u8BA9\u7528\u6237\u51B3\u5B9A\u8981\u4E0D\u8981\u91CD\u6765\u3002`:`\u4FEE\u6539 ${e.project} \u7684\u914D\u989D\u5931\u8D25\uFF1A${e.detail}
|
|
27
|
+
\uFF08\u914D\u989D\u662F\u6574\u4F53\u5199\u5165\u7684\uFF1A\u8FD9\u6B21\u8981\u4E48\u5168\u90E8\u751F\u6548\u3001\u8981\u4E48\u5168\u90FD\u6CA1\u751F\u6548\uFF0C\u4E0D\u4F1A\u53EA\u6539\u4E86\u4E00\u534A\u3002\u5982\u679C\u5E73\u53F0\u8BF4 DB \u5DF2\u66F4\u65B0\u800C\u96C6\u7FA4\u672A\u66F4\u65B0\uFF0C\u90A3\u662F\u7269\u5316\u88AB\u62E6\u4E0B\uFF0C\u9700\u8981\u8FD0\u7EF4\u4ECB\u5165\u3002\uFF09`}]},timeoutMs:12e4,async execute(t,e){let o=w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668"),i=b(e),s=e?.agent?.session?.id,u=E(r,i),n={action:"quota_set",target:t.project,via:"agent",sessionId:s,reason:t.reason};if((t.pools?.length??0)===0&&(t.storage?.length??0)===0)throw new Error("\u81F3\u5C11\u8981\u7ED9\u51FA\u4E00\u4E2A\u8981\u6539\u7684\u6C60\uFF1Apools\uFF08\u7B97\u529B\u6C60\uFF09\u6216 storage\uFF08\u5B58\u50A8\u6C60\uFF09");let a=await o.projectBySlug(i,t.project,e.signal),{next:p,lines:h}=H(a.quota,{pools:t.pools,storage:t.storage});if(h.length===0){let m=`${t.project} \u7684\u914D\u989D\u5DF2\u7ECF\u662F\u8FD9\u4E9B\u503C\u4E86\uFF0C\u6CA1\u6709\u53EF\u5199\u7684\u6539\u52A8\u3002`;return await g({...n,...await u,outcome:"skipped",error:m}),y({project:t.project,outcome:"skipped",changes:[],detail:m})}let k=r.get("approval");if(!k){let m=D;return await g({...n,...await u,outcome:"rejected",error:m}),y({project:t.project,outcome:"rejected",changes:h,detail:m})}let c=await k.request({agent:e.agent,toolName:"cluster_quota_set",callId:e.callId,reason:`\u4FEE\u6539 ${t.project} \u7684\u914D\u989D\uFF1A${h.join("\uFF1B")}\uFF08${t.reason}\uFF09`,signal:e.signal});if(c!=="allowed-once")return await g({...n,...await u,outcome:"rejected",error:c}),y({project:t.project,outcome:"rejected",changes:h,detail:c});try{return await o.setQuota(i,a.id,p,e.signal),await g({...n,...await u,outcome:"executed",details:{changes:h.join("\uFF1B")}}),y({project:t.project,outcome:"executed",changes:h})}catch(m){let R=m.code==="foreign_instance"?`${m.message}\uFF08\u96C6\u7FA4\u91CC\u7684 QuotaGrant / \u547D\u540D\u7A7A\u95F4\u5E26\u7740\u53E6\u4E00\u4E2A\u63A7\u5236\u9762\u677F\u5B9E\u4F8B\u7684\u6807\u8BB0\uFF0C\u672C\u5B9E\u4F8B\u4E0D\u5199\u4E5F\u4E0D\u5220\u3002\u8FD9\u9700\u8981\u8FD0\u7EF4\u5728\u5E73\u53F0\u4FA7\u5904\u7406\u5F52\u5C5E\uFF0C\u4E0D\u8981\u91CD\u8BD5\u3002\uFF09`:m.code==="empty_quota_grant_exists"?`${m.message}\uFF08\u8FD9\u6B21\u6539\u52A8\u628A\u7B97\u529B\u6C60\u6E05\u7A7A\u4E86\uFF0C\u800C\u96C6\u7FA4\u91CC\u5DF2\u7ECF\u6709 QuotaGrant\u3002\u5E73\u53F0\u4E0D\u4F1A\u9690\u5F0F\u5220\u9664\u5B83\u2014\u2014\u5220\u6389\u4F1A\u8BA9\u8BE5\u9879\u76EE\u5185\u6240\u6709\u65B0\u8D1F\u8F7D\u62A5 QuotaGrantNotFound\u3002\u8981\u64A4\u9500\u7B97\u529B\uFF0C\u8BF7\u4FDD\u7559\u6C60\u5E76\u628A\u4FDD\u5E95/\u4E0A\u9650\u8C03\u5230 0\uFF1B\u8981\u8FDE\u5BF9\u8C61\u4E00\u8D77\u6E05\u6389\uFF0C\u53EA\u80FD\u5220\u9664\u9879\u76EE\u3002\uFF09`:m.status===403?`${m.message}\uFF08\u4FEE\u6539\u914D\u989D\u9700\u8981\u7CFB\u7EDF\u7EA7 projects:quota \u6743\u9650\uFF0C\u5F53\u524D\u8EAB\u4EFD\u6CA1\u6709\u3002\u9879\u76EE\u7EA7 projects:update \u53EA\u591F\u6539\u540D\u5B57\u548C\u63CF\u8FF0\u3002\u8BF7\u8BA9\u5E73\u53F0\u7BA1\u7406\u5458\u64CD\u4F5C\uFF0C\u6216\u5148\u6388\u4E88\u6743\u9650\u3002\uFF09`:m.message;return await g({...n,...await u,outcome:"failed",error:R}),y({project:t.project,outcome:"failed",changes:h,detail:R})}}})),r.tools.register(f({name:"cluster_report",description:'Produce the periodic cluster report: what CHANGED since the last report. This is not a status snapshot \u2014 `cluster_usage` and `cluster_anomalies` answer "how are things now". A report answers "what moved", which is the only thing worth delivering on a schedule: a digest that repeats the same three findings every morning gets skipped, including the morning a fourth one appears. Calling this ADVANCES the baseline, so the next report diffs against this moment; pass preview to look without consuming the delta. Lead your summary with what appeared and what resolved, and say plainly when nothing moved \u2014 `quiet` is a real and useful outcome, and one line is the right length for it.',parameters:{preview:{type:"boolean",description:"Compute the report without saving it as the new baseline. Use when trying the report out, so the first real one still has a delta to show."}},output:{schema:{type:"object",additionalProperties:!1,properties:{at:{type:"string",required:!0},since:{type:"string"},quiet:{type:"boolean",required:!0},saved:{type:"boolean",required:!0},metricsAvailable:{type:"boolean",required:!0},cards:{type:"object",required:!0,additionalProperties:!1,properties:{total:{type:"integer",required:!0},allocated:{type:"integer",required:!0},available:{type:"integer",required:!0},idleAllocated:{type:"integer",required:!0},allocatedDelta:{type:"integer"},idleAllocatedDelta:{type:"integer"}}},workloads:{type:"object",required:!0,additionalProperties:!1,properties:{running:{type:"integer",required:!0},queued:{type:"integer",required:!0},failed:{type:"integer",required:!0},runningDelta:{type:"integer"},queuedDelta:{type:"integer"},failedDelta:{type:"integer"}}},appeared:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{severity:{type:"string",required:!0},kind:{type:"string",required:!0},subject:{type:"string",required:!0},detail:{type:"string",required:!0}}}},resolved:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{severity:{type:"string",required:!0},kind:{type:"string",required:!0},subject:{type:"string",required:!0},detail:{type:"string",required:!0}}}},ongoing:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{severity:{type:"string",required:!0},kind:{type:"string",required:!0},subject:{type:"string",required:!0},detail:{type:"string",required:!0}}}},projects:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{slug:{type:"string",required:!0},quotaCards:{type:"integer",required:!0},usedCards:{type:"integer",required:!0},running:{type:"integer",required:!0},idleHeld:{type:"integer",required:!0},usedDelta:{type:"integer"},idleHeldDelta:{type:"integer"}}}}}},render:(t,e)=>{let o=n=>n===void 0?"":n===0?"\uFF08\u6301\u5E73\uFF09":n>0?`\uFF08+${n}\uFF09`:`\uFF08${n}\uFF09`,i=(n,a)=>a.length===0?"":`
|
|
28
|
+
|
|
29
|
+
${n}
|
|
30
|
+
${a.map(p=>`- ${p.subject}\uFF1A${p.detail}`).join(`
|
|
31
|
+
`)}`,s=e.since?`\u81EA ${e.since} \u4EE5\u6765`:"\u9996\u6B21\u62A5\u544A\uFF08\u5C1A\u65E0\u5BF9\u6BD4\u57FA\u7EBF\uFF09";if(e.quiet)return[{type:"text",text:`${s}\uFF1A\u65E0\u53D8\u5316\u3002\u5361 ${e.cards.allocated}/${e.cards.total} \u5DF2\u5206\u914D\uFF0C\u5176\u4E2D ${e.cards.idleAllocated} \u5F20\u95F2\u7F6E\uFF1B\u8D1F\u8F7D \u8FD0\u884C ${e.workloads.running}\u3001\u6392\u961F ${e.workloads.queued}\u3001\u5931\u8D25 ${e.workloads.failed}\u3002${e.ongoing.length} \u9879\u5F02\u5E38\u4ECD\u5728\u3002`}];let u=e.projects.slice(0,6).map(n=>`- ${n.slug}\uFF1A\u6301\u6709 ${n.usedCards}/${n.quotaCards} \u5361${o(n.usedDelta)}\uFF0C\u5176\u4E2D ${n.idleHeld} \u5F20\u672A\u5728\u8DD1${o(n.idleHeldDelta)}`).join(`
|
|
32
|
+
`);return[{type:"text",text:`${s}
|
|
33
|
+
\u5361\uFF1A\u5DF2\u5206\u914D ${e.cards.allocated}${o(e.cards.allocatedDelta)}\uFF0C\u95F2\u7F6E\u5206\u914D ${e.cards.idleAllocated}${o(e.cards.idleAllocatedDelta)}\uFF0C\u7A7A\u95F2 ${e.cards.available}\u3002
|
|
34
|
+
\u8D1F\u8F7D\uFF1A\u8FD0\u884C ${e.workloads.running}${o(e.workloads.runningDelta)}\uFF0C\u6392\u961F ${e.workloads.queued}${o(e.workloads.queuedDelta)}\uFF0C\u5931\u8D25 ${e.workloads.failed}${o(e.workloads.failedDelta)}\u3002`+i("\u65B0\u589E\u5F02\u5E38\uFF1A",e.appeared)+i("\u5DF2\u6D88\u5931\uFF1A",e.resolved)+i("\u4ECD\u5728\uFF1A",e.ongoing)+(u?`
|
|
35
|
+
|
|
36
|
+
\u9879\u76EE\u5360\u7528\u53D8\u5316\uFF1A
|
|
37
|
+
${u}`:"")+(e.metricsAvailable?"":`
|
|
38
|
+
|
|
39
|
+
\u6CE8\u610F\uFF1A\u6307\u6807\u6E90\u672A\u63A5\u5165\uFF0C\u5229\u7528\u7387\u7C7B\u5224\u65AD\u6B64\u65F6\u4E0D\u53EF\u4FE1\u3002`)+(e.saved?"":`
|
|
40
|
+
|
|
41
|
+
\uFF08\u9884\u89C8\uFF0C\u672A\u63A8\u8FDB\u57FA\u7EBF\uFF09`)}]}},async execute(t,e){let o=w(r,"runnpu","RunNPU \u8FDE\u63A5\u5668"),i=b(e),[s,u,n]=await Promise.all([o.query(i,"overview",{},e.signal),o.query(i,"workloads",{limit:100},e.signal),B()]),a=x(s,u,n),p=t.preview!==!0;return p&&await Q(a),y({at:a.at,since:a.since,quiet:a.quiet,saved:p,metricsAvailable:a.metricsAvailable,cards:a.cards,workloads:a.workloads,appeared:a.changes.appeared,resolved:a.changes.resolved,ongoing:a.changes.ongoing,projects:a.projects.map(h=>({slug:h.slug,quotaCards:h.quotaCards,usedCards:h.usedCards,running:h.running,idleHeld:h.idleHeld,usedDelta:h.usedDelta,idleHeldDelta:h.idleHeldDelta}))})}})),r.tools.register(f({name:"cluster_followups",description:'Offer the user 2\u20134 one-click follow-up questions, shown as buttons above the composer. Call this at the END of a turn, after you have answered \u2014 it replaces whatever was offered before. Propose what THIS answer actually raises: name the workload, node or project you just found something about ("\u770B test-project/svc-41hl \u7684\u65E5\u5FD7", "\u753B\u4E00\u4E0B 910b1 \u6700\u8FD1\u7684 CPU \u66F2\u7EBF"), or the obvious next decision ("\u8981\u4E0D\u8981\u5220\u6389\u91CD\u5EFA\uFF1F"). Do not offer generic questions the user could have asked before reading your answer, and do not offer something you already answered. Skip the call entirely when nothing genuinely follows \u2014 an empty row is better than four questions nobody wants, and the user can always type.',parameters:{questions:{type:"array",required:!0,items:{type:"string"},description:"The follow-ups, most useful first. At most four; each should read as a question the user would actually send."}},output:{schema:{type:"object",additionalProperties:!1,properties:{accepted:{type:"array",required:!0,items:{type:"string"}},dropped:{type:"integer",required:!0}}},render:(t,e)=>[{type:"text",text:e.accepted.length===0?"\u6CA1\u6709\u63D0\u4F9B\u8FFD\u95EE\u5EFA\u8BAE\u3002":`\u5DF2\u5728\u8F93\u5165\u6846\u4E0A\u65B9\u7ED9\u51FA ${e.accepted.length} \u4E2A\u8FFD\u95EE\u5EFA\u8BAE\u3002`+(e.dropped>0?`\uFF08\u8D85\u8FC7 4 \u4E2A\u4E0A\u9650\uFF0C\u4E22\u5F03\u4E86 ${e.dropped} \u4E2A\uFF09`:"")}]},async execute(t,e){let o=w(r,"runnpuFollowups","\u8FFD\u95EE\u5EFA\u8BAE"),i=e?.agent?.session?.id;if(!i)throw new Error("\u8FD9\u4E2A\u5DE5\u5177\u9700\u8981\u4E00\u4E2A\u4F1A\u8BDD");let s=t.questions.map(n=>String(n).trim()).filter(n=>n.length>0&&n.length<=60),u=s.slice(0,Y);return o.set(i,u),y({accepted:u,dropped:Math.max(0,s.length-u.length)})}})),r.tools.register(f({name:"cluster_unpin",description:"Remove one pinned chart from the right column. Only on the user's say-so: the right column is theirs, and a chart you find redundant may be the one they check every morning. Get the id from cluster_pins.",parameters:{id:{type:"string",required:!0,description:"Pin id, from cluster_pins."}},output:{schema:{type:"object",additionalProperties:!1,properties:{id:{type:"string",required:!0},removed:{type:"boolean",required:!0}}},render:(t,e)=>[{type:"text",text:e.removed?"\u5DF2\u4ECE\u53F3\u680F\u79FB\u9664\u3002":"\u53F3\u680F\u91CC\u6CA1\u6709\u8FD9\u4E2A id \u5BF9\u5E94\u7684\u56FE\u8868\u3002"}]},async execute(t){let e=w(r,"runnpuPins","\u56FE\u8868\u56FA\u5B9A\u533A");return{id:t.id,removed:await e.remove(t.id)}}}))}export{ge as apply,me as inject,ye as name};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{a as L,b as x,c as I,d as N,e as O,f as P,g as C,h as B,i as W,j as K,l as $,m as j,n as R}from"./chunk-YKASEMP3.mjs";import{a as y,b,e as v,g as h,i as g,j as A,k as S,l as E}from"./chunk-JUBIWGKW.mjs";import{a as J}from"./chunk-JZXLCA2E.mjs";var H={};J(H,{SwanLabError:()=>p,api:()=>d,check:()=>_,extractJson:()=>U,findRunByName:()=>V,keysOf:()=>ne,metricSeries:()=>re,probe:()=>F,projectList:()=>Q,runInfo:()=>z,runKeys:()=>ee,runList:()=>X,runLogs:()=>ae,runMetrics:()=>te,runSummary:()=>Z,runWebUrl:()=>ie,selfHostedInfo:()=>G,userInfo:()=>D,usernameOf:()=>Y,version:()=>oe,workloadEnv:()=>k});import{execFile as T}from"node:child_process";var p=class extends Error{constructor(n,t,o){super(t),this.name="SwanLabError",this.code=n,this.details=o}};function d(e,n,t={}){let o=e.cli||"swanlab",u=["api",...n];return e.apiHost&&u.push("--host",e.apiHost),e.apiKey&&u.push("--api-key",e.apiKey),new Promise((i,r)=>{T(o,u,{signal:t.signal,timeout:t.timeoutMs??3e4,maxBuffer:32*1024*1024,env:{...process.env,NO_COLOR:"1"}},(s,a,c)=>{if(s){if(s.code==="ENOENT"){r(new p("cli_missing",`\`${o}\` \u4E0D\u5728 PATH \u4E0A\uFF1B\u7528 \`pip install -U swanlab\` \u5B89\u88C5`));return}let l=String(c||s.message).trim().slice(0,2e3);if(/No such command ['"]api['"]/i.test(l)){r(new p("cli_too_old","\u5F53\u524D swanlab CLI \u6CA1\u6709 `api` \u5B50\u547D\u4EE4\uFF08\u8BE5\u547D\u4EE4\u7EC4\u81EA SDK 0.9.0 \u8D77\u63D0\u4F9B\uFF09\u3002\u9762\u677F\u53D6\u6570\u4E0E\u5B9E\u9A8C\u67E5\u8BE2\u90FD\u4F9D\u8D56\u5B83\uFF1A\u8BF7 `pip install -U swanlab` \u5347\u7EA7\u5230 0.9.0 \u4EE5\u4E0A\u3002"));return}r(new p("cli_failed",`swanlab api ${n[0]??""} failed: ${l}`,{args:n[0]}));return}let f;try{f=U(a)}catch{r(new p("bad_output","swanlab api returned output that is not JSON",{head:a.slice(0,500)}));return}if(f&&typeof f=="object"&&f.ok===!1){r(new p("api_error",String(f.errmsg||"SwanLab API request failed").slice(0,2e3)));return}i(f&&typeof f=="object"&&f.ok===!0&&"data"in f?f.data:f)})})}function U(e){let n=String(e??"").trim();if(!n)return;try{return JSON.parse(n)}catch{}let t=n.split(`
|
|
2
|
+
`);for(let o=0;o<t.length;o+=1){let u=t[o].trimStart()[0];if(u!=="{"&&u!=="[")continue;let i=t.slice(o).join(`
|
|
3
|
+
`);try{return JSON.parse(i)}catch{}}throw new Error("no JSON document in output")}async function D(e,n){return await d(e,["user","info"],n)}async function G(e,n){return await d(e,["self-hosted","info"],n)}async function Q(e,{workspace:n,all:t=!0}={},o){let u=["project","list"];return n&&u.push("--workspace",n),t&&u.push("--all"),await d(e,u,o)}async function X(e,n,t){return await d(e,["run","list",n],t)}function Y(e){for(let n of[e?.username,e?.name,e?.user?.username,e?.user?.name])if(typeof n=="string"&&n.trim()!=="")return n.trim()}function V(e,n){let t=typeof n=="string"?n.trim():"";if(t==="")return;let o=Array.isArray(e?.list)?e.list:Array.isArray(e)?e:[],u=[];for(let r of o){if(r?.name!==t)continue;let s=typeof r?.run_id=="string"?r.run_id.trim():"";if(s==="")continue;let a=Date.parse(r?.created_at??r?.createdAt??"");u.push({row:r,runId:s,at:Number.isFinite(a)?a:Number.NEGATIVE_INFINITY})}if(u.length===0)return;let i=u[0];for(let r of u.slice(1))r.at>i.at&&(i=r);return{runId:i.runId,state:typeof i.row?.state=="string"?i.row.state:void 0,ambiguous:u.length>1,matches:u.length}}async function z(e,n,t){return await d(e,["run","info",n],t)}async function Z(e,n,t){return await d(e,["run","summary",n],t)}async function ee(e,n,t){try{return{source:"series",data:await d(e,["run","series",n],t)}}catch(o){if(o.code==="cli_missing")throw o;return{source:"columns",data:await d(e,["run","columns",n],t)}}}function ne(e){let n=e?.source!==void 0&&e?.data!==void 0?e.data:e,t=Array.isArray(n?.keys)?n.keys:Array.isArray(n?.list)?n.list:Array.isArray(n)?n:[],o=[],u=new Set;for(let i of t){let r=typeof i=="string"?i:typeof i?.key=="string"?i.key:void 0;if(r===void 0)continue;let s=r.trim();s===""||u.has(s)||(u.add(s),o.push(s))}return o}async function te(e,n,t,o){if(!t?.length)throw new p("no_keys","runMetrics needs an explicit key list");return await d(e,["run","metrics",n,"--keys",t.join(",")],o)}function re(e){let n={};for(let t of Array.isArray(e?.list)?e.list:[])typeof t?.key=="string"&&(n[t.key]=(Array.isArray(t.metrics)?t.metrics:[]).filter(o=>Number.isFinite(o?.timestamp??o?.index)).map(o=>[o.timestamp??o.index,typeof o?.data=="number"&&Number.isFinite(o.data)?o.data:null]));return n}function ie(e,n){if(!e||!n)return;let t=String(n).split("/");if(t.length!==3||t.some(r=>r===""))return;let[o,u,i]=t;return`${e.replace(/\/+$/,"")}/@${o}/${u}/runs/${i}/chart`}async function ae(e,n,t){return await d(e,["run","logs",n],t)}function k(e,n={}){let t={};return e.apiKey&&(t.SWANLAB_API_KEY=e.apiKey),e.apiHost&&(t.SWANLAB_API_HOST=e.apiHost),e.webHost&&(t.SWANLAB_WEB_HOST=e.webHost),n.name&&(t.SWANLAB_NAME=n.name,t.SWANLAB_EXP_NAME=n.name,t.SWANLAB_EXPERIMENT_NAME=n.name),n.description&&(t.SWANLAB_DESCRIPTION=n.description),t}async function oe(e,n){try{let t=e.cli||"swanlab";return(await new Promise((u,i)=>{T(t,["--version"],{timeout:1e4,signal:n?.signal},(r,s)=>r?i(r):u(s))})).trim()}catch{return}}async function F(e,n){if(!e.apiHost)return{reachable:!1,reason:"SwanLab API \u5730\u5740\u672A\u914D\u7F6E"};let t=new AbortController,o=setTimeout(()=>t.abort(),n?.timeoutMs??8e3);try{return await fetch(e.apiHost,{method:"HEAD",signal:t.signal,redirect:"manual"}),{reachable:!0}}catch(u){return u?.name==="AbortError"?{reachable:!1,reason:`\u8FDE\u63A5 ${e.apiHost} \u8D85\u65F6`,code:"timeout"}:{reachable:!1,reason:`\u65E0\u6CD5\u8FDE\u901A ${e.apiHost}\uFF1A${u?.message??u}`,code:"unreachable"}}finally{clearTimeout(o)}}async function _(e,n){let t=await F(e,n);if(!t.reachable)return{...t,credentials:"unknown"};if(!e.apiKey)return{reachable:!0,credentials:"missing"};try{return await D(e,{...n,timeoutMs:15e3}),{reachable:!0,credentials:"valid"}}catch(o){return o.code==="cli_too_old"||o.code==="cli_missing"?{reachable:!0,credentials:"unverifiable",reason:o.message,code:o.code}:{reachable:!0,credentials:"invalid",reason:o.message,code:o.code}}}var fe="runnpu-connectors",M={runnpu:{server:"",tokenEnv:b.runnpuToken},swanlab:{apiHost:y.apiHost,webHost:y.webHost,apiKeyEnv:b.swanlabApiKey,cli:"swanlab"}};function se(e){return{runnpu:{...M.runnpu,...e?.runnpu??{}},swanlab:{...M.swanlab,...e?.swanlab??{}}}}function le(e,n){let t=se(n),o={api:R,Error:L,async connection(i){let r=await S(e,t,i);return{server:r.runnpu.server,token:r.runnpu.token}},async describe(i){return await A(e,t,i)},async health(i,r){let s=await this.connection(i);if(!s.server)return{state:"unconfigured",reason:"\u5C1A\u672A\u586B\u5199 RunNPU \u767B\u5F55\u5730\u5740"};try{let a=await I(s,r);if(!s.token)return{state:"unauthenticated",reason:"\u5DF2\u8FDE\u901A\u5E73\u53F0\uFF0C\u4F46\u5C1A\u672A\u767B\u5F55",platform:a};let c=await N(s,r);return{state:"connected",platform:a,identity:c}}catch(a){return{state:a.code==="unreachable"?"unreachable":"error",reason:a.message,code:a.code}}},async identity(i,r){try{let s=await this.connection(i);if(!s.server||!s.token)return;let a=(await N(s,r))?.user;if(!a)return;let c={...a.id===void 0?{}:{id:String(a.id)},...a.login===void 0?{}:{login:String(a.login)},...a.display_name===void 0?{}:{displayName:String(a.display_name)}};return Object.keys(c).length===0?void 0:c}catch{return}},async projectBySlug(i,r,s){let a=await this.connection(i);if(!a.server||!a.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55");let c=await B(a,s),f=Array.isArray(c?.items)?c.items:[],l=f.find(m=>m?.slug===r||m?.id===r);if(!l){let m=f.map(q=>q?.slug).filter(Boolean).join(" / ");throw new Error(`\u6CA1\u6709\u540D\u4E3A ${r} \u7684\u9879\u76EE${m?`\u3002\u53EF\u9009\uFF1A${m}`:"\uFF08\u9879\u76EE\u5217\u8868\u4E3A\u7A7A\uFF09"}`)}let w=await W(a,l.id,s);return{id:l.id,slug:l.slug??r,name:w?.name??l.name,quota:w?.quota??l.quota}},async setQuota(i,r,s,a){let c=await this.connection(i);if(!c.server||!c.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55");return await K(c,r,{quota:s},a)},async login({server:i,email:r,password:s,scope:a="global",cwd:c}){let{token:f,me:l}=await x(i,r,s),w=await g(e,b.runnpuToken,f,{scope:a,cwd:c});return a==="project"&&c?await h(c,{runnpu:{server:i}}):await E({runnpu:{server:i}}),{stored:w,identity:l}},async act(i,r,s,a,c){let f=await this.connection(i);if(!f.server||!f.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55");if(r==="stop")return await O(f,s,a,c);if(r==="start")return await P(f,s,a,c);if(r==="delete")return await C(f,s,a,c);throw new Error(`\u4E0D\u652F\u6301\u7684\u64CD\u4F5C\uFF1A${r}`)},async query(i,r,s,a){let c=await this.connection(i);return await j(c,r,s,a)},queries:Object.fromEntries(Object.entries($).map(([i,r])=>[i,{id:i,label:r.label}]))},u={api:H,Error:p,async connection(i){let r=await S(e,t,i);return{mode:r.swanlab.mode,apiHost:r.swanlab.apiHost,webHost:r.swanlab.webHost,apiKey:r.swanlab.apiKey,cli:t.swanlab.cli}},async describe(i){return(await A(e,t,i)).swanlab},async health(i,r){let s=await this.connection(i);if(!s.apiHost)return{state:"unconfigured",reason:"\u5C1A\u672A\u914D\u7F6E SwanLab \u5730\u5740"};if(!s.apiKey)return{state:"unauthenticated",reason:"\u5C1A\u672A\u914D\u7F6E SwanLab API Key"};let a=await _(s,{signal:r}),c={mode:s.mode,apiHost:s.apiHost,webHost:s.webHost};return a.reachable?a.credentials==="invalid"?{state:"error",reason:a.reason,code:a.code,...c}:a.credentials==="unverifiable"?{state:"degraded",reason:a.reason,code:a.code,...c}:{state:"connected",...c}:{state:"unreachable",reason:a.reason,code:a.code,...c}},async configure({mode:i,apiHost:r,webHost:s,apiKey:a,scope:c="global",cwd:f}){let l=i==="cloud"?{mode:i,apiHost:y.apiHost,webHost:y.webHost}:{mode:"self-hosted",apiHost:r,webHost:s||r};if(l.mode==="self-hosted"&&!l.apiHost)throw new Error("\u79C1\u6709\u90E8\u7F72\u5FC5\u987B\u586B\u5199 API \u5730\u5740");if(c==="project"&&f){let w=(await v(f)).config?.swanlab;await h(f,{swanlab:{...w,...l}})}else await E({swanlab:l});return a&&await g(e,b.swanlabApiKey,a,{scope:c,cwd:f}),l},async workloadEnv(i,r){return k(await this.connection(i),r)}};e.effect(()=>e.provide("runnpu",o),"runnpu-connectors: runnpu"),e.effect(()=>e.provide("swanlab",u),"runnpu-connectors: swanlab")}export{le as apply,fe as name};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import"./chunk-JZXLCA2E.mjs";import{delimiter as zn,join as T}from"node:path";import{execFile as pn}from"node:child_process";import{createHash as fn}from"node:crypto";import{access as dn,chmod as hn,copyFile as mn,mkdir as B,mkdtemp as wn,readFile as $n,readdir as gn,rm as yn,stat as U,writeFile as bn}from"node:fs/promises";import{constants as xn}from"node:fs";import{homedir as vn,tmpdir as kn}from"node:os";import{delimiter as j,dirname as En,join as p,resolve as y}from"node:path";import{fileURLToPath as Sn}from"node:url";import{promisify as Pn}from"node:util";var L=Pn(pn),Rn=y(En(Sn(import.meta.url)),"..");async function P(n=Rn){let r=JSON.parse(await $n(p(n,"package.json"),"utf8")).cliDeps;if(!r?.runnpu?.version||!r?.swanlab?.min)throw new Error("package.json cliDeps is missing the runnpu / swanlab pins");return r}function R(n=process.platform,e=process.arch){let r={darwin:"darwin",linux:"linux",win32:"windows"}[n],i={arm64:"arm64",x64:"amd64"}[e];if(!r||!i)throw new Error(`no runnpu build for ${n}/${e} (have darwin/linux/windows \xD7 arm64/amd64)`);return{os:r,arch:i,ext:r==="windows"?"zip":"tar.gz",exe:r==="windows"?".exe":""}}function I(n){return String(n).replace(/^v/,"")}function F(n,e){return`runnpu_${I(n)}_${e.os}_${e.arch}.${e.ext}`}function x(n){return`${n.releaseBase.replace(/\/+$/,"")}/${n.tag??n.version}`}function A(n,e){return`${x(n)}/${F(n.version,e)}`}function An(n){return`${x(n)}/checksums.txt`}function _n(n,e){for(let r of String(n).split(`
|
|
3
|
+
`)){let i=r.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);if(i&&i[2].trim()===e)return i[1].toLowerCase()}}function K(n){let e=String(n).match(/runnpu version\s+(\S+)/);return e?e[1]:void 0}function z(n,e){if(!n||!e)return!1;let r=i=>I(String(i).replace(/-dirty$/,""));return r(n)===r(e)}function Dn(n){let e=String(n).match(/(\d+\.\d+\.\d+)/);return e?e[1]:void 0}function Hn(n,e){let r=String(n).split(".").map(Number),i=String(e).split(".").map(Number);for(let o=0;o<Math.max(r.length,i.length);o++){let t=(r[o]??0)-(i[o]??0);if(t!==0)return t}return 0}async function S(n,e=process.env){let r=process.platform==="win32"?(e.PATHEXT??".EXE;.CMD;.BAT").split(";"):[""];for(let i of(e.PATH??"").split(j).filter(Boolean))for(let o of r){let t=p(i,n+o);try{if(await dn(t,xn.X_OK),(await U(t)).isFile())return t}catch{}}}async function b(n,e,r=process.env){try{let{stdout:i,stderr:o}=await L(n,e,{env:r,timeout:15e3});return{ok:!0,output:`${i}${o}`}}catch(i){let o=i;return{ok:!1,output:`${o.stdout??""}${o.stderr??""}${o.message??""}`}}}async function V(n,e=process.env){let r=[],i=await S("runnpu",e),o=i?K((await b(i,["--version"],e)).output):void 0,t=z(o,n.runnpu.version);r.push({name:"runnpu",path:i,version:o,want:n.runnpu.version,ok:t,detail:i?o?t?"matches the vendored runnpu skill":`build ${o} does not match the skill (${n.runnpu.version}) \u2014 parameters may disagree`:"`runnpu --version` did not answer":"not on PATH"});let s=await S("swanlab",e),a=s?Dn((await b(s,["--version"],e)).output):void 0,u=!!a&&Hn(a,n.swanlab.min)>=0;return r.push({name:"swanlab",path:s,version:a,want:`>=${n.swanlab.min}`,ok:u,detail:s?a?u?"ok":`${a} is below ${n.swanlab.min} \u2014 \`swanlab api\` output contract differs`:"`swanlab --version` did not answer":"not on PATH (training mode needs it; compute mode does not)"}),r}function _(n=process.env){return n.RUNNPU_BIN_DIR||p(vn(),".local","bin")}async function M(n,e,r){let i={"user-agent":"dsh-runnpu"},o=e.GITHUB_TOKEN||e.GH_TOKEN;o&&/github\.com|githubusercontent\.com/.test(n)&&(i.authorization=`Bearer ${o}`);let t=await r(n,{headers:i,redirect:"follow"});if(!t.ok)throw new Error(`download failed: ${t.status} ${t.statusText} \u2014 ${n}`);return Buffer.from(await t.arrayBuffer())}async function G(n,e){for(let r of await gn(n,{withFileTypes:!0})){let i=p(n,r.name);if(r.isDirectory()){let o=await G(i,e);if(o)return o}else if(r.name===`runnpu${e}`)return i}}async function J({pin:n,from:e,binDir:r,env:i=process.env,fetchImpl:o=fetch,plat:t=R()}){let s=r??_(i),a=await wn(p(kn(),"dsh-runnpu-cli-"));try{let u=e??A(n,t),f;if(/^https?:\/\//.test(u)){let h=F(n.version,t);f=p(a,h);let O=await M(u,i,o);if(!e){let cn=(await M(An(n),i,o)).toString("utf8"),E=_n(cn,h);if(!E)throw new Error(`checksums.txt of ${n.version} has no entry for ${h}`);let C=fn("sha256").update(O).digest("hex");if(C!==E)throw new Error(`sha256 mismatch for ${h}: got ${C}, checksums.txt says ${E}`)}await bn(f,O)}else u=y(u),f=(await U(u)).isDirectory()?void 0:u;let $=u;f&&($=p(a,"x"),await B($),await L("tar",["-xf",f,"-C",$]));let N=await G($,t.exe);if(!N)throw new Error(`no runnpu${t.exe} binary inside ${u}`);await B(s,{recursive:!0});let d=p(s,`runnpu${t.exe}`);await mn(N,d),await hn(d,493);let g=K((await b(d,["--version"],i)).output);if(g&&!z(g,n.version))throw new Error(`installed ${d} reports ${g}, expected ${n.version} \u2014 wrong archive for this skill`);return{path:d,version:g,source:u}}finally{await yn(a,{recursive:!0,force:!0})}}function W(n,e=process.env){let r=y(n);return(e.PATH??"").split(j).filter(Boolean).some(i=>y(i)===r)}async function Tn({pin:n,which:e,python:r}){let i=`${n.package}>=${n.min}`;if(r)return{tool:"python",argv:[r,"-m","pip","install","--upgrade",i]};let o=await e("uv");if(o)return{tool:"uv",argv:[o,"tool","install","--upgrade",i]};let t=await e("pipx");if(t)return{tool:"pipx",argv:[t,"install","--force",i]};let s=await e("python3")??await e("python");if(!s)throw new Error("no python3 on PATH \u2014 install Python \u2265 3.9 (or uv / pipx) first, then re-run");return{tool:"python",argv:[s,"-m","pip","install","--user","--upgrade",i]}}async function X({pin:n,env:e=process.env,python:r,dryRun:i=!1}){let o=await Tn({pin:n,which:s=>S(s,e),python:r});if(i)return{plan:o,ok:!0,output:""};let t=await b(o.argv[0],o.argv.slice(1),e);return{plan:o,...t}}import{cp as Nn,lstat as On,mkdir as Cn,readFile as Y,readlink as Bn,rm as Q,symlink as Mn,writeFile as D}from"node:fs/promises";import{existsSync as Z}from"node:fs";import{homedir as Un}from"node:os";import{dirname as jn,isAbsolute as Ln,join as c,resolve as nn}from"node:path";import{fileURLToPath as In}from"node:url";var l="dsh-runnpu",q=nn(jn(In(import.meta.url)),".."),m=Object.freeze([{id:"compute",display:"\u7B97\u529B\u7BA1\u7406\u6A21\u5F0F",from:c(q,"compute-mode","preset")},{id:"training",display:"\u8BAD\u7EC3\u6A21\u5F0F",from:c(q,"training-mode","preset")}]),en=`.installed-by-${l}`,Fn=[".installed-by-dsh-runnpu-modes"],Kn="__DSH_PRESET_ROOT__";function v(n=process.env){return n.DSH_HOME||c(Un(),".dsh")}function w(n){return c(n,".agent-presets")}function tn(n){if(n==="all")return[...m];let e=m.find(r=>r.id===n);if(!e)throw new Error(`unknown mode ${JSON.stringify(n??"")}: expected ${m.map(r=>r.id).join(" | ")} | all`);return[e]}async function k(n,e){let r;try{r=await On(n)}catch{return"absent"}return r.isSymbolicLink()?await Bn(n)===e?"our-link":"foreign":[en,...Fn].some(i=>Z(c(n,i)))?"ours":"foreign"}async function rn(n,{home:e=v(),force:r=!1,link:i=!1}={}){let o=w(e),t=c(o,n.id);await Cn(o,{recursive:!0});let s=await k(t,n.from);if(s==="foreign"&&!r)throw new Error(`refusing to replace ${t}: it exists and was not installed by ${l} (pass --force to replace)`);if(s!=="absent"&&await Q(t,{recursive:!0,force:!0}),i)return await Mn(n.from,t,"dir"),t;await Nn(n.from,t,{recursive:!0,filter:u=>!u.endsWith(".DS_Store")});let a=c(t,"agent.cordis.yml");return await D(a,(await Y(a,"utf8")).replaceAll(Kn,o),"utf8"),await D(c(t,en),`installed from ${n.from}
|
|
4
|
+
`,"utf8"),t}async function on(n,{home:e=v(),force:r=!1}={}){let i=c(w(e),n.id),o=await k(i,n.from);if(o==="absent")return"absent";if(o==="foreign"&&!r)throw new Error(`refusing to remove ${i}: it was not installed by ${l} (pass --force to remove)`);return await Q(i,{recursive:!0,force:!0}),"removed"}function H(n,e){return Ln(n)||n.startsWith(".")||n.includes("/")?nn(n):c(e,"profiles",n)}async function sn(n){let e=c(n,"package.json");if(!Z(e))throw new Error(`no package.json in ${n}: is this a dsh profile directory?`);let r=await Y(e,"utf8"),i=JSON.parse(r),o={...i.dsh?.profile??{}},t=Array.isArray(o.bundles)?[...o.bundles]:[];return t.includes(l)?!1:(t.push(l),i.dsh={...i.dsh??{},profile:{...o,bundles:t}},await D(e,JSON.stringify(i,null,2)+`
|
|
5
|
+
`,"utf8"),!0)}var an=`usage:
|
|
6
|
+
${l} install <compute|training|all> [--force] [--link] [--profile <name|dir>] [--with-cli]
|
|
7
|
+
${l} uninstall <compute|training|all> [--force]
|
|
8
|
+
${l} status
|
|
9
|
+
${l} doctor
|
|
10
|
+
${l} cli <runnpu|swanlab|all> [--from <archive|dir|url>] [--bin-dir <dir>] [--python <exe>] [--dry-run]
|
|
11
|
+
|
|
12
|
+
install copy the preset(s) into $DSH_HOME/.agent-presets/<id> (DSH_HOME defaults to ~/.dsh)
|
|
13
|
+
--force replace / remove a preset directory this package did not install
|
|
14
|
+
--profile also add "${l}" to <profile>/package.json \u2192 dsh.profile.bundles
|
|
15
|
+
--with-cli then install the host CLIs the mode needs (compute: runnpu; training: runnpu + swanlab)
|
|
16
|
+
--link dev only: symlink instead of copy (a symlinked preset is NOT discovered by dsh)
|
|
17
|
+
doctor check runnpu / swanlab on PATH against the versions this package was built for
|
|
18
|
+
cli install runnpu (pinned build, from the release or --from) and/or swanlab (uv > pipx > pip)
|
|
19
|
+
--from runnpu source: a local runnpu_<ver>_<os>_<arch>.tar.gz/.zip, an extracted directory, or a URL
|
|
20
|
+
--bin-dir where runnpu goes (default $RUNNPU_BIN_DIR or ~/.local/bin)
|
|
21
|
+
--python interpreter for swanlab (default: uv tool, else pipx, else python3 -m pip --user)
|
|
22
|
+
`;function Vn(n){let e={force:!1,link:!1,withCli:!1,dryRun:!1,profile:void 0,from:void 0,binDir:void 0,python:void 0},r={"--profile":"profile","--from":"from","--bin-dir":"binDir","--python":"python"},i=[];for(let o=0;o<n.length;o++){let t=n[o],s=t.indexOf("="),a=s>0?t.slice(0,s):t;if(a in r){let u=s>0?t.slice(s+1):n[++o];if(!u)throw new Error(`${a} needs a value`);e[r[a]]=u}else if(t==="--force")e.force=!0;else if(t==="--link")e.link=!0;else if(t==="--with-cli")e.withCli=!0;else if(t==="--dry-run")e.dryRun=!0;else if(t==="-h"||t==="--help")i.push("help");else{if(t.startsWith("-"))throw new Error(`unknown flag ${t}`);i.push(t)}}return{command:i[0],selector:i[1],flags:e}}async function Gn(n){console.log(`preset root: ${w(n)}`);for(let e of m){let r=T(w(n),e.id),i=await k(r,e.from),o={absent:"not installed",ours:"installed","our-link":"symlinked (dev only, not discovered)",foreign:"present, not ours"}[i];console.log(` ${e.id.padEnd(9)} ${e.display} ${o}`)}}async function un(n=process.env){let e=await P(),r=await V(e,n);for(let o of r){let t=o.ok?"\u2713":"\u2717",s=o.path?`${o.path}`:"(not found)";console.log(`${t} ${o.name.padEnd(8)} ${(o.version??"-").padEnd(16)} want ${o.want.padEnd(10)} ${s}`),o.ok||console.log(` ${o.detail}`)}let i=r.filter(o=>!o.ok);return i.length&&console.log(`
|
|
23
|
+
fix: ${l} cli ${i.length===2?"all":i[0].name}`),i.length===0}async function ln(n,e){let r=await P(),i=process.env;if(n.includes("runnpu")){let o=R(),t=e.binDir??_();i={...process.env,PATH:[t,process.env.PATH??""].filter(Boolean).join(zn)};let s=e.from??A(r.runnpu,o);if(e.dryRun)console.log(`runnpu: would install build ${r.runnpu.version} from ${s} \u2192 ${T(t,`runnpu${o.exe}`)}`);else{console.log(`runnpu: installing build ${r.runnpu.version} (${o.os}/${o.arch}) from ${s}`);try{let a=await J({pin:r.runnpu,from:e.from,binDir:t,plat:o});console.log(`\u2713 runnpu ${a.version??""} \u2192 ${a.path}`)}catch(a){let u=a instanceof Error?a.message:String(a);throw new Error(`${u}
|
|
24
|
+
The runnpu build must match the vendored skill (${r.runnpu.version}). If the release asset is not
|
|
25
|
+
reachable (offline cluster \u2192 use the archive the RunNPU console ships, a mirror \u2192 its URL),
|
|
26
|
+
pass it with --from <archive|dir|url>. Release: ${x(r.runnpu)}`)}W(t)||console.log(`
|
|
27
|
+
\u26A0 ${t} is not on PATH \u2014 add it, e.g.
|
|
28
|
+
fish: fish_add_path ${t}
|
|
29
|
+
bash/zsh: echo 'export PATH="${t}:$PATH"' >> ~/.profile`)}}if(n.includes("swanlab")){let o=await X({pin:r.swanlab,python:e.python,dryRun:e.dryRun}),t=o.plan.argv.join(" ");if(e.dryRun)console.log(`swanlab: would run (${o.plan.tool}): ${t}`);else if(o.ok)console.log(`\u2713 swanlab installed via ${o.plan.tool}: ${t}`);else throw new Error(`swanlab install failed (${t}):
|
|
30
|
+
${o.output}`)}e.dryRun||(console.log(""),await un(i))}async function Jn(){let{command:n,selector:e,flags:r}=Vn(process.argv.slice(2)),i=v();if(!n||n==="help"){console.log(an);return}if(n==="status"){await Gn(i);return}if(n==="doctor"){process.exitCode=await un()?0:1;return}if(n==="cli"){let t=e==="all"?["runnpu","swanlab"]:e==="runnpu"||e==="swanlab"?[e]:void 0;if(!t)throw new Error(`cli: expected runnpu | swanlab | all, got ${JSON.stringify(e??"")}`);await ln(t,r);return}if(n!=="install"&&n!=="uninstall")throw new Error(`unknown command ${n}
|
|
31
|
+
${an}`);let o=tn(e);if(n==="uninstall"){for(let t of o){let s=await on(t,{home:i,force:r.force});console.log(`${s==="removed"?"removed":"not installed"}: ${t.id} (${t.display})`)}return}for(let t of o){let s=await rn(t,{home:i,force:r.force,link:r.link});console.log(`installed ${t.id} (${t.display}) \u2192 ${s}${r.link?" (symlink, dev only)":""}`)}if(r.profile){let t=H(r.profile,i),s=await sn(t);console.log(`${s?"added":"already in"} dsh.profile.bundles: ${T(t,"package.json")}`)}if(r.withCli){let t=o.some(s=>s.id==="training");console.log(""),await ln(t?["runnpu","swanlab"]:["runnpu"],r)}if(r.profile){let t=H(r.profile,i);console.log(`
|
|
32
|
+
Next:
|
|
33
|
+
cd ${t} && pnpm install # if "${l}" is not yet in dependencies: pnpm add ${l}
|
|
34
|
+
restart dsh, then start a NEW session and pick the mode in the picker.${r.withCli?"":`
|
|
35
|
+
host CLIs: ${l} doctor (install with: ${l} cli all)`}
|
|
36
|
+
`);return}console.log(`
|
|
37
|
+
Next, mount the plugin bundle in the dsh profile you run (usually $DSH_HOME/profiles/web):
|
|
38
|
+
|
|
39
|
+
1. dependencies: pnpm add ${l} (already done if you are running this via npx from that profile)
|
|
40
|
+
2. dsh.profile.bundles: add "${l}" (or re-run with --profile <name|dir>)
|
|
41
|
+
3. host CLIs: ${l} doctor (install with: ${l} cli all)
|
|
42
|
+
4. restart dsh, then start a NEW session and pick the mode in the picker.
|
|
43
|
+
`)}Jn().catch(n=>{console.error(`${l}: ${n instanceof Error?n.message:String(n)}`),process.exit(1)});
|
package/dist/json.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a}from"./chunk-XQPU3AJN.mjs";import"./chunk-JZXLCA2E.mjs";export{a as lossless};
|
package/dist/pins.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as g,b as h,c as i,d as j,e as k,f as l,g as m,h as n,i as o,j as p,k as q}from"./chunk-XMZG2NP7.mjs";import{a,b,c,d,e,f}from"./chunk-JLZMOSQC.mjs";import"./chunk-JUBIWGKW.mjs";import"./chunk-JZXLCA2E.mjs";export{a as CHARTABLE_QUERIES,b as CHART_GROUPS,o as MAX_PINS,q as PinStore,n as RANGES,h as TABULAR_QUERIES,g as VIEW_KINDS,d as availableGroups,j as deriveView,c as groupsOf,m as isTimeView,f as lastOf,l as normalizeViewSpec,p as pinKey,i as rowsOf,e as toSeries,k as viewOf};
|
package/dist/series.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a,b,c,d,e,f}from"./chunk-JLZMOSQC.mjs";import"./chunk-JZXLCA2E.mjs";export{a as CHARTABLE_QUERIES,b as CHART_GROUPS,d as availableGroups,c as groupsOf,f as lastOf,e as toSeries};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as N,b as P,c as C,d as D,e as L,f as U}from"./chunk-KQOFBYGE.mjs";import{e as H,m as E,n as I,o as j,p as q}from"./chunk-JUBIWGKW.mjs";import"./chunk-JZXLCA2E.mjs";var pe="runnpu-training-node",we=["webServer"],J={apiPath:"/training-api",refreshMs:15e3},v=5,W=200,X=Object.freeze(["completed","failed","stopped","rejected"]);function T(e){return typeof e=="string"&&X.includes(e)}var Y=new Map([["submitting","submitting"],["queued","queued"],["pending","queued"],["admitted","queued"],["running","running"],["completed","completed"],["succeeded","completed"],["failed","failed"],["rejected","rejected"],["stopped","stopped"],["suspended","stopped"]]);function Q(e){if(typeof e=="string")return Y.get(e.trim().toLowerCase())}var V=15*6e4;function Z(e,p,n=V){if(T(e?.status))return;let i=Date.parse(e?.startedAt??"");if(Number.isFinite(i)&&!(p-i<n))return"stopped"}function K(e){return!e?.swanlabRun&&e?.swanlabUpload==="online"&&typeof e?.swanlabRunName=="string"&&e.swanlabRunName!==""}function ee(e){return!e||typeof e.seq!="number"?!1:!T(e.status)||K(e)}var G=200;async function te(e,p,n,i,f){if(!(!p.some(r=>!T(r.status)&&typeof r.workloadRef=="string")||!n)){f.workloads+=1;try{let r=await n.query(e,"workloads",{limit:G},i),u=Array.isArray(r?.items)?r.items:[];return{byRef:new Map(u.map(w=>[`${w?.project}/${w?.name}`,w])),complete:u.length<G}}catch{return}}}async function ne(e,p,n,i,f){if(!(!p.some(K)||!n))try{let l=(await H(e)).config?.swanlab?.project;if(typeof l!="string"||l.trim()==="")return;let r=await n.connection(e);if(!r?.apiHost||!r?.apiKey)return;let u=l.trim();if(!u.includes("/")){f.userInfo+=1;let w=n.api.usernameOf(await n.api.userInfo(r,{signal:i}));if(!w)return;u=`${w}/${u}`}return f.runList+=1,{path:u,data:await n.api.runList(r,u,{signal:i})}}catch{return}}async function se(e,{runnpu:p,swanlab:n,now:i=Date.now(),signal:f}={}){let l={workloads:0,runList:0,userInfo:0},r=[];if(!e)return{changed:r,requests:l};let u=(await E(e)).filter(ee);if(u.length===0)return{changed:r,requests:l};let[w,m]=await Promise.all([te(e,u,p,f,l),ne(e,u,n,f,l)]);for(let c of u){let h={};if(w&&!T(c.status)&&typeof c.workloadRef=="string"){let g=w.byRef.get(c.workloadRef),y=g?Q(g.status):w.complete?Z(c,i):void 0;y&&y!==c.status&&(h.status=y)}if(m&&K(c)){let g=n.api.findRunByName(m.data,c.swanlabRunName);g&&(h.swanlabRun=`${m.path}/${g.runId}`)}let b=h.status??c.status;if(T(b)&&!c.endedAt&&(h.endedAt=new Date(i).toISOString()),Object.keys(h).length!==0)try{let g=await I(e,c.seq,h);g.changed.length>0&&r.push({seq:c.seq,fields:g.changed})}catch{}}return{changed:r,requests:l}}var re="\u5B9E\u9A8C\u91CC\u8FD8\u6CA1\u6709\u4EFB\u4F55\u5173\u952E\u6307\u6807\u7684\u6570\u636E \u2014\u2014 \u53EF\u80FD\u662F\u8BAD\u7EC3\u521A\u8D77\u6B65\uFF0C\u4E5F\u53EF\u80FD\u662F .train/metrics.json \u7684\u952E\u5168\u90FD\u6CA1\u547D\u4E2D\u3002\u82E5\u6301\u7EED\u5982\u6B64\uFF0C\u8BA9 Agent \u7528 `swanlab api run series` \u590D\u6838\u5B9E\u9645\u7684\u6307\u6807\u540D\u5E76\u91CD\u65B0 training_metrics_set\u3002";function ae(e,p){let n=[];for(let f of Array.isArray(e)?e:[])typeof f=="string"&&f.trim()!==""&&!n.includes(f)&&n.push(f);let i=n.filter(f=>{let l=p?.[f];return!Array.isArray(l)||!l.some(r=>typeof r?.[1]=="number")});return i.length===0?{missing:i}:i.length===n.length?{missing:i,hint:re}:{missing:i,missingKeys:i}}var ie=5*6e4;async function oe({swanlab:e,conn:p,cwd:n,runPath:i,keys:f,stamp:l,memo:r,now:u=Date.now(),signal:w}){let m=`${i}
|
|
2
|
+
${l}`,c=r?.get(n);if(c?.token===m&&(c.settled||u-c.at<ie))return c.unknown?f.filter(b=>c.unknown.includes(b)):void 0;let h=b=>{if(r)for(r.delete(n),r.set(n,{token:m,at:u,...b});r.size>W;)r.delete(r.keys().next().value)};try{let b=await e.api.runKeys(p,i,{signal:w});if(b?.source!=="series"){h({settled:!1});return}let g=new Set(e.api.keysOf(b));if(g.size===0){h({settled:!1});return}let y=f.filter(s=>!g.has(s));return h({settled:y.length===0,unknown:y}),y}catch{h({settled:!1});return}}function ue(e){let p=Array.isArray(e?.keys)?e.keys.map(n=>n?.name).join(","):"";return`${e?.inferredAt??""}|${p}`}function ge(e,p){let n={...J,...p??{}},i=n.apiPath,f=e.get("webServer"),l=U({refresh:()=>Promise.all([...m.keys()].map(async s=>{let a=s||void 0;await se(a,{runnpu:e.get("runnpu"),swanlab:e.get("swanlab")}).catch(()=>{}),await g(a).catch(()=>{})})),intervalMs:n.refreshMs}),r=l.hub;N(e);let u=new Map,w=new Map,m=new Map,c=s=>{let a=e.get(s);if(!a){let t=new Error(`${s} \u8FDE\u63A5\u5668\u672A\u6302\u8F7D`);throw t.code="connector_missing",t.status=503,t}return a};function h(s){return s.find(a=>typeof a.swanlabRun=="string"&&a.swanlabRun.length>0)}async function b(s,a){if(!s)return{state:"no-workspace"};if(!await q(s))return{state:"not-training-project"};let[t,o]=await Promise.all([E(s),j(s)]),d=h(t),k=t.find(A=>A.decision==="promote"),R=t[0]?{seq:t[0].seq,total:t.length,hyperparams:t[0].hyperparams,template:t[0].template,status:t[0].status,startedAt:t[0].startedAt,previous:t[1]?.hyperparams,decision:t[0].decision,promotedSeq:k?.seq}:void 0;if(!o?.keys?.length)return{state:"no-metric-keys",version:R,hint:"\u5173\u952E\u6307\u6807\u5C1A\u672A\u4ECE\u8BAD\u7EC3\u4EE3\u7801\u4E2D\u5206\u6790\u51FA\u6765"};if(!d)return{state:"no-run",version:R,keys:o.keys.slice(0,v)};let M=o.keys.slice(0,v),S=c("swanlab"),_=await S.connection(s);if(!_.apiKey||!_.apiHost)return{state:"swanlab-unconfigured",version:R,keys:M,run:d.swanlabRun};try{let A=M.map(B=>B.name),z=await S.api.runMetrics(_,d.swanlabRun,A,{signal:a}),$=S.api.metricSeries(z),{missing:O,...F}=ae(A,$),x=O.length>0?await oe({swanlab:S,conn:_,cwd:s,runPath:d.swanlabRun,keys:O,stamp:ue(o),memo:w,signal:a}):void 0;return{state:"ok",version:R,keys:M,run:d.swanlabRun,runUrl:S.api.runWebUrl(_.webHost,d.swanlabRun),metrics:$,...F,...x?.length?{unknownKeys:x}:{},at:Date.now()}}catch(A){return{state:"error",version:R,keys:M,run:d.swanlabRun,reason:A.message,code:A.code}}}async function g(s){let a=await b(s),t=s??"";for(u.delete(t),u.set(t,a);u.size>W;)u.delete(u.keys().next().value);return r.publish("strip",{cwd:s,strip:a}),a}let y=(s,a,t)=>{e.effect(()=>f.register({kind:s,path:a,handler:t}),`runnpu-training-node: ${a}`)};y("exact",`${i}/state`,L({async GET(s,a,t){let o=t.searchParams.get("cwd")||void 0,d=e.get("runnpu"),k=e.get("swanlab"),[R,M,S,_,A]=await Promise.all([q(o),E(o),j(o),d?d.health(o):Promise.resolve({state:"unconfigured",reason:"\u8FDE\u63A5\u5668\u672A\u6302\u8F7D"}),k?k.health(o):Promise.resolve({state:"unconfigured",reason:"\u8FDE\u63A5\u5668\u672A\u6302\u8F7D"})]);P(a,200,{workspace:o,isTrainingProject:R,runnpu:_,swanlab:A,connection:d?await d.describe(o):void 0,metricKeys:S?.keys?.slice(0,v)??[],runs:M.slice(0,20),refreshMs:n.refreshMs})}})),y("exact",`${i}/swanlab`,L({async POST(s,a){let t=await D(s),o=t?.mode==="self-hosted"?"self-hosted":"cloud";if(o==="self-hosted"&&!t?.apiHost){C(a,422,"validation_failed","\u79C1\u6709\u90E8\u7F72\u5FC5\u987B\u586B\u5199 API \u5730\u5740");return}let d=c("swanlab"),k=await d.configure({mode:o,apiHost:t?.apiHost,webHost:t?.webHost,apiKey:t?.apiKey,scope:t?.scope==="project"?"project":"global",cwd:t?.cwd||void 0}),R=await d.health(t?.cwd||void 0);P(a,200,{saved:k,health:R})}})),y("exact",`${i}/metrics`,L({async GET(s,a,t){let o=t.searchParams.get("cwd")||void 0;P(a,200,await g(o))}})),y("exact",`${i}/events`,(s,a)=>{let o=new URL(s.url??"/","http://127.0.0.1").searchParams.get("cwd")||"";l.handler(s,a,{seed(d){m.set(o,(m.get(o)??0)+1);let k=u.get(o);k&&d("strip",{cwd:o||void 0,strip:k})},onClose(){let d=(m.get(o)??1)-1;d>0?m.set(o,d):m.delete(o)}})}),l.install(e,"runnpu-training-node")}export{ie as METRIC_KEY_RECHECK_MS,V as MISSING_WORKLOAD_GRACE_MS,re as NO_METRIC_DATA_HINT,X as TERMINAL_RUN_STATUSES,ge as apply,we as inject,T as isTerminalRunStatus,Q as ledgerStatusOf,ae as metricCoverage,pe as name,ee as needsLedgerSync,K as needsRunLookup,Z as statusForMissingWorkload,se as syncLedger,oe as unknownMetricKeys};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import{a as R}from"./chunk-XQPU3AJN.mjs";import{d as q,f as F,m as H}from"./chunk-JUBIWGKW.mjs";import"./chunk-JZXLCA2E.mjs";import{execFile as je,spawn as $e}from"node:child_process";import{mkdtemp as Ce,readFile as V,rm as Ue,writeFile as O,readdir as Ie}from"node:fs/promises";import{tmpdir as Fe}from"node:os";import{join as x}from"node:path";import{defineTool as T}from"@deepseek-ai/dsh-tools";function ve(r,n,e=5){let t=(r??[]).map(d=>d?.[1]).filter(d=>typeof d=="number"&&Number.isFinite(d));if(t.length===0)return{points:0,flags:["no-data"]};let i=t[t.length-1],a=n==="higher"?Math.max(...t):Math.min(...t),s={points:t.length,final:i,best:a,flags:[]},o=t.slice(-Math.max(2,e));if(o.length>=2&&o[0]!==0){let d=(o[o.length-1]-o[0])/Math.abs(o[0])*100;s.tailPct=d,Math.abs(d)<1?s.flags.push("plateau"):(n==="higher"?d>0:d<0)?s.flags.push("improving"):s.flags.push("worsening")}return s}function X(r,n,e={}){let t=e.tailK??5,i=r.map(s=>({label:s.label,perKey:Object.fromEntries(n.map(o=>[o.name,ve(s.series?.[o.name],C(o.direction),t)]))})),a=n.map(s=>{let o=C(s.direction),d=i.map(u=>({label:u.label,final:u.perKey[s.name].final})).filter(u=>typeof u.final=="number").sort((u,l)=>o==="higher"?l.final-u.final:u.final-l.final),p={key:s.name,direction:o};if(d.length===0)return p;let[c,y]=d;return p.winner=c.label,p.final=c.final,y&&y.final!==0&&(p.runnerUp=y.label,p.marginPct=Math.abs((c.final-y.final)/Math.abs(y.final)*100)),p});return{rows:i,winners:a}}function C(r){return r==="higher"?"higher":"lower"}import{createHash as Y}from"node:crypto";import{createReadStream as Re}from"node:fs";import{copyFile as Ne,mkdir as Q,readdir as qe,stat as z}from"node:fs/promises";import{dirname as Ee,join as E,resolve as Z}from"node:path";var S=class extends Error{constructor(n,e,t){super(e),this.name="UploadError",this.code=n,this.details=t}},ee=32222,_e="/workspace/code",Ae="/workspace/data",B=Object.freeze([Object.freeze({pattern:".train/secrets.json",why:"SwanLab / RunNPU \u51ED\u636E\uFF0C\u7EDD\u4E0D\u8FDB\u5BB9\u5668",group:"secret"}),Object.freeze({pattern:".train",why:"\u8BAD\u7EC3\u8D26\u672C\u662F\u672C\u5730\u7684\uFF0C\u5BB9\u5668\u91CC\u7528\u4E0D\u5230",group:"secret"})]),K=Object.freeze([Object.freeze({pattern:".git",why:"\u7248\u672C\u5E93\u5386\u53F2\uFF0C\u5BB9\u5668\u91CC\u8DD1\u8BAD\u7EC3\u7528\u4E0D\u5230",group:"noise"}),Object.freeze({pattern:"__pycache__",why:"\u5B57\u8282\u7801\u7F13\u5B58\uFF0C\u67B6\u6784\u76F8\u5173\u4E14\u4F1A\u88AB\u91CD\u65B0\u751F\u6210",group:"noise"}),Object.freeze({pattern:"*.pyc",why:"\u540C\u4E0A",group:"noise"}),Object.freeze({pattern:"node_modules",why:"\u524D\u7AEF\u4F9D\u8D56\uFF0C\u8BAD\u7EC3\u955C\u50CF\u91CC\u6CA1\u6709\u7528",group:"noise"}),Object.freeze({pattern:".DS_Store",why:"macOS \u76EE\u5F55\u5143\u6570\u636E",group:"noise"}),Object.freeze({pattern:"swanlog",why:"SwanLab \u672C\u5730\u65E5\u5FD7\u76EE\u5F55\uFF0C\u5BB9\u5668\u91CC\u4F1A\u91CD\u65B0\u751F\u6210",group:"noise"})]),ie=Object.freeze([Object.freeze({pattern:"*.ckpt",why:"\u6743\u91CD\u6587\u4EF6\uFF1A\u4EE3\u7801\u9053\u6BCF\u8F6E\u90FD\u540C\u6B65\uFF0C\u522B\u628A\u5B83\u5E26\u4E0A\uFF08\u7528 dataset:true \u5355\u72EC\u4F20\uFF09",group:"weight"}),Object.freeze({pattern:"*.safetensors",why:"\u540C\u4E0A",group:"weight"}),Object.freeze({pattern:"*.pt",why:"\u540C\u4E0A",group:"weight"}),Object.freeze({pattern:"*.pth",why:"\u540C\u4E0A",group:"weight"}),Object.freeze({pattern:"*.bin",why:"\u540C\u4E0A\uFF08HF \u6743\u91CD\u5206\u7247\u5E38\u7528\u8FD9\u4E2A\u540E\u7F00\uFF09",group:"weight"})]);function Oe(r){if(typeof r!="string")throw new S("bad_exclude",`extraExcludes \u53EA\u63A5\u53D7\u5B57\u7B26\u4E32\uFF0C\u6536\u5230 ${JSON.stringify(r)}`);let n=r.trim();if(n==="")throw new S("bad_exclude","extraExcludes \u91CC\u6709\u4E00\u4E2A\u7A7A\u6A21\u5F0F");if(/[\0\n\r]/.test(n))throw new S("bad_exclude",`\u6392\u9664\u6A21\u5F0F\u4E0D\u80FD\u5305\u542B\u6362\u884C\u6216 NUL\uFF1A${JSON.stringify(r)}`);if(/^[!+]/.test(n)||/^-\s/.test(n))throw new S("bad_exclude",`\u6392\u9664\u6A21\u5F0F ${JSON.stringify(n)} \u770B\u8D77\u6765\u662F\u4E00\u6761\u300C\u91CD\u65B0\u5305\u542B / \u6E05\u7A7A\u89C4\u5219\u300D\u7684 filter \u8BED\u6CD5\u3002extraExcludes \u53EA\u80FD\u589E\u52A0\u6392\u9664\u9879\uFF0C\u4E0D\u80FD\u64A4\u9500\u5F3A\u5236\u6392\u9664\uFF08\u5C24\u5176\u662F .train/secrets.json\uFF09\u3002`);return n}function se(r,n=[]){let t=(r==="data"?[...B,...K]:[...B,...K,...ie]).map(a=>({...a})),i=new Set(t.map(a=>a.pattern));for(let a of n??[]){let s=Oe(a);i.has(s)||(i.add(s),t.push({pattern:s,why:"\u8C03\u7528\u65B9\u8FFD\u52A0",group:"extra"}))}return t}var te=new Map;function re(r){let n="";for(let e of r)e==="*"?n+="[^/]*":e==="?"?n+="[^/]":n+=e.replace(/[.+^${}()|[\]\\]/g,"\\$&");return n}function Te(r){let n=te.get(r);if(n)return n;let e=r.trim(),t=e.endsWith("/");t&&(e=e.slice(0,-1)),e.startsWith("/")&&(e=e.slice(1));let i=e.includes("/"),a={anchored:i,dirOnly:t,regex:new RegExp(i?`^${re(e)}(?:/.*)?$`:`^${re(e)}$`)};return te.set(r,a),a}function De(r,n,e=!1){let t=r.split("/");for(let i of n){let a=Te(i);if(!(a.dirOnly&&!e)){if(a.anchored){if(a.regex.test(r))return i;continue}for(let s of t)if(a.regex.test(s))return i}}}async function oe(r,n,e={}){let t=e.maxFiles??2e5,i=[],a=new Map,s=[],o=0,d=0,p=new Map([...B,...K,...ie].map(u=>[u.pattern,u.why])),c=(u,l,m)=>{let w=a.get(u);w||(w={pattern:u,why:p.get(u)??"\u8C03\u7528\u65B9\u8FFD\u52A0",files:0,dirs:0,bytes:0},a.set(u,w)),l==="dir"?w.dirs+=1:(w.files+=1,w.bytes+=m)},y=[""];for(;y.length>0;){e.signal?.throwIfAborted();let u=y.shift(),l;try{l=await qe(E(r,u),{withFileTypes:!0})}catch(m){throw new S("scan_failed",`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55 ${E(r,u)}\uFF1A${m?.message??m}`)}l.sort((m,w)=>m.name<w.name?-1:m.name>w.name?1:0);for(let m of l){let w=u===""?m.name:`${u}/${m.name}`,h=m.isDirectory();if(!h&&!m.isFile()){o+=1,s.length<20&&s.push(w);continue}let $=De(w,n,h);if($!==void 0){if(h)c($,"dir",0);else{let k=0;try{k=(await z(E(r,w))).size}catch{}c($,"file",k)}continue}if(h){y.push(w);continue}let b;try{b=await z(E(r,w))}catch{o+=1,s.length<20&&s.push(w);continue}if(i.push({path:w,size:b.size}),d+=b.size,i.length>t)throw new S("too_many_files",`\u5F85\u4E0A\u4F20\u6587\u4EF6\u8D85\u8FC7 ${t} \u4E2A\uFF0C\u5DF2\u4E2D\u6B62\u3002\u8BF7\u628A source \u7F29\u5C0F\u5230\u771F\u6B63\u8981\u4F20\u7684\u76EE\u5F55\uFF0C\u6216\u7528 extraExcludes \u6392\u6389\u6570\u636E/\u4EA7\u7269\u76EE\u5F55\uFF08\u6570\u636E\u96C6\u8BF7\u7528 dataset: true \u5355\u72EC\u8D70\u6570\u636E\u901A\u9053\uFF09\u3002`,{maxFiles:t})}}return i.sort((u,l)=>u.path<l.path?-1:u.path>l.path?1:0),{files:i,bytes:d,excluded:[...a.values()],skipped:s,skippedCount:o}}async function ae(r,n,e){let t=[...n].sort((a,s)=>a.path<s.path?-1:a.path>s.path?1:0),i=Y("sha256");for(let a of t){e?.throwIfAborted();let s=Y("sha256");s.update(a.path,"utf8"),s.update(Buffer.from([0]));let o=Re(E(r,a.path));for await(let d of o)s.update(d);i.update(s.digest("hex")),i.update(`
|
|
2
|
+
`)}return`sha256:${i.digest("hex")}`}async function de(r,n,e,t){await Q(e,{recursive:!0});let i=new Set,a=0;for(let s of n){t?.throwIfAborted();let o=E(e,s.path),d=Ee(o);i.has(d)||(await Q(d,{recursive:!0}),i.add(d)),await Ne(E(r,s.path),o),a+=s.size}return{files:n.length,bytes:a}}function Le(r){let e=(Array.isArray(r?.items)?r.items:[]).filter(t=>typeof t?.internal_ip=="string"&&t.internal_ip!=="").sort((t,i)=>String(t.name)<String(i.name)?-1:String(t.name)>String(i.name)?1:0);return e.find(t=>t.status==="ready")??e[0]}function pe(r,n,e={}){let t=e.ref?`\u8D1F\u8F7D ${e.ref}`:"\u8FD9\u4E2A\u8D1F\u8F7D",i=r?.ssh;if(!i||i.enabled!==!0)throw new S("ssh_disabled",`${t} \u6CA1\u6709\u5F00\u542F SSH\uFF08\u5E73\u53F0\u62A5\u544A ssh.enabled = false\uFF09\u3002\u4E0A\u4F20\u53EA\u8D70 SSH \u901A\u9053\uFF1A\u8BF7\u5E26 \`--ssh\` \u91CD\u65B0\u521B\u5EFA\u8D1F\u8F7D\uFF0C\u6216\u6362\u4E00\u4E2A\u5DF2\u5F00 SSH \u7684 training-env / workspace\u3002\uFF08\u4E0D\u4F1A\u9000\u56DE \`runnpu workload exec\`\u2014\u2014\u90A3\u6761\u8DEF\u8981\u8FC7 K8s API Server\uFF0C\u5DE5\u7A0B\u6D41\u91CF\u4E0D\u8BE5\u5360\u7528\u63A7\u5236\u9762\u3002\uFF09`,{ref:e.ref});let a=typeof i.username=="string"&&i.username.trim()!==""?i.username.trim():void 0;if(a===void 0){let y=Array.isArray(r?.pods)?r.pods:[],u=y.filter(l=>l?.ready===!0);if(u.length===0){let l=y.length===0?"\u5F53\u524D\u6CA1\u6709\u4EFB\u4F55 Pod":`\u5F53\u524D ${y.length} \u4E2A Pod \u90FD\u4E0D\u662F Ready\uFF08${y.map(m=>`${m?.name??"?"}: ${m?.phase??"?"}`).join("\u3001")}\uFF09`;throw new S("ssh_pending",`${t} \u7684 SSH \u5DF2\u5F00\u542F\uFF0C\u4F46\u5E73\u53F0\u8FD8\u6CA1\u6709\u62A5\u544A\u767B\u5F55\u540D\uFF1Aoperator \u53EA\u5BF9 **Ready \u7684 Pod** \u4E0A\u62A5 status.ssh\uFF0C${l}\u3002\u8FD9\u662F\u300C\u7B49\u4E00\u7B49\u300D\uFF0C\u4E0D\u662F\u914D\u7F6E\u9519\u2014\u2014\u7B49\u8D1F\u8F7D\u8D77\u6765\u518D\u4F20\uFF1B\u5982\u679C\u4E00\u76F4\u8D77\u4E0D\u6765\uFF0C\u5148\u770B \`runnpu workload events\` \u6392\u67E5 Pod\u3002`,{pods:y.length,ready:u.length})}throw new S("ssh_username_missing",`${t} \u7684 SSH \u5DF2\u5F00\u542F\u3001Pod \u4E5F Ready\uFF0C\u4F46\u5E73\u53F0\u6CA1\u6709\u900F\u51FA ssh.username\u3002\u8FD9\u662F\u63A7\u5236\u53F0\u7248\u672C\u592A\u65E7\uFF08\u8BE5\u6295\u5F71 2026-08 \u624D\u8865\u9F50\uFF09\uFF0C\u9700\u8981\u91CD\u65B0\u90E8\u7F72 / \u5347\u7EA7\u63A7\u5236\u53F0\u3002`+(i.target?`**\u4E0D\u8981**\u62FF ssh.target\uFF08${i.target}\uFF09\u5F53\u7528\u6237\u540D\u2014\u2014\u90A3\u662F piper\u2192\u5BB9\u5668\u90A3\u4E00\u6BB5\u7684\u4E0A\u6E38\u5730\u5740\uFF0C\u7528\u5B83\u767B\u5F55\u4F1A\u88AB\u5224 no matching pipe\uFF0C\u8868\u73B0\u4E3A Permission denied\u3002`:""),{target:i.target})}let s=[],o=typeof i.host=="string"?i.host.trim():"",d={value:o,from:"projection"};if(o===""){let y=typeof e.sshHost=="string"?e.sshHost.trim():"";if(y!=="")o=y,d={value:o,from:"config"},s.push(`\u5165\u53E3\u4E3B\u673A\u6765\u81EA\u63D2\u4EF6\u914D\u7F6E sshHost=${o}\uFF08\u5E73\u53F0\u5C1A\u672A\u6295\u5F71 ssh.host\uFF09\u3002`);else{let u=Le(n);if(!u)throw new S("no_ssh_host","\u5E73\u53F0\u6CA1\u6709\u7ED9\u51FA SSH \u5165\u53E3\u5730\u5740\uFF08ssh.host \u7F3A\u5931\u2014\u2014\u7EBF\u4E0A\u63A7\u5236\u53F0\u9700\u8981\u91CD\u65B0\u90E8\u7F72\u4E00\u6B21\u624D\u4F1A\u5E26\u4E0A\u8FD9\u4E2A\u5B57\u6BB5\uFF09\uFF0C\u8282\u70B9\u5217\u8868\u91CC\u4E5F\u6CA1\u6709\u53EF\u7528\u7684 internal_ip \u53EF\u4EE5\u515C\u5E95\u3002\u8BF7\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u663E\u5F0F\u8BBE\u7F6E sshHost\uFF08\u67D0\u53F0\u8282\u70B9 IP \u6216 SSHPiper \u7684 VIP\uFF09\u3002");o=String(u.internal_ip),d={value:o,from:"node",node:String(u.name??"")},s.push(`\u5165\u53E3\u4E3B\u673A\u53D6\u81EA\u8282\u70B9 ${u.name} \u7684 internal_ip=${o}\u2014\u2014\u5E73\u53F0\u8FD8\u6CA1\u6295\u5F71 ssh.host\u3002SSHPiper \u662F NodePort\uFF0C\u6BCF\u53F0\u8282\u70B9\u90FD\u901A\uFF0C\u6240\u4EE5\u8FD9\u662F\u53EF\u7528\u7684\u515C\u5E95\uFF1B\u63A7\u5236\u53F0\u91CD\u65B0\u90E8\u7F72\u540E\u4F1A\u81EA\u52A8\u6539\u7528\u5E73\u53F0\u7ED9\u7684\u503C\u3002`)}}let p=Number(i.port),c;return Number.isInteger(p)&&p>0&&p<65536?c={value:p,from:"projection"}:Number.isInteger(e.sshPort)&&Number(e.sshPort)>0&&Number(e.sshPort)<65536?(c={value:Number(e.sshPort),from:"config"},s.push(`\u5165\u53E3\u7AEF\u53E3\u6765\u81EA\u63D2\u4EF6\u914D\u7F6E sshPort=${c.value}\uFF08\u5E73\u53F0\u5C1A\u672A\u6295\u5F71 ssh.port\uFF09\u3002`)):(c={value:ee,from:"default"},s.push(`\u5165\u53E3\u7AEF\u53E3\u7528\u9ED8\u8BA4\u503C ${ee}\uFF08SSHPiper \u7684 NodePort\uFF09\u2014\u2014\u5E73\u53F0\u8FD8\u6CA1\u6295\u5F71 ssh.port\u3002\u5982\u679C\u90E8\u7F72\u65B9\u6539\u8FC7\u8FD9\u4E2A\u7AEF\u53E3\uFF0C\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u8BBE sshPort\u3002`)),{username:a,host:o,port:c.value,target:typeof i.target=="string"&&i.target!==""?i.target:void 0,derivation:{username:{value:a,from:"projection"},host:d,port:c,fallback:d.from!=="projection"||c.from!=="projection",notes:s}}}function M({hasRsync:r,hasTar:n,lane:e}){return r?e==="data"?{method:"rsync",reason:"\u8FDC\u7AEF\u6709 rsync\uFF1A\u589E\u91CF\u4F20\u8F93 + --partial \u65AD\u70B9\u7EED\u4F20\uFF0C\u5DF2\u5728\u8FDC\u7AEF\u7684\u540C\u540D\u540C\u5927\u5C0F\u6587\u4EF6\u4F1A\u81EA\u52A8\u8DF3\u8FC7\uFF1B\u4E0D\u5E26 --delete\uFF0C\u6570\u636E\u96C6\u53EA\u589E\u4E0D\u5220\u3002"}:{method:"rsync",reason:"\u8FDC\u7AEF\u6709 rsync\uFF1A\u589E\u91CF\u4F20\u8F93 + --delete\uFF0C\u672C\u5730\u5220\u6389\u7684\u6587\u4EF6\u5728\u5BB9\u5668\u91CC\u4E5F\u4F1A\u6D88\u5931\uFF0C\u4EE3\u7801\u9053\u6BCF\u8F6E\u540C\u6B65\u6700\u7701\u5E26\u5BBD\u3002"}:e==="data"?{method:"sftp",reason:"\u8FDC\u7AEF\u6CA1\u6709 rsync\uFF1A\u6570\u636E\u9053\u4E0D\u8D70 tar \u7BA1\u9053\uFF08tar \u6D41\u65E2\u4E0D\u80FD\u7EED\u4F20\u4E5F\u4E0D\u80FD\u8DF3\u8FC7\u5DF2\u6709\u6587\u4EF6\uFF09\uFF0C\u6539\u7528 helper \u5185\u5D4C\u7684 SFTP \u5B50\u7CFB\u7EDF\uFF0C\u9010\u6587\u4EF6 reput \u7EED\u4F20\u3002"}:n?{method:"tar",reason:"\u8FDC\u7AEF\u6CA1\u6709 rsync\u3001\u6709 tar\uFF1A\u8D70 SSH \u7684 exec \u901A\u9053\u505A tar \u7BA1\u9053\uFF0C\u4E00\u6B21\u6027\u5168\u91CF\u63A8\u9001\uFF08\u540C\u4E00\u6761 TCP\uFF0C\u4E0D\u8FC7 API Server\uFF09\u3002"}:{method:"sftp",reason:"\u8FDC\u7AEF\u65E2\u6CA1\u6709 rsync \u4E5F\u6CA1\u6709 tar\uFF08distroless \u955C\u50CF\u5E38\u89C1\uFF09\uFF1A\u7528 helper \u4E8C\u8FDB\u5236\u5185\u5D4C\u7684 SFTP \u5B50\u7CFB\u7EDF\uFF0C\u5B83\u4E0E\u955C\u50CF\u5185\u5BB9\u65E0\u5173\uFF0C\u662F\u552F\u4E00\u666E\u9002\u901A\u9053\u3002"}}function ce(r){return[{probe:"rsync",...M({hasRsync:!0,hasTar:!0,lane:r})},{probe:"tar",...M({hasRsync:!1,hasTar:!0,lane:r})},{probe:"none",...M({hasRsync:!1,hasTar:!1,lane:r})}]}var J=Object.freeze(["-o","BatchMode=yes","-o","StrictHostKeyChecking=accept-new","-o","ConnectTimeout=10"]),ue="command -v rsync >/dev/null 2>&1 && echo rsync; command -v tar >/dev/null 2>&1 && echo tar; exit 0";function le(r){let n=String(r??"").split(`
|
|
3
|
+
`).map(e=>e.trim());return{hasRsync:n.includes("rsync"),hasTar:n.includes("tar")}}function G(r,n){return["-p",String(r.port),...J,`${r.username}@${r.host}`,n]}function ne(r){let n=String(r);if(n.includes("\0"))throw new S("bad_path","\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B NUL");return`'${n.replaceAll("'","'\\''")}'`}function A(r){let n=String(r??"");if(!n.startsWith("/"))throw new S("bad_dest",`\u8FDC\u7AEF\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6536\u5230 ${JSON.stringify(r)}`);if(!/^\/[A-Za-z0-9._@+:\-/]*$/.test(n))throw new S("bad_dest",`\u8FDC\u7AEF\u76EE\u5F55\u53EA\u63A5\u53D7\u5B57\u6BCD\u3001\u6570\u5B57\u548C . _ - + @ : / \u8FD9\u4E9B\u5B57\u7B26\uFF0C\u6536\u5230 ${JSON.stringify(r)}\u2014\u2014\u542B\u7A7A\u683C\u6216\u5F15\u53F7\u7684\u8DEF\u5F84\u4F1A\u540C\u65F6\u7A7F\u8FC7\u8FDC\u7AEF shell \u548C sftp \u7684\u5206\u8BCD\u5668\uFF0C\u4E0D\u503C\u5F97\u4E3A\u5B83\u5192\u9669\u3002`);let e=n.replace(/\/+$/,"")||"/";if(e.split("/").includes(".."))throw new S("bad_dest",`\u8FDC\u7AEF\u76EE\u5F55\u4E0D\u80FD\u5305\u542B ..\uFF1A${JSON.stringify(r)}`);return e}function he({entry:r,source:n,dest:e,patterns:t,lane:i}){let a=A(e),s=i==="data"?["-a","--partial"]:["-a","-z","--delete"];for(let o of t)s.push("--exclude",o);return s.push("-e",`ssh -p ${r.port} ${J.join(" ")}`),s.push(`${n.replace(/\/+$/,"")}/`,`${r.username}@${r.host}:${a}/`),s}function fe({entry:r,source:n,dest:e,listFile:t}){let i=A(e);return{local:{bin:"tar",argv:["-c","-f","-","-C",n,"--null","-T",t],env:{COPYFILE_DISABLE:"1"}},remote:{bin:"ssh",argv:G(r,`mkdir -p ${ne(i)} && tar -x --no-same-owner --no-same-permissions -f - -C ${ne(i)}`)}}}function _(r){let n=String(r);if(/[\0\n\r]/.test(n))throw new S("bad_path",`\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B\u6362\u884C\u6216 NUL\uFF1A${JSON.stringify(r)}`);return`"${n.replace(/(["\\])/g,"\\$1")}"`}function me(r){let n=r.split("/").filter(Boolean),e=[],t="";for(let i of n)t+=`/${i}`,e.push(`-mkdir ${_(t)}`);return e}function ye({dest:r,stagedPath:n,lane:e}){let i=A(r).split("/").slice(0,-1).join("/")||"/",a=e==="data"?"reput -r":"put -r";return[...me(i),`${a} ${_(n)} ${_(i)}`,""].join(`
|
|
4
|
+
`)}function ge({dest:r,files:n,lane:e,localRoot:t}){let i=A(r),a=e==="data"?"reput":"put",s=[`lcd ${_(t)}`,...me(i)],o=new Set;for(let d of n){let p=d.path.split("/"),c=i;for(let y of p.slice(0,-1))c+=`/${y}`,!o.has(c)&&(o.add(c),s.push(`-mkdir ${_(c)}`))}for(let d of n)s.push(`${a} ${_(d.path)} ${_(`${i}/${d.path}`)}`);return s.push(""),s.join(`
|
|
5
|
+
`)}function W({entry:r,batchFile:n}){return["-b",n,"-P",String(r.port),...J,`${r.username}@${r.host}`]}function we(r){return r==="data"?Ae:_e}async function be(r,n){let e=n?Z(r,n):Z(r),t;try{t=await z(e)}catch{throw new S("no_source",`\u672C\u5730\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${e}`)}if(!t.isDirectory())throw new S("no_source",`source \u5FC5\u987B\u662F\u76EE\u5F55\uFF1A${e}`);return e}var ot="runnpu-training-tools",at=["tools"],Se=5,ke=r=>`${String(r).padStart(4,"0")}.json`;function D(r){let n=r?.agent?.session?.header?.cwd;if(!n)throw new Error("\u8FD9\u4E2A\u5DE5\u5177\u9700\u8981\u4E00\u4E2A\u7ED1\u5B9A\u4E86\u5DE5\u4F5C\u76EE\u5F55\u7684\u4F1A\u8BDD\uFF08\u8BAD\u7EC3\u6A21\u5F0F\u5FC5\u987B\u5148\u9009\u597D\u8BAD\u7EC3\u5DE5\u7A0B\u76EE\u5F55\uFF09");return n}function U(r,n,e){let t=r.get(n);if(!t)throw new Error(`${e} \u8FDE\u63A5\u5668\u672A\u6302\u8F7D\uFF1A\u8BF7\u786E\u8BA4 dsh-runnpu bundle \u5DF2\u88C5\u8FDB\u5F53\u524D profile`);return t}function He(r,n,e={}){return new Promise((t,i)=>{je(r,n,{env:{...process.env,...e.env??{},NO_COLOR:"1"},timeout:e.timeoutMs??15*6e4,maxBuffer:32*1024*1024,signal:e.signal},(a,s,o)=>{if(a){let d=String(o||"").trim().slice(0,4e3),p=new Error(d||`${r} \u6267\u884C\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${a.code??"\u672A\u77E5"}\uFF09`);p.stderr=d,i(p);return}t(s)})})}function I(r,n,e={}){return new Promise((t,i)=>{je(r,n,{env:{...process.env,NO_COLOR:"1"},timeout:e.timeoutMs??15e3,maxBuffer:8*1024*1024,signal:e.signal},(a,s,o)=>{if(a){i(xe(r,a,o));return}t({stdout:String(s),stderr:String(o)})})})}function xe(r,n,e){let t=String(e||"").trim().slice(0,2e3);if(n?.code==="ENOENT")return new Error(`\u672C\u673A\u6CA1\u6709 ${r} \u547D\u4EE4\uFF1A\u4E0A\u4F20\u7528\u7684\u662F\u672C\u673A\u7684 ssh / sftp / rsync / tar \u5BA2\u6237\u7AEF\uFF0C\u8BF7\u5148\u628A ${r} \u88C5\u4E0A\u518D\u8BD5\u3002`);let i=n?.killed?"\u8D85\u65F6\u88AB\u7EC8\u6B62":typeof n?.code=="number"?`\u9000\u51FA\u7801 ${n.code}`:`${n?.code??"\u672A\u77E5\u9519\u8BEF"}`;return new Error(`${r} ${i}${t?`\uFF1A${t}`:""}`)}function ze(r,n,e){return new Promise((t,i)=>{let a={...process.env,NO_COLOR:"1"},s=$e(r.bin,r.argv,{env:{...a,...r.env??{}},stdio:["ignore","pipe","pipe"]}),o=$e(n.bin,n.argv,{env:a,stdio:["pipe","pipe","pipe"]}),d=!1,p={[r.bin]:"",[n.bin]:""},c=m=>{d||(d=!0,clearTimeout(y),e.signal?.removeEventListener("abort",u),s.kill("SIGKILL"),o.kill("SIGKILL"),m?i(m):t(void 0))},y=setTimeout(()=>c(new Error(`tar \u7BA1\u9053\u5728 ${e.timeoutMs}ms \u5185\u6CA1\u6709\u5B8C\u6210\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u90E8\u5206\u6587\u4EF6\u53EF\u80FD\u5DF2\u7ECF\u843D\u5730\uFF09`)),e.timeoutMs),u=()=>c(new Error("\u4E0A\u4F20\u5DF2\u53D6\u6D88"));e.signal?.addEventListener("abort",u,{once:!0}),s.stderr.on("data",m=>{p[r.bin]=(p[r.bin]+m).slice(0,2e3)}),o.stderr.on("data",m=>{p[n.bin]=(p[n.bin]+m).slice(0,2e3)}),s.stdout.pipe(o.stdin),s.stdout.on("error",()=>{}),o.stdin.on("error",()=>{});for(let[m,w]of[[s,r],[o,n]])m.on("error",h=>c(xe(w.bin,h,p[w.bin])));let l;s.on("close",m=>{l=m,m!==0&&c(new Error(`\u672C\u5730 ${r.bin} \u6253\u5305\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${m}\uFF09${p[r.bin]?`\uFF1A${p[r.bin].trim()}`:""}`))}),o.on("close",m=>{if(m!==0){c(new Error(`\u8FDC\u7AEF\u89E3\u5305\u5931\u8D25\uFF08${n.bin} \u9000\u51FA\u7801 ${m}\uFF09${p[n.bin]?`\uFF1A${p[n.bin].trim()}`:""}`));return}l===void 0?s.on("close",()=>c(void 0)):l===0&&c(void 0)})})}async function Be(r){let n;try{n=await Ie(x(q(r),"runs"))}catch{return 1}let e=n.filter(t=>t.endsWith(".json")).map(t=>Number.parseInt(t,10)).filter(Number.isInteger);return e.length===0?1:Math.max(...e)+1}function Ke(r,n){return!(r.length===2&&/(^|\/)(ba)?sh$/.test(r[0])&&r[1]==="-c")||n.length!==1?{args:n,applied:!1,warning:'SwanLab API \u5730\u5740\u4ECE\u672C\u673A\u63A2\u6D4B\u4E0D\u901A\uFF0C\u4F46\u542F\u52A8\u547D\u4EE4\u4E0D\u662F `bash -c "<\u811A\u672C>"` \u7684\u5F62\u72B6\uFF0C\u65E0\u6CD5\u81EA\u52A8\u964D\u7EA7\u3002\u8BF7\u5728\u8BAD\u7EC3\u547D\u4EE4\u524D\u81EA\u884C\u52A0 `swanlab offline`\uFF0C\u5426\u5219\u8FD9\u6B21\u8BAD\u7EC3\u4E0D\u4F1A\u6709\u66F2\u7EBF\u3002'}:{args:[`swanlab offline >/dev/null 2>&1; ${n[0]}`],applied:!0,warning:void 0}}function dt(r,n={}){r.tools.register(T({name:"training_upload",description:'Upload this project into a workload over SSH. Use this INSTEAD of composing scp/rsync/sftp yourself for every code or dataset upload: the exclusion of .train/secrets.json is enforced here as a mechanism rather than remembered, the transport is chosen from what the remote image actually has (rsync, a tar pipe, or the SFTP subsystem built into the SSH helper, which works even on a distroless image), and the code lane returns a codeRev content hash that you then pass to training_submit so "which code ran" stays answerable. Two lanes: code (the default \u2014 small, re-synced every iteration) and dataset:true (large, uploaded once to a separate destination; never re-send a dataset alongside the code). Call it with dryRun:true first when you want to show the user the plan \u2014 which files, which exclusions, which SSH entry \u2014 before anything moves.',parameters:{project:{type:"string",required:!0,description:"RunNPU project slug that owns the target workload."},name:{type:"string",required:!0,description:"Target workload name. It must already have SSH enabled and a Ready pod."},source:{type:"string",description:"Local directory to upload, absolute or relative to the workspace. Defaults to the workspace root."},dest:{type:"string",description:"Absolute remote directory. Defaults to /workspace/code, or /workspace/data when dataset is true."},dataset:{type:"boolean",description:"Switch to the DATA lane: a separate destination, resume-first transfer, no codeRev, and model-weight suffixes are NOT excluded (they are the payload). Use it for anything large and stable, and do not re-upload it with every code change."},dryRun:{type:"boolean",description:"Report the plan \u2014 SSH entry, candidate methods, file and exclusion counts \u2014 and transfer nothing."},extraExcludes:{type:"array",items:{type:"string"},description:"Additional exclude patterns (rsync flavour: a pattern without a slash matches any path segment). These only ADD; the forced rules, .train/secrets.json first among them, cannot be cancelled."},timeoutMs:{type:"integer",description:"Transfer deadline in milliseconds. Default 1800000 (30 minutes)."}},output:{schema:{type:"object",additionalProperties:!1,properties:{workloadRef:{type:"string",required:!0},lane:{type:"string",required:!0,enum:["code","data"]},dryRun:{type:"boolean",required:!0},source:{type:"string",required:!0},dest:{type:"string",required:!0},method:{type:"string",enum:["rsync","tar","sftp"]},methodReason:{type:"string"},methodCandidates:{type:"array",items:{type:"object",additionalProperties:!1,properties:{probe:{type:"string",required:!0},method:{type:"string",required:!0},reason:{type:"string",required:!0}}}},entry:{type:"object",additionalProperties:!1,properties:{username:{type:"string",required:!0},host:{type:"string",required:!0},port:{type:"integer",required:!0},fallback:{type:"boolean",required:!0},from:{type:"object",required:!0,additionalProperties:!1,properties:{username:{type:"string",required:!0},host:{type:"string",required:!0},port:{type:"string",required:!0}}},notes:{type:"array",required:!0,items:{type:"string"}}}},entryError:{type:"object",additionalProperties:!1,properties:{code:{type:"string",required:!0},message:{type:"string",required:!0}}},files:{type:"integer",required:!0},bytes:{type:"integer",required:!0},excludePatterns:{type:"array",required:!0,items:{type:"string"}},excluded:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{pattern:{type:"string",required:!0},why:{type:"string",required:!0},files:{type:"integer",required:!0},dirs:{type:"integer",required:!0},bytes:{type:"integer",required:!0}}}},skipped:{type:"array",required:!0,items:{type:"string"}},skippedCount:{type:"integer",required:!0},codeRev:{type:"string"},durationMs:{type:"integer",required:!0},warnings:{type:"array",required:!0,items:{type:"string"}}}},render:(e,t)=>{let i=t.dryRun?`\u4E0A\u4F20\u8BA1\u5212\uFF08dryRun\uFF0C\u6CA1\u6709\u4F20\u4EFB\u4F55\u4E1C\u897F\uFF09\uFF1A${t.source} \u2192 ${t.workloadRef}:${t.dest}`:`\u5DF2\u4E0A\u4F20\u5230 ${t.workloadRef}:${t.dest}\uFF08${t.method}\uFF0C${Math.round(t.durationMs/100)/10}s\uFF09`,a=t.entry?`\u5165\u53E3\uFF1A${t.entry.username}@${t.entry.host}:${t.entry.port}\uFF08\u7528\u6237\u540D\u6765\u81EA${t.entry.from.username==="projection"?"\u5E73\u53F0\u6295\u5F71":t.entry.from.username}\uFF0C\u4E3B\u673A\u6765\u81EA${t.entry.from.host}\uFF0C\u7AEF\u53E3\u6765\u81EA${t.entry.from.port}\uFF09`:`\u5165\u53E3\u672A\u89E3\u6790\uFF1A${t.entryError?.message??"\u672A\u77E5\u539F\u56E0"}`,s=t.excluded.length===0?"\u5F3A\u5236\u6392\u9664\u6E05\u5355\u6CA1\u6709\u547D\u4E2D\u4EFB\u4F55\u6587\u4EF6":`\u5F3A\u5236\u6392\u9664\u547D\u4E2D\uFF1A${t.excluded.map(o=>`${o.pattern}\uFF08${o.files} \u6587\u4EF6 / ${o.dirs} \u76EE\u5F55\uFF09`).join("\u3001")}`;return[{type:"text",text:`${i}
|
|
6
|
+
${t.files} \u4E2A\u6587\u4EF6\u3001${t.bytes} \u5B57\u8282${t.skippedCount>0?`\uFF0C\u8DF3\u8FC7 ${t.skippedCount} \u4E2A\u975E\u666E\u901A\u6587\u4EF6\uFF08\u7B26\u53F7\u94FE\u63A5\u7B49\uFF09`:""}
|
|
7
|
+
${s}
|
|
8
|
+
${a}`+(t.codeRev?`
|
|
9
|
+
codeRev\uFF1A${t.codeRev}\uFF08\u63D0\u4EA4\u8BAD\u7EC3\u65F6\u628A\u5B83\u4F20\u7ED9 training_submit \u7684 codeRev \u53C2\u6570\uFF09`:"")+(t.methodCandidates?`
|
|
10
|
+
\u5B9E\u9645\u65B9\u6CD5\u8981\u7B49\u63A2\u6D4B\uFF1A${t.methodCandidates.map(o=>`${o.probe}\u2192${o.method}`).join("\u3001")}`:"")+(t.entry?.notes?.length?`
|
|
11
|
+
${t.entry.notes.join(`
|
|
12
|
+
`)}`:"")+(t.warnings.length>0?`
|
|
13
|
+
\u6CE8\u610F\uFF1A
|
|
14
|
+
- ${t.warnings.join(`
|
|
15
|
+
- `)}`:"")}]}},timeoutMs:35*6e4,async execute(e,t){let i=Date.now(),a=D(t),s=`${e.project}/${e.name}`,o=e.dataset===!0?"data":"code",d=[],p=await be(a,e.source),c=A(e.dest??we(o));if(c==="/")throw new Error("\u62D2\u7EDD\u628A\u5DE5\u7A0B\u76F4\u63A5\u94FA\u5230\u5BB9\u5668\u6839\u76EE\u5F55 /\u3002\u8BF7\u7ED9\u4E00\u4E2A\u5177\u4F53\u7684\u76EE\u5F55\uFF0C\u6BD4\u5982 /workspace/code\u3002");let u=se(o,e.extraExcludes??[]).map(f=>f.pattern),l=await oe(p,u,{signal:t.signal});if(l.files.length===0)throw new Error(`${p} \u91CC\u6CA1\u6709\u4EFB\u4F55\u53EF\u4E0A\u4F20\u7684\u6587\u4EF6\uFF08\u5168\u90E8\u88AB\u6392\u9664\uFF0C\u6216\u76EE\u5F55\u662F\u7A7A\u7684\uFF09\u3002\u5F53\u524D\u6392\u9664\u6E05\u5355\uFF1A${u.join("\u3001")}`);l.skippedCount>0&&d.push(`\u8DF3\u8FC7\u4E86 ${l.skippedCount} \u4E2A\u7B26\u53F7\u94FE\u63A5 / \u975E\u666E\u901A\u6587\u4EF6\uFF08\u4E0D\u8DDF\u968F\u7B26\u53F7\u94FE\u63A5\uFF0C\u907F\u514D\u628A\u5BB9\u5668\u5916\u7684\u4E1C\u897F\u4E00\u8D77\u4F20\u8FDB\u53BB\uFF09\uFF1A${l.skipped.join("\u3001")}${l.skippedCount>l.skipped.length?" \u2026":""}`);let m={workloadRef:s,lane:o,source:p,dest:c,files:l.files.length,bytes:l.bytes,excludePatterns:u,excluded:l.excluded,skipped:l.skipped,skippedCount:l.skippedCount},w={sshHost:n?.sshHost,sshPort:n?.sshPort,ref:s},h=async()=>{let f=U(r,"runnpu","RunNPU"),j=await f.connection(a);if(!j.server||!j.token)throw new S("not_logged_in","RunNPU \u5C1A\u672A\u767B\u5F55\uFF1A\u8BF7\u5728\u754C\u9762\u53F3\u680F\u6216\u8BBE\u7F6E\u91CC\u586B\u5199\u767B\u5F55\u5730\u5740\u4E0E\u8D26\u53F7\u5BC6\u7801");let v=await f.api.workload(j,e.project,e.name,t.signal),Pe=!(typeof v?.ssh?.host=="string"&&v.ssh.host.trim()!=="")&&!w.sshHost?await f.api.nodes(j,t.signal):void 0;return pe(v,Pe,w)},$=f=>({username:f.username,host:f.host,port:f.port,fallback:f.derivation.fallback,from:{username:f.derivation.username.from,host:f.derivation.host.from,port:f.derivation.port.from},notes:f.derivation.notes});if(e.dryRun===!0){let f,j;try{f=$(await h())}catch(v){j={code:v?.code??"entry_failed",message:String(v?.message??v)}}return R({...m,dryRun:!0,methodCandidates:ce(o),entry:f,entryError:j,durationMs:Date.now()-i,warnings:d})}let b=await h();b.derivation.fallback&&d.push(`SSH \u5165\u53E3\u6709\u515C\u5E95\u6210\u5206\uFF1A${b.derivation.notes.join(" ")}`);let k={hasRsync:!1,hasTar:!1};try{let f=await I("ssh",G(b,ue),{timeoutMs:15e3,signal:t.signal});k=le(f.stdout)}catch(f){d.push(`\u8FDC\u7AEF\u80FD\u529B\u63A2\u6D4B\u6CA1\u8DD1\u6210\uFF08${String(f?.message??f).slice(0,200)}\uFF09\uFF0C\u6309\u300C\u65E2\u65E0 rsync \u4E5F\u65E0 tar\u300D\u5904\u7406\uFF0C\u8D70 SFTP\u3002`)}let P=M({...k,lane:o}),L=o==="code"?await ae(p,l.files,t.signal):void 0,N=Number.isInteger(e.timeoutMs)&&e.timeoutMs>0?e.timeoutMs:30*6e4,g=await Ce(x(Fe(),"dsh-upload-"));try{if(P.method==="rsync")await I("rsync",he({entry:b,source:p,dest:c,patterns:u,lane:o}),{timeoutMs:N,signal:t.signal});else if(P.method==="tar"){let f=x(g,"members.list");await O(f,`${l.files.map(v=>v.path).join("\0")}\0`,"utf8");let j=fe({entry:b,source:p,dest:c,listFile:f});await ze(j.local,j.remote,{timeoutMs:N,signal:t.signal})}else if(o==="code"){let f=x(g,c.split("/").pop()??"code");await de(p,l.files,f,t.signal);let j=x(g,"batch.sftp");await O(j,ye({dest:c,stagedPath:f,lane:o}),"utf8"),await I("sftp",W({entry:b,batchFile:j}),{timeoutMs:N,signal:t.signal})}else{let f=x(g,"batch.sftp");await O(f,ge({dest:c,files:l.files,lane:o,localRoot:p}),"utf8"),await I("sftp",W({entry:b,batchFile:f}),{timeoutMs:N,signal:t.signal})}}catch(f){throw new Error(`\u4E0A\u4F20\u5931\u8D25\uFF08\u65B9\u6CD5 ${P.method}\uFF0C\u76EE\u6807 ${s}:${c}\uFF09\uFF1A${String(f?.message??f).slice(0,2e3)}
|
|
16
|
+
\u8FDC\u7AEF\u53EF\u80FD\u5DF2\u7ECF\u843D\u4E86\u4E00\u90E8\u5206\u6587\u4EF6\uFF0C\u8BF7\u4FEE\u597D\u540E\u91CD\u8DD1\u672C\u5DE5\u5177\uFF08rsync / sftp \u90FD\u662F\u5E42\u7B49\u7684\uFF09\uFF0C\u4E0D\u8981\u5F53\u6210\u529F\u5904\u7406\u3002`)}finally{await Ue(g,{recursive:!0,force:!0})}return R({...m,dryRun:!1,method:P.method,methodReason:P.reason,entry:$(b),codeRev:L,durationMs:Date.now()-i,warnings:d})}})),r.tools.register(T({name:"training_submit",description:"Submit a training workload to RunNPU with SwanLab instrumentation wired up, and record the run in the project ledger. Use this INSTEAD of composing `runnpu workload create` yourself whenever the workload is a training run: it injects the SwanLab credentials as container environment variables without ever putting them in the conversation, checks whether the cluster can actually reach the SwanLab API (degrading to offline logging when it cannot, so you never promise a curve that will not arrive), allocates the run sequence number, and writes .train/runs/NNNN.json with the hyperparameters, template identity and code revision. Restate the parameters to the user and get confirmation before calling it \u2014 this creates real cluster workloads that consume quota.",parameters:{project:{type:"string",required:!0,description:"RunNPU project slug that owns the workload."},type:{type:"string",enum:["training-job","training-env"],description:"training-job runs to completion; training-env is the long-lived interactive box. Default training-job."},name:{type:"string",description:"Workload name. Defaults to train-NNNN matching the ledger sequence."},image:{type:"string",description:"Container image. Give this or fromTemplate."},fromTemplate:{type:"string",description:"Workload template name or id to expand (preferred \u2014 templates carry presets you would otherwise rediscover the hard way)."},xpu:{type:"integer",description:"Accelerator cards. Default 1; 0 means CPU only."},nodes:{type:"integer",description:"training-job only: 1 for single node, >= 2 for multi-node. No values in between."},pool:{type:"string",description:"Compute pool; omitted takes the project default."},command:{type:"array",items:{type:"string"},description:'Command, in order \u2014 e.g. ["bash", "-c"].'},args:{type:"array",items:{type:"string"},description:'Arguments \u2014 e.g. ["python train.py"].'},mounts:{type:"array",items:{type:"string"},description:'Raw --mount specs, e.g. "datavolume:/data,name=ds,ro". Passed through verbatim.'},env:{type:"array",items:{type:"string"},description:"Extra NON-SECRET environment variables as KEY=VALUE. Never put credentials here \u2014 they would enter the session log."},security:{type:"string",enum:["restricted","baseline"],description:"Pod security level. SSH is unrelated to it and to root: the helper is an injected binary that keeps the container's existing identity when PID 1 is not root, so SSH works under BOTH levels \u2014 never downgrade a workload to baseline just to get SSH. Under restricted a root IMAGE still needs runAsUid, which is a requirement of the security level itself, not of SSH."},runAsUid:{type:"integer",description:"Container UID; required under restricted for root images."},workdir:{type:"string",description:"Working directory inside the container."},workspacePool:{type:"string",description:"Managed work-disk storage pool."},workspaceGib:{type:"integer",description:"Managed work-disk size in GiB. Size it for code + dataset + checkpoints together."},backoffLimit:{type:"integer",description:"training-job retry budget."},activeDeadlineSeconds:{type:"integer",description:"training-job wall-clock deadline."},ttlAfterFinished:{type:"integer",description:"Seconds before a finished training-job is auto-deleted."},hyperparams:{type:"object",additionalProperties:!0,properties:{},description:"This run's hyperparameters. Recorded in the ledger and used for the version card diff \u2014 pass what you actually set."},template:{type:"object",additionalProperties:!1,properties:{id:{type:"string",required:!0,description:"Template identity."},version:{type:"string",description:"Template version."}},description:"Solution-template identity and version behind this run."},adaptation:{type:"object",additionalProperties:!0,properties:{},description:"Parameters used to adapt the template to this project."},codeRev:{type:"string",description:'Git sha or content hash of the code that was uploaded, so "which code ran" stays answerable.'},note:{type:"string",description:"Why this run is configured this way \u2014 one line, kept in the ledger."},wait:{type:"boolean",description:"Block until running/failed/rejected (up to 10 minutes). Default false."}},output:{schema:{type:"object",additionalProperties:!1,properties:{seq:{type:"integer",required:!0},workloadRef:{type:"string",required:!0},ledgerPath:{type:"string",required:!0},swanlab:{type:"object",required:!0,additionalProperties:!1,properties:{upload:{type:"string",required:!0,enum:["online","offline","unconfigured"]},runName:{type:"string"},injectedKeys:{type:"array",required:!0,items:{type:"string"}},reason:{type:"string"}}},workload:{type:"object",additionalProperties:!0,properties:{}},warnings:{type:"array",required:!0,items:{type:"string"}}}},render:(e,t)=>[{type:"text",text:`\u7B2C ${t.seq} \u6B21\u8BAD\u7EC3\u5DF2\u63D0\u4EA4\uFF1A${t.workloadRef}
|
|
17
|
+
SwanLab\uFF1A${t.swanlab.upload==="online"?`\u5728\u7EBF\u4E0A\u62A5\uFF08\u5B9E\u9A8C\u540D ${t.swanlab.runName??"\u672A\u547D\u540D"}\uFF09`:t.swanlab.upload==="offline"?`\u79BB\u7EBF\u8BB0\u5F55\uFF0C\u7ED3\u675F\u540E\u9700 swanlab sync \u8865\u4F20\uFF08${t.swanlab.reason??"\u96C6\u7FA4\u65E0\u6CD5\u8FDE\u901A API"}\uFF09`:"\u672A\u914D\u7F6E\uFF0C\u672C\u6B21\u6CA1\u6709\u66F2\u7EBF"}
|
|
18
|
+
\u6CE8\u5165\u7684\u73AF\u5883\u53D8\u91CF\uFF1A${t.swanlab.injectedKeys.join(", ")||"\u65E0"}\uFF08\u503C\u4E0D\u51FA\u73B0\u5728\u5BF9\u8BDD\u4E2D\uFF09
|
|
19
|
+
\u8D26\u672C\uFF1A${t.ledgerPath}`+(t.warnings.length>0?`
|
|
20
|
+
\u6CE8\u610F\uFF1A
|
|
21
|
+
- ${t.warnings.join(`
|
|
22
|
+
- `)}`:"")}]},timeoutMs:16*6e4,async execute(e,t){let i=D(t),a=U(r,"runnpu","RunNPU"),s=U(r,"swanlab","SwanLab"),o=[],d=await a.connection(i);if(!d.server||!d.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55\uFF1A\u8BF7\u5728\u754C\u9762\u53F3\u680F\u6216\u8BBE\u7F6E\u91CC\u586B\u5199\u767B\u5F55\u5730\u5740\u4E0E\u8D26\u53F7\u5BC6\u7801");await F(i);let p=await Be(i),c=e.name??`train-${String(p).padStart(4,"0")}`,y=await s.connection(i),u="unconfigured",l;if(!y.apiKey||!y.apiHost)l="SwanLab \u672A\u914D\u7F6E",o.push("SwanLab \u672A\u914D\u7F6E\uFF0C\u8FD9\u6B21\u8BAD\u7EC3\u4E0D\u4F1A\u6709\u66F2\u7EBF\u3002\u5148\u5728\u8BAD\u7EC3\u6A21\u5F0F\u7684\u914D\u7F6E\u533A\u586B\u597D API \u5730\u5740\u4E0E Key\u3002");else{let g=await s.api.check(y,{signal:t.signal});u=g.reachable?"online":"offline",g.reachable||(l=g.reason),g.credentials==="unverifiable"?o.push(`\u51ED\u636E\u65E0\u6CD5\u5728\u672C\u673A\u6821\u9A8C\uFF08${g.reason}\uFF09\u3002\u5730\u5740\u662F\u901A\u7684\uFF0C\u672C\u6B21\u4ECD\u6309\u5728\u7EBF\u4E0A\u62A5\u63D0\u4EA4\uFF1B\u5982\u679C\u8BAD\u7EC3\u8D77\u6765\u540E\u6CA1\u6709\u66F2\u7EBF\uFF0C\u5148\u6000\u7591\u8FD9\u4E2A\u3002`):g.credentials==="invalid"&&o.push(`SwanLab \u51ED\u636E\u6821\u9A8C\u5931\u8D25\uFF1A${g.reason}\u3002\u53EF\u80FD\u4E0D\u4F1A\u6709\u66F2\u7EBF\u3002`)}let m=[e.template?.id?`template=${e.template.id}${e.template.version?`@${e.template.version}`:""}`:void 0,e.codeRev?`code=${e.codeRev}`:void 0,e.hyperparams?`hp=${JSON.stringify(e.hyperparams)}`:void 0].filter(Boolean).join(" ").slice(0,900),w=u==="unconfigured"?{}:await s.workloadEnv(i,{name:c,description:m||void 0}),h=Array.isArray(e.command)?[...e.command]:[],$=Array.isArray(e.args)?[...e.args]:[];if(u==="offline"){let g=Ke(h,$);$=g.args,g.warning?o.push(g.warning):o.push("\u96C6\u7FA4\u65E0\u6CD5\u8FDE\u901A SwanLab API\uFF0C\u5DF2\u6539\u4E3A\u79BB\u7EBF\u8BB0\u5F55\uFF1A\u8BAD\u7EC3\u7ED3\u675F\u540E\u7528 `swanlab sync` \u8865\u4F20\u3002")}let b=["workload","create","--project",e.project,"--name",c,"--type",e.type??"training-job"],k=(g,f)=>{f!=null&&f!==""&&b.push(g,String(f))};k("--from-template",e.fromTemplate),k("--image",e.image),k("--pool",e.pool),k("--security",e.security),k("--workdir",e.workdir),k("--workspace-pool",e.workspacePool),e.xpu!==void 0&&k("--xpu",e.xpu),e.nodes!==void 0&&k("--nodes",e.nodes),e.runAsUid!==void 0&&k("--run-as-uid",e.runAsUid),e.workspaceGib!==void 0&&k("--workspace-gib",e.workspaceGib),e.backoffLimit!==void 0&&k("--backoff-limit",e.backoffLimit),e.activeDeadlineSeconds!==void 0&&k("--active-deadline-seconds",e.activeDeadlineSeconds),e.ttlAfterFinished!==void 0&&k("--ttl-after-finished",e.ttlAfterFinished);for(let g of h)b.push("--command",g);for(let g of $)b.push("--arg",g);for(let g of e.mounts??[])b.push("--mount",g);for(let g of e.env??[])b.push("--env",g);for(let[g,f]of Object.entries(w))b.push("--env",`${g}=${f}`);e.wait&&b.push("--wait"),b.push("-o","json");let P;try{let g=await He("runnpu",b,{env:{RUNNPU_SERVER:d.server,RUNNPU_TOKEN:d.token},signal:t.signal,timeoutMs:e.wait?9e5:6e4});P=g.trim()?JSON.parse(g):void 0}catch(g){throw new Error(`\u63D0\u4EA4\u5931\u8D25\uFF1A${g.message}`)}let L={seq:p,startedAt:new Date().toISOString(),endedAt:null,template:e.template,adaptation:e.adaptation,hyperparams:e.hyperparams,codeRev:e.codeRev,workloadRef:`${e.project}/${c}`,swanlabRun:void 0,swanlabRunName:u==="unconfigured"?void 0:c,swanlabUpload:u,status:P?.status??"submitting",decision:void 0,note:e.note},N=x(q(i),"runs",ke(p));return await O(N,`${JSON.stringify(L,void 0,2)}
|
|
23
|
+
`,"utf8"),R({seq:p,workloadRef:L.workloadRef,ledgerPath:N,swanlab:{upload:u,runName:L.swanlabRunName,injectedKeys:Object.keys(w),reason:l},workload:P,warnings:o})}})),r.tools.register(T({name:"training_run_update",description:"Record what you and the user CONCLUDED about one run, into .train/runs/NNNN.json: the decision (promote, discard, iterate), the note that says why, and any correction to the recorded hyperparameters. That judgment is the part of the ledger nothing else can produce, and it is what makes the ledger worth reading three weeks later \u2014 so call this when a run has taught you something, not on a schedule. The mechanical half of the entry is NOT yours to maintain: a background sync copies the workload status, the end instant and the SwanLab run path (reverse-looked-up from the SWANLAB_NAME the submit injected) into the entry on its own, so after submitting you never have to come back to fill those in. The parameters for them exist only as a manual override for when that sync cannot work.",parameters:{seq:{type:"integer",required:!0,description:"Run sequence number, as returned by training_submit."},decision:{type:"string",enum:["promote","discard","next"],description:"The conclusion about this run. This, the note and the hyperparameter corrections are what this tool is for."},note:{type:"string",description:"What was learned from this run, and why the next one changes what it changes."},hyperparams:{type:"object",additionalProperties:!0,properties:{},description:"Corrections merged into the recorded hyperparameters \u2014 what actually ran, when it differed from what was submitted."},swanlabRun:{type:"string",description:'MANUAL OVERRIDE, only when the automatic sync cannot fill this in \u2014 no `swanlab.project` registered in .train/config.json, or the experiment is not on the first page of `run list`. Full SwanLab experiment path, "username/project/run_id". Normally the background sync writes it; do not write it routinely.'},status:{type:"string",enum:["submitting","queued","running","completed","failed","stopped","rejected"],description:"MANUAL OVERRIDE, only when the automatic sync is wrong or not running (it copies the workload status from RunNPU every refresh round). Correcting a run the platform lost track of is a legitimate use."},endedAt:{type:"string",description:"MANUAL OVERRIDE, only when the automatic sync did not record one. ISO-8601 end instant; the sync records the instant it NOTICED the run was over."}},output:{schema:{type:"object",additionalProperties:!1,properties:{seq:{type:"integer",required:!0},updated:{type:"array",required:!0,items:{type:"string"}},run:{type:"object",required:!0,additionalProperties:!0,properties:{}}}},render:(e,t)=>[{type:"text",text:`\u7B2C ${t.seq} \u6B21\u8BAD\u7EC3\u7684\u8D26\u672C\u5DF2\u66F4\u65B0\uFF1A${t.updated.join(", ")||"\u65E0\u53D8\u5316"}`}]},async execute(e,t){let i=D(t),a=x(q(i),"runs",ke(e.seq)),s;try{s=JSON.parse(await V(a,"utf8"))}catch{throw new Error(`\u8D26\u672C\u91CC\u6CA1\u6709\u7B2C ${e.seq} \u6B21\u8BAD\u7EC3\uFF08${a}\uFF09`)}let o=[],d=(p,c)=>{c!==void 0&&(s[p]=c,o.push(p))};return d("swanlabRun",e.swanlabRun),d("status",e.status),d("decision",e.decision),d("endedAt",e.endedAt),d("note",e.note),e.hyperparams&&(s.hyperparams={...s.hyperparams??{},...e.hyperparams},o.push("hyperparams")),await O(a,`${JSON.stringify(s,void 0,2)}
|
|
24
|
+
`,"utf8"),R({seq:e.seq,updated:o,run:s})}})),r.tools.register(T({name:"training_metrics_set",description:"Record the key metrics to watch for this project, into .train/metrics.json. Derive them by READING the training code \u2014 the swanlab.log() call sites, the printed log fields, the framework defaults \u2014 not by guessing and not by querying an experiment that may not exist yet. At most five: they render above the composer and the dashboard pulls only these keys. Give each one a direction (is lower or higher better, which cross-run comparison needs) and the source line you inferred it from, so the choice can be checked.",parameters:{keys:{type:"array",required:!0,description:"The metric keys to watch, most important first. At most five.",items:{type:"object",additionalProperties:!1,properties:{name:{type:"string",required:!0,description:'Exact SwanLab metric key, e.g. "train/loss".'},direction:{type:"string",required:!0,enum:["lower","higher"],description:"Whether lower or higher is better."},source:{type:"string",required:!0,description:`Where this came from, e.g. "train.py:142 swanlab.log({'train/loss': ...})".`}}}},inferredFrom:{type:"string",description:"Code revision the inference was made against."}},output:{schema:{type:"object",additionalProperties:!1,properties:{path:{type:"string",required:!0},keys:{type:"array",required:!0,items:{type:"string"}},dropped:{type:"array",required:!0,items:{type:"string"}}}},render:(e,t)=>[{type:"text",text:`\u5173\u952E\u6307\u6807\u5DF2\u8BB0\u5F55\uFF08${t.keys.length} \u4E2A\uFF09\uFF1A${t.keys.join(", ")}`+(t.dropped.length>0?`
|
|
25
|
+
\u8D85\u8FC7 5 \u4E2A\u4E0A\u9650\uFF0C\u5DF2\u4E22\u5F03\uFF1A${t.dropped.join(", ")}`:"")+"\n\u8FD9\u4E9B\u952E\u662F\u4ECE\u4EE3\u7801\u91CC\u63A8\u65AD\u51FA\u6765\u7684\uFF1A\u5B9E\u9A8C\u4E00\u65E6\u6709\u6570\u636E\uFF0C\u7528 `swanlab api run series <user/project/run_id>` \u590D\u6838\u5B83\u4EEC\u662F\u5426\u771F\u7684\u5B58\u5728\uFF1B\u9762\u677F\u5BF9\u4E0D\u4E0A\u65F6\u4E5F\u4F1A\u5728\u6307\u6807\u5E26\u4E0A\u70B9\u540D\uFF0C\u5C4A\u65F6\u91CD\u65B0\u5206\u6790\u5E76\u518D\u8C03\u7528\u4E00\u6B21\u672C\u5DE5\u5177\u3002"}]},async execute(e,t){let i=D(t);await F(i);let a=e.keys.slice(0,Se),s=e.keys.slice(Se).map(d=>d.name),o=x(q(i),"metrics.json");return await O(o,`${JSON.stringify({keys:a,inferredAt:new Date().toISOString(),inferredFrom:e.inferredFrom},void 0,2)}
|
|
26
|
+
`,"utf8"),R({path:o,keys:a.map(d=>d.name),dropped:s})}})),r.tools.register(T({name:"training_ledger",description:"Read this project's training ledger: every recorded run newest first, plus the key-metric list. Call it before proposing the next experiment so the proposal is grounded in what has already been tried and what was concluded about it, rather than in the current conversation alone.",parameters:{limit:{type:"integer",description:"How many runs to return, newest first. Default 10."}},output:{schema:{type:"object",additionalProperties:!1,properties:{workspace:{type:"string",required:!0},total:{type:"integer",required:!0},runs:{type:"array",required:!0,items:{type:"object",additionalProperties:!0,properties:{}}},metricKeys:{type:"array",required:!0,items:{type:"object",additionalProperties:!0,properties:{}}}}},render:(e,t)=>[{type:"text",text:t.total===0?"\u8FD9\u4E2A\u9879\u76EE\u8FD8\u6CA1\u6709\u8BB0\u5F55\u8FC7\u8BAD\u7EC3\u8FD0\u884C\u3002":`\u5171 ${t.total} \u6B21\u8BAD\u7EC3\uFF0C\u6700\u8FD1\u4E00\u6B21\u662F\u7B2C ${t.runs[0]?.seq} \u6B21\uFF08${t.runs[0]?.status??"\u72B6\u6001\u672A\u77E5"}\uFF09\u3002\u5173\u952E\u6307\u6807\uFF1A${t.metricKeys.map(i=>i.name).join(", ")||"\u5C1A\u672A\u5206\u6790"}`}]},async execute(e,t){let i=D(t),a=await H(i),s=[];try{s=JSON.parse(await V(x(q(i),"metrics.json"),"utf8"))?.keys??[]}catch{s=[]}return R({workspace:i,total:a.length,runs:a.slice(0,e.limit??10),metricKeys:s})}})),r.tools.register(T({name:"training_compare",description:"Deterministically compare runs of THIS project across the key metrics: per-run final / best value, tail-window trend (plateau / improving / worsening), and the winner per metric by its recorded direction. Use this INSTEAD of pulling each run's metrics yourself and comparing numbers in your head \u2014 these numbers decide who gets promoted, so they come from arithmetic, not from the model. Defaults to every ledger run that has a SwanLab run linked; interpretation (why the winner won, which knob to turn next) stays with you and the training-research skill's rule table. The result is a summary \u2014 do not restate the whole table back into the conversation.",parameters:{seqs:{type:"array",items:{type:"integer"},description:"Ledger sequence numbers to compare. Omitted: every run with a linked SwanLab run (max 8, oldest first)."},extraRuns:{type:"array",items:{type:"string"},description:"Off-ledger SwanLab run paths (user/project/run_id) to include, e.g. a colleague's baseline."},keys:{type:"array",items:{type:"string"},description:"Metric keys to compare. Omitted: the keys in .train/metrics.json (their directions apply either way)."},tailK:{type:"integer",description:"Tail window (points) for the trend verdict. Default 5."}},output:{schema:{type:"object",additionalProperties:!1,properties:{workspace:{type:"string",required:!0},keys:{type:"array",required:!0,items:{type:"object",additionalProperties:!1,properties:{name:{type:"string",required:!0},direction:{type:"string",required:!0,enum:["lower","higher"]}}}},rows:{type:"array",required:!0,items:{type:"object",additionalProperties:!0,properties:{}}},winners:{type:"array",required:!0,items:{type:"object",additionalProperties:!0,properties:{}}},warnings:{type:"array",required:!0,items:{type:"string"}}}},render:(e,t)=>{let i=t.winners.filter(a=>a.winner).map(a=>{let s=typeof a.marginPct=="number"?`\uFF0C\u9886\u5148 ${a.runnerUp} ${a.marginPct.toFixed(1)}%`:"";return`${a.key}\uFF08${a.direction==="higher"?"\u8D8A\u9AD8\u8D8A\u597D":"\u8D8A\u4F4E\u8D8A\u597D"}\uFF09\uFF1A${a.winner} = ${a.final}${s}`});return[{type:"text",text:(i.length?`\u8D62\u5BB6\u5224\u5B9A\uFF1A
|
|
27
|
+
${i.join(`
|
|
28
|
+
`)}`:"\u6CA1\u6709\u4EFB\u4F55 run \u5728\u8FD9\u4E9B\u6307\u6807\u4E0A\u6709\u6570\u636E\u3002")+(t.warnings.length?`
|
|
29
|
+
\u6CE8\u610F\uFF1A${t.warnings.join("\uFF1B")}`:"")+`
|
|
30
|
+
\u5B8C\u6574\u9010 run \u7EDF\u8BA1\u5728\u7ED3\u6784\u5316\u7ED3\u679C\u91CC\uFF1B\u89E3\u91CA\u4E0E\u4E0B\u4E00\u6B65\u6309 training-research \u7684\u8BCA\u65AD\u89C4\u5219\u8868\u6765\uFF0C\u4E0D\u8981\u628A\u8868\u539F\u6837\u6284\u56DE\u5BF9\u8BDD\u3002`}]}},async execute(e,t){let i=D(t),a=U(r,"swanlab","SwanLab"),s=await a.connection(i);if(!s.apiKey||!s.apiHost)throw new Error("SwanLab \u5C1A\u672A\u914D\u7F6E\uFF08\u7F3A apiKey/apiHost\uFF09\uFF1A\u5148\u5728\u8BBE\u7F6E\u91CC\u8FDE\u597D\uFF0C\u6216\u68C0\u67E5 .train/secrets.json");let o=[],d=await H(i),p;e.seqs?.length?p=e.seqs.map(h=>{let $=d.find(b=>b.seq===h);if(!$)throw new Error(`\u8D26\u672C\u91CC\u6CA1\u6709\u7B2C ${h} \u6B21\u8BAD\u7EC3`);return $.swanlabRun||o.push(`\u7B2C ${h} \u6B21\u6CA1\u6709\u5173\u8054 SwanLab run\uFF0C\u8DF3\u8FC7`),$}):(p=d.filter(h=>typeof h.swanlabRun=="string"&&h.swanlabRun.length>0),p.length>8&&(o.push(`\u8D26\u672C\u6709 ${p.length} \u4E2A\u53EF\u6BD4 run\uFF0C\u53EA\u53D6\u6700\u8FD1 8 \u4E2A`),p=p.slice(0,8)));let c=p.filter(h=>h.swanlabRun).reverse().map(h=>({label:`#${h.seq}`,runPath:h.swanlabRun}));for(let h of e.extraRuns??[])c.push({label:h,runPath:h});if(c.length===0)throw new Error("\u6CA1\u6709\u53EF\u6BD4\u7684 run\uFF1A\u8D26\u672C\u91CC\u8FD8\u6CA1\u6709\u5173\u8054\u4E86 SwanLab run \u7684\u8BAD\u7EC3");let y=[];try{y=JSON.parse(await V(x(q(i),"metrics.json"),"utf8"))?.keys??[]}catch{y=[]}if(e.keys?.length&&(y=e.keys.map(h=>{let $=y.find(b=>b.name===h);return $||o.push(`${h} \u4E0D\u5728 metrics.json \u91CC\uFF0Cdirection \u6309 lower \u5904\u7406`),{name:h,direction:$?.direction}})),y.length===0)throw new Error("\u6CA1\u6709\u53EF\u6BD4\u7684\u6307\u6807\uFF1A.train/metrics.json \u4E3A\u7A7A\u4E14\u672A\u663E\u5F0F\u7ED9 keys");let u=y.map(h=>h.name),l=[];for(let h of c)try{let $=await a.api.runMetrics(s,h.runPath,u,{signal:t.signal});l.push({label:h.label,series:a.api.metricSeries($)})}catch($){o.push(`${h.label}\uFF08${h.runPath}\uFF09\u62C9\u53D6\u5931\u8D25\uFF1A${$?.message??$}`),l.push({label:h.label,series:{}})}let{rows:m,winners:w}=X(l,y,{tailK:e.tailK??5});return R({workspace:i,keys:y.map(h=>({name:h.name,direction:C(h.direction)})),rows:m,winners:w,warnings:o})}}))}export{dt as apply,at as inject,ot as name};
|
package/dist/ui-host.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-JZXLCA2E.mjs";var n="runnpu-modes-ui";function o(){}export{o as apply,n as name};
|